diff --git a/src/Mod/Draft/draftobjects/layer.py b/src/Mod/Draft/draftobjects/layer.py index 720f6a86b6..197627659b 100644 --- a/src/Mod/Draft/draftobjects/layer.py +++ b/src/Mod/Draft/draftobjects/layer.py @@ -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") diff --git a/src/Mod/Draft/draftviewproviders/view_layer.py b/src/Mod/Draft/draftviewproviders/view_layer.py index 8ae7cb29a4..4e23ee1e6e 100644 --- a/src/Mod/Draft/draftviewproviders/view_layer.py +++ b/src/Mod/Draft/draftviewproviders/view_layer.py @@ -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