Draft: allow objects in layers to have overrides (#19207)
* Draft: allow objects in layers to have overrides Fixes #17844. 1. When an object is put in a layer it always adopts the properties of that layer. 2. Properties of the object that are subsequently changed are considered overrides. 3. When the properties of the layer, or the object in the layer, are then changed so that they match again, there is no longer an override, and properties are synced again. 4. The layer and the layer container object get an extra Tree view context menu option "Reassign properties of layer"/"Reassign properties of all layers".
This commit is contained in:
@@ -113,12 +113,49 @@ class Layer:
|
||||
"""Execute when the object is created or recomputed. Do nothing."""
|
||||
pass
|
||||
|
||||
def _get_other_layers(self, obj, child):
|
||||
other_lyrs = []
|
||||
for find in child.Document.findObjects(Type="App::FeaturePython"):
|
||||
if utils.get_type(find) == "Layer" and find != obj and child in find.Group:
|
||||
other_lyrs.append(find)
|
||||
return other_lyrs
|
||||
|
||||
def onBeforeChange(self, obj, prop):
|
||||
if prop == "Group":
|
||||
self.oldGroup = obj.Group
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop != "Group":
|
||||
return
|
||||
vobj = getattr(obj, "ViewObject", None)
|
||||
old_grp = getattr(self, "oldGroup", [])
|
||||
for child in obj.Group:
|
||||
if child in old_grp:
|
||||
continue
|
||||
for other_lyr in self._get_other_layers(obj, child):
|
||||
other_grp = other_lyr.Group
|
||||
other_grp.remove(child)
|
||||
other_lyr.Group = other_grp
|
||||
if vobj is None:
|
||||
continue
|
||||
for prop in ("LineColor", "ShapeAppearance", "LineWidth", "DrawStyle", "Visibility"):
|
||||
vobj.Proxy.change_view_properties(vobj, prop, old_prop=None, targets=[child])
|
||||
|
||||
def addObject(self, obj, child):
|
||||
"""Add an object to this object if not in the Group property."""
|
||||
group = obj.Group
|
||||
if child not in group:
|
||||
group.append(child)
|
||||
obj.Group = group
|
||||
if utils.get_type(child) in ("Layer", "LayerContainer"):
|
||||
return
|
||||
grp = obj.Group
|
||||
if child in grp:
|
||||
return
|
||||
grp.append(child)
|
||||
obj.Group = grp
|
||||
|
||||
def removeObject(self, obj, child):
|
||||
grp = obj.Group
|
||||
if not child in grp:
|
||||
return
|
||||
grp.remove(child)
|
||||
obj.Group = grp
|
||||
|
||||
|
||||
# Alias for compatibility with v0.18 and earlier
|
||||
@@ -147,9 +184,9 @@ class LayerContainer:
|
||||
Update the value of `Group` by sorting the contained layers
|
||||
by `Label`.
|
||||
"""
|
||||
group = obj.Group
|
||||
group.sort(key=lambda layer: layer.Label)
|
||||
obj.Group = group
|
||||
grp = obj.Group
|
||||
grp.sort(key=lambda layer: layer.Label)
|
||||
obj.Group = grp
|
||||
|
||||
def dumps(self):
|
||||
"""Return a tuple of objects to save or None."""
|
||||
@@ -162,7 +199,6 @@ class LayerContainer:
|
||||
self.Type = state
|
||||
|
||||
|
||||
# Similar function as in view_layer.py
|
||||
def get_layer(obj):
|
||||
"""Get the layer the object belongs to."""
|
||||
finds = obj.Document.findObjects(Name="LayerContainer")
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
|
||||
## \addtogroup draftviewproviders
|
||||
# @{
|
||||
import pivy.coin as coin
|
||||
import PySide.QtCore as QtCore
|
||||
import PySide.QtGui as QtGui
|
||||
from pivy import coin
|
||||
from PySide import QtCore
|
||||
from PySide import QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
from draftobjects.layer import Layer
|
||||
from draftobjects.layer import get_layer
|
||||
from draftutils import params
|
||||
from draftutils import utils
|
||||
from draftutils.translate import translate
|
||||
@@ -216,14 +216,43 @@ class ViewProviderLayer:
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
"""Execute when a property from the Proxy class is changed."""
|
||||
if prop == "Group":
|
||||
for _prop in ("LineColor", "ShapeAppearance", "LineWidth",
|
||||
"DrawStyle", "Visibility"):
|
||||
self.onChanged(obj.ViewObject, _prop)
|
||||
if prop == "Label":
|
||||
self._paint_tree_icon(obj.ViewObject)
|
||||
|
||||
def change_view_properties(self, vobj, prop):
|
||||
"""Iterate over the contents and change the properties."""
|
||||
obj = vobj.Object
|
||||
def change_view_properties(self, vobj, prop, old_prop=None, targets=None):
|
||||
"""Change the properties of the targets, or of all objects in the layer group.
|
||||
|
||||
A child's property is only changed if it is not overridden (if its value
|
||||
matches old_prop).
|
||||
"""
|
||||
|
||||
def _color_is_same(col1, col2):
|
||||
# Ignore alpha and round RGB values
|
||||
return [round(c, 2) for c in col1[:3]] == [round(c, 2) for c in col2[:3]]
|
||||
|
||||
def _app_material_is_same(mat1, mat2):
|
||||
for prop in (
|
||||
"AmbientColor",
|
||||
"DiffuseColor",
|
||||
"EmissiveColor",
|
||||
"Shininess",
|
||||
"SpecularColor",
|
||||
"Transparency",
|
||||
):
|
||||
if "Color" in prop:
|
||||
if not _color_is_same(getattr(mat1, prop), getattr(mat2, prop)):
|
||||
return False
|
||||
elif getattr(mat1, prop) != getattr(mat2, prop):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _prop_is_same(prop1, prop2):
|
||||
if isinstance(prop1, tuple):
|
||||
if isinstance(prop1[0], App.Material):
|
||||
# We do not check the length of the ShapeAppearance
|
||||
return _app_material_is_same(prop1[0], prop2[0])
|
||||
return _color_is_same(prop1, prop2)
|
||||
return prop1 == prop2
|
||||
|
||||
# Return if the property does not exist
|
||||
if not hasattr(vobj, prop):
|
||||
@@ -231,27 +260,30 @@ class ViewProviderLayer:
|
||||
# If the override properties are not set return without change
|
||||
if prop == "LineColor" and not vobj.OverrideLineColorChildren:
|
||||
return
|
||||
elif prop == "ShapeAppearance" and not vobj.OverrideShapeAppearanceChildren:
|
||||
if prop == "ShapeAppearance" and not vobj.OverrideShapeAppearanceChildren:
|
||||
return
|
||||
|
||||
for target_obj in obj.Group:
|
||||
for target_obj in (targets if targets is not None else vobj.Object.Group):
|
||||
target_vobj = target_obj.ViewObject
|
||||
|
||||
# This checks that the property exists in the target object,
|
||||
# and then sets the target property accordingly
|
||||
if hasattr(target_vobj, prop):
|
||||
setattr(target_vobj, prop, getattr(vobj, prop))
|
||||
if old_prop is None \
|
||||
or _prop_is_same(getattr(target_vobj, prop), old_prop):
|
||||
setattr(target_vobj, prop, getattr(vobj, prop))
|
||||
|
||||
# Use the line color for the point color and text color
|
||||
if prop == "LineColor":
|
||||
if hasattr(target_vobj, "PointColor"):
|
||||
target_vobj.PointColor = vobj.LineColor
|
||||
if hasattr(target_vobj, "TextColor"):
|
||||
target_vobj.TextColor = vobj.LineColor
|
||||
# Use the line width for the point size
|
||||
elif prop == "LineWidth":
|
||||
if hasattr(target_vobj, "PointSize"):
|
||||
target_vobj.PointSize = vobj.LineWidth
|
||||
# Use the line color for the point color and text color,
|
||||
# and the line width for the point size
|
||||
dic = {"LineColor": ("PointColor", "TextColor"), "LineWidth": ("PointSize", )}
|
||||
if prop in dic:
|
||||
for target_prop in dic[prop]:
|
||||
if hasattr(target_vobj, target_prop):
|
||||
if old_prop is None \
|
||||
or _prop_is_same(getattr(target_vobj, target_prop), old_prop):
|
||||
setattr(target_vobj, target_prop, getattr(vobj, prop))
|
||||
|
||||
def onBeforeChange(self, vobj, prop):
|
||||
if prop in ("LineColor", "ShapeAppearance", "LineWidth", "DrawStyle", "Visibility"):
|
||||
setattr(self, "old" + prop, getattr(vobj, prop))
|
||||
|
||||
def onChanged(self, vobj, prop):
|
||||
"""Execute when a view property is changed."""
|
||||
@@ -290,58 +322,52 @@ class ViewProviderLayer:
|
||||
"DrawStyle", "Visibility")
|
||||
and hasattr(vobj, "OverrideLineColorChildren")
|
||||
and hasattr(vobj, "OverrideShapeAppearanceChildren")):
|
||||
self.change_view_properties(vobj, prop)
|
||||
old_prop = getattr(self, "old" + prop, None)
|
||||
self.change_view_properties(vobj, prop, old_prop)
|
||||
if hasattr(self, "old" + prop):
|
||||
delattr(self, "old" + prop)
|
||||
|
||||
# Paint the layer icon in the tree view:
|
||||
if (prop in ("LineColor", "ShapeAppearance")
|
||||
and hasattr(vobj, "LineColor")
|
||||
and hasattr(vobj, "ShapeAppearance")):
|
||||
l_color = vobj.LineColor
|
||||
s_color = vobj.ShapeAppearance[0].DiffuseColor
|
||||
if prop in ("LineColor", "ShapeAppearance"):
|
||||
self._paint_tree_icon(vobj)
|
||||
|
||||
l_color = QtGui.QColor(int(l_color[0] * 255),
|
||||
int(l_color[1] * 255),
|
||||
int(l_color[2] * 255))
|
||||
s_color = QtGui.QColor(int(s_color[0] * 255),
|
||||
int(s_color[1] * 255),
|
||||
int(s_color[2] * 255))
|
||||
p1 = QtCore.QPointF(2, 17)
|
||||
p2 = QtCore.QPointF(13, 8)
|
||||
p3 = QtCore.QPointF(30, 15)
|
||||
p4 = QtCore.QPointF(20, 25)
|
||||
def _paint_tree_icon(self, vobj):
|
||||
"""Paint the layer icon in the tree view."""
|
||||
if not hasattr(vobj, "LineColor"):
|
||||
return
|
||||
if not hasattr(vobj, "ShapeAppearance"):
|
||||
return
|
||||
l_color = vobj.LineColor
|
||||
s_color = vobj.ShapeAppearance[0].DiffuseColor
|
||||
|
||||
image = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32)
|
||||
image.fill(QtCore.Qt.transparent)
|
||||
l_color = QtGui.QColor(int(l_color[0] * 255),
|
||||
int(l_color[1] * 255),
|
||||
int(l_color[2] * 255))
|
||||
s_color = QtGui.QColor(int(s_color[0] * 255),
|
||||
int(s_color[1] * 255),
|
||||
int(s_color[2] * 255))
|
||||
p1 = QtCore.QPointF(2, 17)
|
||||
p2 = QtCore.QPointF(13, 8)
|
||||
p3 = QtCore.QPointF(30, 15)
|
||||
p4 = QtCore.QPointF(20, 25)
|
||||
|
||||
pt = QtGui.QPainter(image)
|
||||
pt.setBrush(QtGui.QBrush(s_color, QtCore.Qt.SolidPattern))
|
||||
pt.drawPolygon([p1, p2, p3, p4])
|
||||
pt.setPen(QtGui.QPen(l_color, 2,
|
||||
QtCore.Qt.SolidLine, QtCore.Qt.FlatCap))
|
||||
pt.drawPolygon([p1, p2, p3, p4])
|
||||
pt.end()
|
||||
image = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32)
|
||||
image.fill(QtCore.Qt.transparent)
|
||||
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
image.save(buffer, "XPM")
|
||||
pt = QtGui.QPainter(image)
|
||||
pt.setBrush(QtGui.QBrush(s_color, QtCore.Qt.SolidPattern))
|
||||
pt.drawPolygon([p1, p2, p3, p4])
|
||||
pt.setPen(QtGui.QPen(l_color, 2,
|
||||
QtCore.Qt.SolidLine, QtCore.Qt.FlatCap))
|
||||
pt.drawPolygon([p1, p2, p3, p4])
|
||||
pt.end()
|
||||
|
||||
self.icondata = byte_array.data().decode("latin1")
|
||||
vobj.signalChangeIcon()
|
||||
byte_array = QtCore.QByteArray()
|
||||
buffer = QtCore.QBuffer(byte_array)
|
||||
buffer.open(QtCore.QIODevice.WriteOnly)
|
||||
image.save(buffer, "XPM")
|
||||
|
||||
def _get_layer(self, obj):
|
||||
"""Get the layer the object belongs to.
|
||||
"""
|
||||
from draftmake.make_layer import get_layer_container
|
||||
# First look in the LayerContainer:
|
||||
for layer in get_layer_container().Group:
|
||||
if utils.get_type(layer) == "Layer" and obj in layer.Group:
|
||||
return layer
|
||||
# If not found, look through all App::FeaturePython objects (not just layers):
|
||||
for find in obj.Document.findObjects(Type="App::FeaturePython"):
|
||||
if utils.get_type(find) == "Layer" and obj in find.Group:
|
||||
return find
|
||||
return None
|
||||
self.icondata = byte_array.data().decode("latin1")
|
||||
vobj.signalChangeIcon()
|
||||
|
||||
def canDragObject(self, obj):
|
||||
"""Return True to allow dragging one object from the Layer.
|
||||
@@ -356,7 +382,7 @@ class ViewProviderLayer:
|
||||
if hasattr(parent, "Group"):
|
||||
old_data.append([parent, parent.Group])
|
||||
# Layers are not in the Inlist because a layer's Group is App::PropertyLinkListHidden:
|
||||
layer = self._get_layer(obj)
|
||||
layer = get_layer(obj)
|
||||
if layer is not None:
|
||||
old_data.append([layer, layer.Group])
|
||||
if old_data:
|
||||
@@ -369,14 +395,11 @@ class ViewProviderLayer:
|
||||
"""Return True to allow dragging many objects from the Layer."""
|
||||
return True
|
||||
|
||||
def dragObject(self, vobj, otherobj):
|
||||
def dragObject(self, vobj, child):
|
||||
"""Remove the object that was dragged from the layer."""
|
||||
layer = vobj.Object
|
||||
if otherobj in layer.Group:
|
||||
group = layer.Group
|
||||
group.remove(otherobj)
|
||||
layer.Group = group
|
||||
App.ActiveDocument.recompute()
|
||||
obj = vobj.Object
|
||||
obj.Proxy.removeObject(obj, child)
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
def canDropObject(self, obj):
|
||||
"""Return true to allow dropping one object.
|
||||
@@ -395,30 +418,14 @@ class ViewProviderLayer:
|
||||
"""Return true to allow dropping many objects."""
|
||||
return True
|
||||
|
||||
def dropObject(self, vobj, otherobj):
|
||||
"""Add object that was dropped into the Layer to the group.
|
||||
def dropObject(self, vobj, child):
|
||||
"""Add the object that was dropped on the Layer to the group.
|
||||
|
||||
If the object being dropped is itself a `'Layer'`,
|
||||
return immediately to prevent dropping a layer inside a layer,
|
||||
at least for now.
|
||||
This also results in a call to `change_view_properties` to update the
|
||||
view properties of the child.
|
||||
"""
|
||||
if utils.get_type(otherobj) == "Layer":
|
||||
return
|
||||
|
||||
# We assume a single old layer...
|
||||
|
||||
old_layer = self._get_layer(otherobj)
|
||||
if old_layer is not None:
|
||||
group = old_layer.Group
|
||||
group.remove(otherobj)
|
||||
old_layer.Group = group
|
||||
|
||||
new_layer = vobj.Object
|
||||
if otherobj not in new_layer.Group:
|
||||
group = new_layer.Group
|
||||
group.append(otherobj)
|
||||
new_layer.Group = group
|
||||
|
||||
obj = vobj.Object
|
||||
obj.Proxy.addObject(obj, child)
|
||||
App.ActiveDocument.recompute()
|
||||
|
||||
def update_groups_after_drag_drop(self):
|
||||
@@ -450,7 +457,7 @@ class ViewProviderLayer:
|
||||
old_layer = old_parent
|
||||
break
|
||||
|
||||
new_layer = self._get_layer(child)
|
||||
new_layer = get_layer(child)
|
||||
if new_layer == old_layer:
|
||||
continue
|
||||
|
||||
@@ -497,6 +504,12 @@ class ViewProviderLayer:
|
||||
action_activate.triggered.connect(self.activate)
|
||||
menu.addAction(action_activate)
|
||||
|
||||
action_reassign = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Apply.svg"),
|
||||
translate("draft", "Reassign properties of layer"),
|
||||
menu)
|
||||
action_reassign.triggered.connect(self.reassign_props)
|
||||
menu.addAction(action_reassign)
|
||||
|
||||
action_select = QtGui.QAction(QtGui.QIcon(":/icons/Draft_SelectGroup.svg"),
|
||||
translate("draft", "Select layer contents"),
|
||||
menu)
|
||||
@@ -511,11 +524,15 @@ class ViewProviderLayer:
|
||||
Gui.activateWorkbench("DraftWorkbench")
|
||||
Gui.runCommand("Draft_AutoGroup")
|
||||
|
||||
def reassign_props(self):
|
||||
for prop in ("LineColor", "ShapeAppearance", "LineWidth", "DrawStyle", "Visibility"):
|
||||
self.onChanged(self.Object.ViewObject, prop)
|
||||
|
||||
def select_contents(self):
|
||||
"""Select the contents of the layer."""
|
||||
Gui.Selection.clearSelection()
|
||||
for layer_obj in self.Object.Group:
|
||||
Gui.Selection.addSelection(layer_obj)
|
||||
for obj in self.Object.Group:
|
||||
Gui.Selection.addSelection(obj)
|
||||
|
||||
|
||||
class ViewProviderLayerContainer:
|
||||
@@ -535,17 +552,41 @@ class ViewProviderLayerContainer:
|
||||
|
||||
def setupContextMenu(self, vobj, menu):
|
||||
"""Set up actions to perform in the context menu."""
|
||||
action_add = QtGui.QAction(QtGui.QIcon(":/icons/Draft_NewLayer.svg"),
|
||||
translate("draft", "Add new layer"),
|
||||
menu)
|
||||
action_add.triggered.connect(self.add_layer)
|
||||
menu.addAction(action_add)
|
||||
|
||||
action_reassign = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Apply.svg"),
|
||||
translate("draft", "Reassign properties of all layers"),
|
||||
menu)
|
||||
action_reassign.triggered.connect(self.reassign_props)
|
||||
menu.addAction(action_reassign)
|
||||
|
||||
action_merge = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Layers.svg"),
|
||||
translate("draft", "Merge layer duplicates"),
|
||||
menu)
|
||||
action_merge.triggered.connect(self.merge_by_name)
|
||||
menu.addAction(action_merge)
|
||||
|
||||
action_add = QtGui.QAction(QtGui.QIcon(":/icons/Draft_NewLayer.svg"),
|
||||
translate("draft", "Add new layer"),
|
||||
menu)
|
||||
action_add.triggered.connect(self.add_layer)
|
||||
menu.addAction(action_add)
|
||||
def add_layer(self):
|
||||
"""Creates a new layer"""
|
||||
import Draft
|
||||
|
||||
doc = App.ActiveDocument
|
||||
doc.openTransaction(translate("draft", "Add new layer"))
|
||||
|
||||
Draft.make_layer(name=None, line_color=None, shape_color=None,
|
||||
line_width=None, draw_style=None, transparency=None)
|
||||
|
||||
doc.recompute()
|
||||
doc.commitTransaction()
|
||||
|
||||
def reassign_props(self):
|
||||
for obj in self.Object.Group:
|
||||
if utils.get_type(obj) == "Layer":
|
||||
obj.ViewObject.Proxy.reassign_props()
|
||||
|
||||
def merge_by_name(self):
|
||||
"""Merge the layers that have the same base label."""
|
||||
@@ -590,19 +631,6 @@ class ViewProviderLayerContainer:
|
||||
doc.recompute()
|
||||
doc.commitTransaction()
|
||||
|
||||
def add_layer(self):
|
||||
"""Creates a new layer"""
|
||||
import Draft
|
||||
|
||||
doc = App.ActiveDocument
|
||||
doc.openTransaction(translate("draft", "Add new layer"))
|
||||
|
||||
Draft.make_layer(name=None, line_color=None, shape_color=None,
|
||||
line_width=None, draw_style=None, transparency=None)
|
||||
|
||||
doc.recompute()
|
||||
doc.commitTransaction()
|
||||
|
||||
def dumps(self):
|
||||
"""Return a tuple of objects to save or None."""
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user