| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides the viewprovider code for the Layer object.""" |
| | |
| | |
| | |
| |
|
| | |
| | |
| | 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 get_layer |
| | from draftutils import params |
| | from draftutils import utils |
| | from draftutils.translate import translate |
| |
|
| |
|
| | class ViewProviderLayer: |
| | """The viewprovider for the Layer object.""" |
| |
|
| | def __init__(self, vobj): |
| | self.Object = vobj.Object |
| | self.set_properties(vobj) |
| |
|
| | vobj.Proxy = self |
| |
|
| | def set_properties(self, vobj): |
| | """Set the properties only if they don't already exist.""" |
| | properties = vobj.PropertiesList |
| | self.set_override_options(vobj, properties) |
| | self.set_visual_properties(vobj, properties) |
| |
|
| | def set_override_options(self, vobj, properties): |
| | """Set property options only if they don't already exist.""" |
| | if "OverrideLineColorChildren" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If it is true, the objects contained " |
| | "within this layer will adopt " |
| | "the line color of the layer", |
| | ) |
| | vobj.addProperty( |
| | "App::PropertyBool", "OverrideLineColorChildren", "Layer", _tip, locked=True |
| | ) |
| | vobj.OverrideLineColorChildren = True |
| |
|
| | if "OverrideShapeAppearanceChildren" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If it is true, the objects contained " |
| | "within this layer will adopt " |
| | "the shape appearance of the layer", |
| | ) |
| | vobj.addProperty( |
| | "App::PropertyBool", "OverrideShapeAppearanceChildren", "Layer", _tip, locked=True |
| | ) |
| | vobj.OverrideShapeAppearanceChildren = True |
| |
|
| | if "UsePrintColor" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "If it is true, the print color " |
| | "will be used when objects in this " |
| | "layer are placed on a TechDraw page", |
| | ) |
| | vobj.addProperty("App::PropertyBool", "UsePrintColor", "Print", _tip, locked=True) |
| |
|
| | def set_visual_properties(self, vobj, properties): |
| | """Set visual properties only if they don't already exist.""" |
| | if "LineColor" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", "The line color of the objects " "contained within this layer" |
| | ) |
| | vobj.addProperty("App::PropertyColor", "LineColor", "Layer", _tip, locked=True) |
| | vobj.LineColor = params.get_param_view("DefaultShapeLineColor") | 0x000000FF |
| |
|
| | if "ShapeColor" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", "The shape color of the objects " "contained within this layer" |
| | ) |
| | vobj.addProperty( |
| | "App::PropertyColor", "ShapeColor", "Layer", _tip, 4, locked=True |
| | ) |
| | vobj.ShapeColor = params.get_param_view("DefaultShapeColor") | 0x000000FF |
| |
|
| | if "ShapeAppearance" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The shape appearance of the objects " "contained within this layer", |
| | ) |
| | vobj.addProperty( |
| | "App::PropertyMaterialList", "ShapeAppearance", "Layer", _tip, locked=True |
| | ) |
| | material = App.Material() |
| | material.DiffuseColor = params.get_param_view("DefaultShapeColor") | 0x000000FF |
| | vobj.ShapeAppearance = (material,) |
| |
|
| | if "LineWidth" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", "The line width of the objects contained " "within this layer" |
| | ) |
| | vobj.addProperty("App::PropertyFloat", "LineWidth", "Layer", _tip, locked=True) |
| | vobj.LineWidth = params.get_param_view("DefaultShapeLineWidth") |
| |
|
| | if "DrawStyle" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", "The draw style of the objects contained " "within this layer" |
| | ) |
| | vobj.addProperty("App::PropertyEnumeration", "DrawStyle", "Layer", _tip, locked=True) |
| | vobj.DrawStyle = utils.DRAW_STYLES |
| | vobj.DrawStyle = params.get_param("DefaultDrawStyle") |
| |
|
| | if "Transparency" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", "The transparency of the objects " "contained within this layer" |
| | ) |
| | vobj.addProperty("App::PropertyPercent", "Transparency", "Layer", _tip, locked=True) |
| | vobj.Transparency = params.get_param_view("DefaultShapeTransparency") |
| |
|
| | if "LinePrintColor" not in properties: |
| | _tip = QT_TRANSLATE_NOOP( |
| | "App::Property", |
| | "The line color of the objects " |
| | "contained within this layer, " |
| | "when used on a TechDraw page", |
| | ) |
| | vobj.addProperty("App::PropertyColor", "LinePrintColor", "Print", _tip, locked=True) |
| | vobj.LinePrintColor = params.get_param("DefaultPrintColor") |
| |
|
| | def getIcon(self): |
| | """Return the path to the icon used by the viewprovider. |
| | |
| | Normally it returns the basic Layer icon, but if `icondata` exists |
| | it is the modified icon with the line and shape colors of the layer. |
| | """ |
| | if hasattr(self, "icondata"): |
| | return self.icondata |
| | return ":/icons/Draft_Layer.svg" |
| |
|
| | def attach(self, vobj): |
| | """Set up the scene sub-graph of the viewprovider.""" |
| | self.Object = vobj.Object |
| | sep = coin.SoGroup() |
| | vobj.addDisplayMode(sep, "Default") |
| |
|
| | def claimChildren(self): |
| | """Return objects that will be placed under it in the tree view. |
| | |
| | These are the elements of the `Group` property of the Proxy object. |
| | """ |
| | if hasattr(self, "Object") and hasattr(self.Object, "Group"): |
| | if getattr(self.Object.ViewObject, "HideChildren", False): |
| | return [] |
| | return self.Object.Group |
| |
|
| | def getDisplayModes(self, vobj): |
| | """Return the display modes that this viewprovider supports.""" |
| | return ["Default"] |
| |
|
| | def getDefaultDisplayMode(self): |
| | """Return the default display mode.""" |
| | return "Default" |
| |
|
| | def setDisplayMode(self, mode): |
| | """Return the saved display mode.""" |
| | return mode |
| |
|
| | def dumps(self): |
| | """Return a tuple of objects to save or None.""" |
| | return None |
| |
|
| | def loads(self, state): |
| | """Set the internal properties from the restored state.""" |
| | return None |
| |
|
| | def updateData(self, obj, prop): |
| | """Execute when a property from the Proxy class is changed.""" |
| | if prop == "Label": |
| | self._paint_tree_icon(obj.ViewObject) |
| |
|
| | 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): |
| | |
| | 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): |
| | |
| | return _app_material_is_same(prop1[0], prop2[0]) |
| | return _color_is_same(prop1, prop2) |
| | return prop1 == prop2 |
| |
|
| | |
| | if not hasattr(vobj, prop): |
| | return |
| | |
| | if prop == "LineColor" and not vobj.OverrideLineColorChildren: |
| | return |
| | if prop == "ShapeAppearance" and not vobj.OverrideShapeAppearanceChildren: |
| | return |
| |
|
| | for target_obj in (targets if targets is not None else vobj.Object.Group): |
| | target_vobj = target_obj.ViewObject |
| |
|
| | if hasattr(target_vobj, prop): |
| | if old_prop is None or _prop_is_same(getattr(target_vobj, prop), old_prop): |
| | setattr(target_vobj, prop, getattr(vobj, prop)) |
| |
|
| | |
| | |
| | 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.""" |
| |
|
| | if not hasattr(vobj, prop): |
| | return |
| |
|
| | if prop == "ShapeColor": |
| | if hasattr(vobj, "ShapeAppearance"): |
| | material = vobj.ShapeAppearance[0] |
| | if material.DiffuseColor != vobj.ShapeColor: |
| | material.DiffuseColor = vobj.ShapeColor |
| | vobj.ShapeAppearance = (material,) |
| | |
| | return |
| |
|
| | if prop == "Transparency": |
| | if hasattr(vobj, "ShapeAppearance"): |
| | material = vobj.ShapeAppearance[0] |
| | if material.Transparency != vobj.Transparency / 100: |
| | material.Transparency = vobj.Transparency / 100 |
| | vobj.ShapeAppearance = (material,) |
| | |
| | return |
| |
|
| | if ( |
| | prop == "ShapeAppearance" |
| | and hasattr(vobj, "ShapeColor") |
| | and hasattr(vobj, "Transparency") |
| | ): |
| | material = vobj.ShapeAppearance[0] |
| | if material.DiffuseColor != vobj.ShapeColor: |
| | vobj.ShapeColor = material.DiffuseColor |
| | if material.Transparency != vobj.Transparency / 100: |
| | vobj.Transparency = int(material.Transparency * 100) |
| |
|
| | if ( |
| | prop in ("LineColor", "ShapeAppearance", "LineWidth", "DrawStyle", "Visibility") |
| | and hasattr(vobj, "OverrideLineColorChildren") |
| | and hasattr(vobj, "OverrideShapeAppearanceChildren") |
| | ): |
| | old_prop = getattr(self, "old" + prop, None) |
| | self.change_view_properties(vobj, prop, old_prop) |
| | if hasattr(self, "old" + prop): |
| | delattr(self, "old" + prop) |
| |
|
| | if prop in ("LineColor", "ShapeAppearance"): |
| | self._paint_tree_icon(vobj) |
| |
|
| | 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 |
| |
|
| | 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) |
| |
|
| | image = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32) |
| | image.fill(QtCore.Qt.transparent) |
| |
|
| | 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() |
| |
|
| | byte_array = QtCore.QByteArray() |
| | buffer = QtCore.QBuffer(byte_array) |
| | buffer.open(QtCore.QIODevice.WriteOnly) |
| | image.save(buffer, "XPM") |
| |
|
| | self.icondata = byte_array.data().decode("latin1") |
| | vobj.signalChangeIcon() |
| |
|
| | def canDragObject(self, obj): |
| | """Return True to allow dragging one object from the Layer. |
| | |
| | Also store parent group data for update_groups_after_drag_drop and |
| | trigger that function. |
| | """ |
| | if not hasattr(self, "old_parent_data"): |
| | self.old_parent_data = {} |
| | old_data = [] |
| | for parent in obj.InList: |
| | if hasattr(parent, "Group"): |
| | old_data.append([parent, parent.Group]) |
| | |
| | layer = get_layer(obj) |
| | if layer is not None: |
| | old_data.append([layer, layer.Group]) |
| | if old_data: |
| | self.old_parent_data.setdefault(obj, old_data) |
| | QtCore.QTimer.singleShot(0, self.update_groups_after_drag_drop) |
| |
|
| | return True |
| |
|
| | def canDragObjects(self): |
| | """Return True to allow dragging many objects from the Layer.""" |
| | return True |
| |
|
| | def dragObject(self, vobj, child): |
| | """Remove the object that was dragged from the layer.""" |
| | obj = vobj.Object |
| | obj.Proxy.removeObject(obj, child) |
| | App.ActiveDocument.recompute() |
| |
|
| | def canDropObject(self, obj): |
| | """Return true to allow dropping one object. |
| | |
| | If the object being dropped is itself a `'Layer'`, return `False` |
| | to prevent dropping a layer inside a layer, at least for now. |
| | |
| | Also store parent group data for update_groups_after_drag_drop and |
| | trigger that function. |
| | """ |
| | if utils.get_type(obj) == "Layer": |
| | return False |
| | return self.canDragObject(obj) |
| |
|
| | def canDropObjects(self): |
| | """Return true to allow dropping many objects.""" |
| | return True |
| |
|
| | def dropObject(self, vobj, child): |
| | """Add the object that was dropped on the Layer to the group. |
| | |
| | This also results in a call to `change_view_properties` to update the |
| | view properties of the child. |
| | """ |
| | obj = vobj.Object |
| | obj.Proxy.addObject(obj, child) |
| | App.ActiveDocument.recompute() |
| |
|
| | def update_groups_after_drag_drop(self): |
| | """Workaround function to improve the drag and drop behavior of Layer |
| | objects. |
| | |
| | The function processes the parent group data stored in the |
| | old_parent_data dictionary by canDragObject and canDropObject. |
| | """ |
| |
|
| | |
| | |
| | if (not hasattr(self, "old_parent_data")) or (not self.old_parent_data): |
| | return |
| |
|
| | |
| | |
| | |
| | parents_to_update = [] |
| |
|
| | |
| | for child, old_data in self.old_parent_data.items(): |
| |
|
| | |
| |
|
| | old_layer = None |
| | for old_parent, old_parent_group in old_data: |
| | if utils.get_type(old_parent) == "Layer": |
| | old_layer = old_parent |
| | break |
| |
|
| | new_layer = get_layer(child) |
| | if new_layer == old_layer: |
| | continue |
| |
|
| | elif new_layer is None: |
| | |
| | |
| | |
| | |
| | |
| | old_parents = [sub[0] for sub in old_data] |
| | for new_parent in child.InList: |
| | if ( |
| | hasattr(new_parent, "Group") and new_parent not in old_parents |
| | ): |
| | for old_parent, old_parent_group in old_data: |
| | if old_parent == old_layer: |
| | parents_to_update.append([old_parent, old_parent_group]) |
| | break |
| | break |
| |
|
| | else: |
| | |
| | |
| | for old_parent, old_parent_group in old_data: |
| | if utils.get_type(old_parent) != "Layer": |
| | parents_to_update.append([old_parent, old_parent_group]) |
| |
|
| | |
| | if parents_to_update: |
| | for old_parent, old_parent_group in parents_to_update: |
| | old_parent.Group = old_parent_group |
| | App.ActiveDocument.recompute() |
| |
|
| | self.old_parent_data = {} |
| |
|
| | def replaceObject(self, old_obj, new_obj): |
| | """Return immediately to prevent replacement of children.""" |
| | return |
| |
|
| | def setupContextMenu(self, vobj, menu): |
| | """Set up actions to perform in the context menu.""" |
| | action_activate = QtGui.QAction( |
| | QtGui.QIcon(":/icons/button_right.svg"), translate("draft", "Activate Layer"), menu |
| | ) |
| | 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, |
| | ) |
| | action_select.triggered.connect(self.select_contents) |
| | menu.addAction(action_select) |
| |
|
| | def activate(self): |
| | """Activate the selected layer, it becomes the Autogroup.""" |
| | Gui.Selection.clearSelection() |
| | Gui.Selection.addSelection(self.Object) |
| | if not "Draft_AutoGroup" in Gui.listCommands(): |
| | 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 obj in self.Object.Group: |
| | Gui.Selection.addSelection(obj) |
| |
|
| |
|
| | class ViewProviderLayerContainer: |
| | """The viewprovider for the LayerContainer object.""" |
| |
|
| | def __init__(self, vobj): |
| | self.Object = vobj.Object |
| | vobj.Proxy = self |
| |
|
| | def getIcon(self): |
| | """Return the path to the icon used by the viewprovider.""" |
| | return ":/icons/Draft_Layers.svg" |
| |
|
| | def attach(self, vobj): |
| | """Set up the scene sub-graph of the viewprovider.""" |
| | self.Object = vobj.Object |
| |
|
| | 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) |
| |
|
| | 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.""" |
| | doc = App.ActiveDocument |
| | doc.openTransaction(translate("draft", "Merge Layer Duplicates")) |
| |
|
| | layer_container = self.Object |
| | layers = [] |
| | for obj in layer_container.Group: |
| | if utils.get_type(obj) == "Layer": |
| | layers.append(obj) |
| |
|
| | to_delete = [] |
| | for layer in layers: |
| | |
| | |
| | base_label = layer.Label.rstrip("0123456789 ") |
| |
|
| | |
| | base = None |
| | for other_layer in layers: |
| | if ( |
| | (not other_layer in to_delete) |
| | and other_layer != layer |
| | and other_layer.Label.upper() == base_label.upper() |
| | ): |
| | base = other_layer |
| | break |
| |
|
| | if base: |
| | if layer.Group: |
| | base_group = base.Group |
| | for obj in layer.Group: |
| | if not obj in base_group: |
| | base_group.append(obj) |
| | base.Group = base_group |
| | to_delete.append(layer) |
| | elif layer.Label != base_label: |
| | layer.Label = base_label |
| |
|
| | for layer in to_delete: |
| | doc.removeObject(layer.Name) |
| |
|
| | doc.recompute() |
| | doc.commitTransaction() |
| |
|
| | def dumps(self): |
| | """Return a tuple of objects to save or None.""" |
| | return None |
| |
|
| | def loads(self, state): |
| | """Set the internal properties from the restored state.""" |
| | return None |
| |
|
| | def replaceObject(self, old_obj, new_obj): |
| | """Return immediately to prevent replacement of children.""" |
| | return |
| |
|
| |
|
| | |
| | _ViewProviderVisGroup = ViewProviderLayer |
| |
|
| | |
| |
|