From 831e5177171fe8af91867b18b0816ea7a9849966 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 2 Jul 2020 18:00:24 -0500 Subject: [PATCH] Draft: migrate Layer object and function to the new structure Move `make_layer` to `draftmake`; `Layer` and `LayerContainer` to `draftobjects`; `ViewProviderLayer` and `ViewProviderLayerContainer` to `draftviewproviders`. The make function and the classes are imported in `Draft.py` to support the usage of the older `VisGroup`. --- src/Mod/Draft/CMakeLists.txt | 3 + src/Mod/Draft/Draft.py | 13 +- src/Mod/Draft/DraftFillet.py | 8 +- src/Mod/Draft/DraftLayer.py | 460 ++---------------- src/Mod/Draft/draftguitools/gui_layers.py | 6 +- src/Mod/Draft/draftmake/make_layer.py | 256 ++++++++++ src/Mod/Draft/draftobjects/layer.py | 120 +++++ .../Draft/draftviewproviders/view_layer.py | 457 +++++++++++++++++ 8 files changed, 893 insertions(+), 430 deletions(-) create mode 100644 src/Mod/Draft/draftmake/make_layer.py create mode 100644 src/Mod/Draft/draftobjects/layer.py create mode 100644 src/Mod/Draft/draftviewproviders/view_layer.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 532d2eb77f05..24bfbbc1082d 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -117,6 +117,7 @@ SET(Draft_make_functions draftmake/make_facebinder.py draftmake/make_fillet.py draftmake/make_label.py + draftmake/make_layer.py draftmake/make_line.py draftmake/make_orthoarray.py draftmake/make_patharray.py @@ -150,6 +151,7 @@ SET(Draft_objects draftobjects/fillet.py draftobjects/draftlink.py draftobjects/label.py + draftobjects/layer.py draftobjects/dimension.py draftobjects/patharray.py draftobjects/point.py @@ -179,6 +181,7 @@ SET(Draft_view_providers draftviewproviders/view_fillet.py draftviewproviders/view_draftlink.py draftviewproviders/view_label.py + draftviewproviders/view_layer.py draftviewproviders/view_dimension.py draftviewproviders/view_point.py draftviewproviders/view_rectangle.py diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 9a8c43c8a82f..bd2490a3220c 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -368,10 +368,15 @@ if App.GuiUp: from draftviewproviders.view_fillet import ViewProviderFillet -# Layers -from DraftLayer import Layer as _VisGroup -from DraftLayer import ViewProviderLayer as _ViewProviderVisGroup -from DraftLayer import makeLayer +from draftobjects.layer import (Layer, + _VisGroup) + +from draftmake.make_layer import (make_layer, + makeLayer) + +if App.GuiUp: + from draftviewproviders.view_layer import (ViewProviderLayer, + _ViewProviderVisGroup) # Annotation objects from draftobjects.dimension import (LinearDimension, diff --git a/src/Mod/Draft/DraftFillet.py b/src/Mod/Draft/DraftFillet.py index 87bcc692a54c..39a7f4f94b4b 100644 --- a/src/Mod/Draft/DraftFillet.py +++ b/src/Mod/Draft/DraftFillet.py @@ -45,8 +45,10 @@ with the 0.19 development version. Since this module is only used to migrate older objects, it is only temporary, -and will be removed after one year of the original introduction of the tool, -that is, in August 2020. +and will be removed after one year, that is, in January 2021. + +The explanation of the migration methods is in the wiki page: +https://wiki.freecadweb.org/Scripted_objects_migration """ ## @package DraftFillet # \ingroup DRAFT @@ -54,7 +56,7 @@ # # This module is only required to migrate old objects created # from August 2019 to February 2020. It will be removed definitely -# in August 2020, as the new Fillet object should be available. +# in January 2021, as the new Fillet object should be available. import FreeCAD as App import draftobjects.fillet diff --git a/src/Mod/Draft/DraftLayer.py b/src/Mod/Draft/DraftLayer.py index ad67799e4f7d..c106619b64b1 100644 --- a/src/Mod/Draft/DraftLayer.py +++ b/src/Mod/Draft/DraftLayer.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- # *************************************************************************** -# * Copyright (c) 2009, 2010 Yorik van Havre * -# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2014 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -20,423 +21,42 @@ # * USA * # * * # *************************************************************************** -"""Provides the Layer object. - -The original Layer object was a VisGroup, but it was renamed to Layer -in the development cycle of 0.19. +"""Provides the Layer object. This module is deprecated. + +In 6f896d8f22 (April 2014) the `Layer` object was created, +but in 4e595bd7bb (June 2014) it was renamed to `VisGroup`. +However, it was not used a lot, so in commit 5ee99ca4e (June 2019) +it was renamed again to `Layer`, but this time it was improved to behave +more like a proper layer system to control the visual properties +of the contained objects. All new code was moved to this module. + +With the reorganization of the entire Draft workbench, the Layer object +and associated viewprovider, make function, and Gui Command +have been moved to the appropriate directories `draftobjects`, +`draftviewproviders`, `draftmake`, and `draftguitools`. +Therefore, this module is only required to migrate old objects +created with v0.18 and earlier, and certain development version of v0.19. + +Since this module is only used to migrate older objects, it is only temporary, +and will be removed after one year, that is, in July 2021. + +The explanation of the migration methods is in the wiki page: +https://wiki.freecadweb.org/Scripted_objects_migration """ ## @package DraftLayer # \ingroup DRAFT -# \brief Provides the Layer object - -import FreeCAD - -if FreeCAD.GuiUp: - import FreeCADGui - - -def translate(ctx, txt): - return txt - - -def QT_TRANSLATE_NOOP(ctx, txt): - return txt - - -"""This module contains everything related to Draft Layers""" - - -def makeLayer(name=None, linecolor=None, drawstyle=None, shapecolor=None, transparency=None): - """makeLayer([name,linecolor,drawstyle,shapecolor,transparency]): - creates a Layer object in the active document - """ - if not FreeCAD.ActiveDocument: - FreeCAD.Console.PrintError(translate("draft", "No active document. Aborting") + "\n") - return - obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "Layer") - Layer(obj) - if name: - obj.Label = name - else: - obj.Label = translate("draft", "Layer") - if FreeCAD.GuiUp: - ViewProviderLayer(obj.ViewObject) - if linecolor: - obj.ViewObject.LineColor = linecolor - if drawstyle: - obj.ViewObject.DrawStyle = drawstyle - if shapecolor: - obj.ViewObject.ShapeColor = shapecolor - if transparency: - obj.ViewObject.Transparency = transparency - getLayerContainer().addObject(obj) - return obj - - -def getLayerContainer(): - """getLayerContainer(): returns a group object to put layers in""" - for obj in FreeCAD.ActiveDocument.Objects: - if obj.Name == "LayerContainer": - return obj - obj = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroupPython", "LayerContainer") - obj.Label = translate("draft", "Layers") - LayerContainer(obj) - if FreeCAD.GuiUp: - ViewProviderLayerContainer(obj.ViewObject) - return obj - - -class Layer: - """The Draft Layer object""" - - def __init__(self, obj): - self.Type = "Layer" - obj.Proxy = self - self.Object = obj - self.setProperties(obj) - - def onDocumentRestored(self, obj): - self.setProperties(obj) - - def setProperties(self, obj): - if "Group" not in obj.PropertiesList: - obj.addProperty( - "App::PropertyLinkList", - "Group", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The objects that are part of this layer") - ) - - def __getstate__(self): - return self.Type - - def __setstate__(self, state): - if state: - self.Type = state - - def execute(self, obj): - pass - - def addObject(self, obj, child): - g = obj.Group - if child not in g: - g.append(child) - obj.Group = g - - -class ViewProviderLayer: - """A View Provider for the Layer object""" - - def __init__(self, vobj): - vobj.addProperty( - "App::PropertyBool", - "OverrideLineColorChildren", - "Layer", - QT_TRANSLATE_NOOP( - "App::Property", - "If on, the child objects of this layer will match its visual aspects" - ) - ) - vobj.addProperty( - "App::PropertyBool", - "OverrideShapeColorChildren", - "Layer", - QT_TRANSLATE_NOOP( - "App::Property", - "If on, the child objects of this layer will match its visual aspects" - ) - ) - vobj.addProperty( - "App::PropertyColor", - "LineColor", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The line color of the children of this layer") - ) - vobj.addProperty( - "App::PropertyColor", - "ShapeColor", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The shape color of the children of this layer") - ) - vobj.addProperty( - "App::PropertyFloat", - "LineWidth", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The line width of the children of this layer") - ) - vobj.addProperty( - "App::PropertyEnumeration", - "DrawStyle", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The draw style of the children of this layer") - ) - vobj.addProperty( - "App::PropertyInteger", - "Transparency", - "Layer", - QT_TRANSLATE_NOOP("App::Property", "The transparency of the children of this layer") - ) - vobj.DrawStyle = ["Solid", "Dashed", "Dotted", "Dashdot"] - - vobj.OverrideLineColorChildren = True - vobj.OverrideShapeColorChildren = True - c = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned("DefaultShapeLineColor", 255) - vobj.LineColor = (((c >> 24) & 0xFF) / 255, ((c >> 16) & 0xFF) / 255, ((c >> 8) & 0xFF) / 255) - w = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetInt("DefaultShapeLineWidth", 2) - vobj.LineWidth = w - c = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned("DefaultShapeColor", 4294967295) - vobj.ShapeColor = (((c >> 24) & 0xFF) / 255, ((c >> 16) & 0xFF) / 255, ((c >> 8) & 0xFF) / 255) - vobj.DrawStyle = "Solid" - - vobj.Proxy = self - - def getIcon(self): - if hasattr(self, "icondata"): - return self.icondata - import Draft_rc - return ":/icons/Draft_Layer.svg" - - def attach(self, vobj): - self.Object = vobj.Object - from pivy import coin - sep = coin.SoGroup() - vobj.addDisplayMode(sep, "Default") - return - - def claimChildren(self): - if hasattr(self, "Object") and hasattr(self.Object, "Group"): - return self.Object.Group - - def getDisplayModes(self, vobj): - return ["Default"] - - def getDefaultDisplayMode(self): - return "Default" - - def setDisplayMode(self, mode): - return mode - - def __getstate__(self): - return None - - def __setstate__(self, state): - return None - - def updateData(self, obj, prop): - if prop == "Group": - self.onChanged(obj.ViewObject, "LineColor") - - def onChanged(self, vobj, prop): - if hasattr(vobj, "OverrideLineColorChildren") and vobj.OverrideLineColorChildren: - if hasattr(vobj, "Object") and hasattr(vobj.Object, "Group"): - for o in vobj.Object.Group: - if o.ViewObject: - for p in ["LineColor", "ShapeColor", "LineWidth", "DrawStyle", "Transparency"]: - if p == "ShapeColor": - if hasattr(vobj, "OverrideShapeColorChildren") and vobj.OverrideShapeColorChildren: - if hasattr(vobj, p): - # see forum topic https://forum.freecadweb.org/viewtopic.php?f=23&t=42197 - setattr(o.ViewObject, p, getattr(vobj, p)) - else: - if hasattr(vobj, p) and hasattr(o.ViewObject, p): - setattr(o.ViewObject, p, getattr(vobj, p)) - - # give line color to texts - if hasattr(vobj, "LineColor") and hasattr(o.ViewObject, "TextColor"): - o.ViewObject.TextColor = vobj.LineColor - - if (prop == "Visibility") and hasattr(vobj, "Visibility"): - if hasattr(vobj, "Object") and hasattr(vobj.Object, "Group"): - for o in vobj.Object.Group: - if o.ViewObject and hasattr(o.ViewObject, "Visibility"): - o.ViewObject.Visibility = vobj.Visibility - - if (prop in ["LineColor", "ShapeColor"]) and hasattr(vobj, "LineColor") and hasattr(vobj, "ShapeColor"): - from PySide import QtCore, QtGui - lc = vobj.LineColor - sc = vobj.ShapeColor - lc = QtGui.QColor(int(lc[0] * 255), int(lc[1] * 255), int(lc[2] * 255)) - sc = QtGui.QColor(int(sc[0] * 255), int(sc[1] * 255), int(sc[2] * 255)) - p1 = QtCore.QPointF(2, 17) - p2 = QtCore.QPointF(13, 8) - p3 = QtCore.QPointF(30, 15) - p4 = QtCore.QPointF(20, 25) - im = QtGui.QImage(32, 32, QtGui.QImage.Format_ARGB32) - im.fill(QtCore.Qt.transparent) - pt = QtGui.QPainter(im) - pt.setBrush(QtGui.QBrush(sc, QtCore.Qt.SolidPattern)) - pt.drawPolygon([p1, p2, p3, p4]) - pt.setPen(QtGui.QPen(lc, 2, QtCore.Qt.SolidLine, QtCore.Qt.FlatCap)) - pt.drawPolygon([p1, p2, p3, p4]) - pt.end() - ba = QtCore.QByteArray() - b = QtCore.QBuffer(ba) - b.open(QtCore.QIODevice.WriteOnly) - im.save(b, "XPM") - self.icondata = ba.data().decode("latin1") - vobj.signalChangeIcon() - - def canDragObject(self, obj): - return True - - def canDragObjects(self): - return True - - def dragObject(self, vobj, otherobj): - if hasattr(vobj.Object, "Group"): - if otherobj in vobj.Object.Group: - g = vobj.Object.Group - g.remove(otherobj) - vobj.Object.Group = g - FreeCAD.ActiveDocument.recompute() - - def canDropObject(self, obj): - - if hasattr(obj, "Proxy") and isinstance(obj.Proxy, Layer): - # for now, prevent stacking layers - return False - return True - - def canDropObjects(self): - - return True - - def dropObject(self, vobj, otherobj): - - if hasattr(vobj.Object, "Group"): - if otherobj not in vobj.Object.Group: - if not (hasattr(otherobj, "Proxy") and isinstance(otherobj.Proxy, Layer)): - # for now, prevent stacking layers - g = vobj.Object.Group - g.append(otherobj) - vobj.Object.Group = g - # remove from all other layers (not automatic) - for parent in otherobj.InList: - if hasattr(parent, "Proxy") and isinstance(parent.Proxy, Layer): - if otherobj in parent.Group: - if parent != vobj.Object: - g = parent.Group - g.remove(otherobj) - parent.Group = g - FreeCAD.ActiveDocument.recompute() - - def setupContextMenu(self, vobj, menu): - - from PySide import QtCore, QtGui - import Draft_rc - action1 = QtGui.QAction( - QtGui.QIcon(":/icons/button_right.svg"), - translate("draft", "Activate this layer"), - menu - ) - action1.triggered.connect(self.activate) - menu.addAction(action1) - action2 = QtGui.QAction( - QtGui.QIcon(":/icons/Draft_SelectGroup.svg"), - translate("draft", "Select contents"), - menu - ) - action2.triggered.connect(self.selectcontents) - menu.addAction(action2) - - def activate(self): - - if hasattr(self, "Object"): - FreeCADGui.Selection.clearSelection() - FreeCADGui.Selection.addSelection(self.Object) - FreeCADGui.runCommand("Draft_AutoGroup") - - def selectcontents(self): - - if hasattr(self, "Object"): - FreeCADGui.Selection.clearSelection() - for o in self.Object.Group: - FreeCADGui.Selection.addSelection(o) - - -class LayerContainer: - """The Layer Container""" - - def __init__(self, obj): - - self.Type = "LayerContainer" - obj.Proxy = self - - def execute(self, obj): - - g = obj.Group - g.sort(key=lambda o: o.Label) - obj.Group = g - - def __getstate__(self): - - if hasattr(self, "Type"): - return self.Type - - def __setstate__(self, state): - - if state: - self.Type = state - - -class ViewProviderLayerContainer: - """A View Provider for the Layer Container""" - - def __init__(self, vobj): - - vobj.Proxy = self - - def getIcon(self): - - import Draft_rc - return ":/icons/Draft_Layer.svg" - - def attach(self, vobj): - - self.Object = vobj.Object - - def setupContextMenu(self, vobj, menu): - - import Draft_rc - from PySide import QtCore, QtGui - action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Layer.svg"), "Merge duplicates", menu) - action1.triggered.connect(self.mergeByName) - menu.addAction(action1) - - def mergeByName(self): - - if hasattr(self, "Object") and hasattr(self.Object, "Group"): - layers = [o for o in self.Object.Group if (hasattr(o, "Proxy") and isinstance(o.Proxy, Layer))] - todelete = [] - for layer in layers: - if layer.Label[-1].isdigit() and layer.Label[-2].isdigit() and layer.Label[-3].isdigit(): - orig = None - for ol in layer: - if ol.Label == layer.Label[:-3].strip(): - orig = ol - break - if orig: - for par in layer.InList: - for prop in par.PropertiesList: - if getattr(par, prop) == layer: - FreeCAD.Console.PrintMessage( - "Changed property '" + prop + "' of object " + par.Label + " from " + layer.Label + " to " + orig.Label + "\n") - setattr(par, prop, orig) - todelete.append(layer) - for tod in todelete: - if not tod.InList: - FreeCAD.Console.PrintMessage("Merging duplicate layer " + tod.Label + "\n") - FreeCAD.ActiveDocument.removeObject(tod.Name) - elif (len(tod.InList) == 1) and (tod.InList[0].isDerivedFrom("App::DocumentObjectGroup")): - FreeCAD.Console.PrintMessage("Merging duplicate layer " + tod.Label + "\n") - FreeCAD.ActiveDocument.removeObject(tod.Name) - else: - FreeCAD.Console.PrintMessage("Unable to delete layer " + tod.Label + ": InList not empty\n") - - def __getstate__(self): - - return None - - def __setstate__(self, state): - - return None +# \brief Provides the Layer object. This module is deprecated. +# +# This module is only required to migrate old objects created +# with v0.18 and earlier and with certain development version of v0.19. +# It will be removed definitely in January 2021. +import FreeCAD as App + +from draftobjects.layer import (Layer, + _VisGroup, + LayerContainer) + +if App.GuiUp: + from draftviewproviders.view_layer import (ViewProviderLayer, + _ViewProviderVisGroup, + ViewProviderLayerContainer) diff --git a/src/Mod/Draft/draftguitools/gui_layers.py b/src/Mod/Draft/draftguitools/gui_layers.py index 1b59efe4e78e..aff58f1d0b2d 100644 --- a/src/Mod/Draft/draftguitools/gui_layers.py +++ b/src/Mod/Draft/draftguitools/gui_layers.py @@ -21,10 +21,10 @@ # * USA * # * * # *************************************************************************** -"""Provides tools for creating Layers with the Draft Workbench.""" +"""Provides GUI tools to create Layer objects.""" ## @package gui_layers # \ingroup draftguitools -# \brief Provides tools for creating Layers with the Draft Workbench. +# \brief Provides GUI tools to create Layer objects. ## \addtogroup draftguitools # @{ @@ -66,7 +66,7 @@ def Activated(self): self.doc.openTransaction("Create Layer") Gui.addModule("Draft") - Gui.doCommand('_layer_ = Draft.makeLayer()') + Gui.doCommand('_layer_ = Draft.make_layer()') Gui.doCommand('FreeCAD.ActiveDocument.recompute()') self.doc.commitTransaction() diff --git a/src/Mod/Draft/draftmake/make_layer.py b/src/Mod/Draft/draftmake/make_layer.py new file mode 100644 index 000000000000..645935aa51fb --- /dev/null +++ b/src/Mod/Draft/draftmake/make_layer.py @@ -0,0 +1,256 @@ +# *************************************************************************** +# * Copyright (c) 2014 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides functions to create Layer objects.""" +## @package make_layer +# \ingroup draftmake +# \brief Provides functions to create Layer objects. + +## \addtogroup draftmake +# @{ +import FreeCAD as App +import draftutils.utils as utils + +from draftutils.messages import _msg, _err +from draftutils.translate import _tr, translate +from draftobjects.layer import (Layer, + LayerContainer) + +if App.GuiUp: + from draftviewproviders.view_layer import (ViewProviderLayer, + ViewProviderLayerContainer) + +view_group = App.ParamGet("User parameter:BaseApp/Preferences/View") + + +def get_layer_container(): + """Return a group object to put layers in. + + Returns + ------- + App::DocumentObjectGroupPython + The existing group object named `'LayerContainer'` + of type `LayerContainer`. + If it doesn't exist it will create it with this default Name. + """ + found, doc = utils.find_doc(App.activeDocument()) + if not found: + _err(_tr("No active document. Aborting.")) + return None + + for obj in doc.Objects: + if obj.Name == "LayerContainer": + return obj + + new_obj = doc.addObject("App::DocumentObjectGroupPython", + "LayerContainer") + new_obj.Label = translate("draft", "Layers") + + LayerContainer(new_obj) + + if App.GuiUp: + ViewProviderLayerContainer(new_obj.ViewObject) + + return new_obj + + +def getLayerContainer(): + """Get the Layer container. DEPRECATED. Use 'get_layer_container'.""" + utils.use_instead("get_layer_container") + + return get_layer_container() + + +def make_layer(name=None, + line_color=None, shape_color=None, + line_width=2.0, + draw_style="Solid", transparency=0): + """Create a Layer object in the active document. + + If a layer container named `'LayerContainer'` does not exist, + it is created with this name. + + A layer controls the view properties of the objects inside the layer, + so all parameters except for `name` only apply if the graphical interface + is up. + + Parameters + ---------- + name: str, optional + It is used to set the layer's `Label` (user editable). + It defaults to `None`, in which case the `Label` + is set to `'Layer'` or to its translation in the current language. + + line_color: tuple, optional + It defaults to `None`, in which case it uses the value of the parameter + `User parameter:BaseApp/Preferences/View/DefaultShapeLineColor`. + If it is given, it should be a tuple of three + floating point values from 0.0 to 1.0. + + shape_color: tuple, optional + It defaults to `None`, in which case it uses the value of the parameter + `User parameter:BaseApp/Preferences/View/DefaultShapeColor`. + If it is given, it should be a tuple of three + floating point values from 0.0 to 1.0. + + line_width: float, optional + It defaults to 2.0. + It determines the width of the edges of the objects contained + in the layer. + + draw_style: str, optional + It defaults to `'Solid'`. + It determines the style of the edges of the objects contained + in the layer. + If it is given, it should be 'Solid', 'Dashed', 'Dotted', + or 'Dashdot'. + + transparency: int, optional + It defaults to 0. + It should be an integer value from 0 (completely opaque) + to 100 (completely transparent). + + Return + ------ + App::FeaturePython + A scripted object of type `'Layer'`. + This object does not have a `Shape` attribute. + Modifying the view properties of this object will affect the objects + inside of it. + + None + If there is a problem it will return `None`. + """ + _name = "make_layer" + utils.print_header(_name, _tr("Layer")) + + found, doc = utils.find_doc(App.activeDocument()) + if not found: + _err(_tr("No active document. Aborting.")) + return None + + if name: + _msg("name: {}".format(name)) + try: + utils.type_check([(name, str)], name=_name) + except TypeError: + _err(_tr("Wrong input: it must be a string.")) + return None + else: + name = translate("draft", "Layer") + + _info_color = ("Wrong input: " + "must be a tuple of three floats 0.0 to 1.0.") + if line_color: + _msg("line_color: {}".format(line_color)) + try: + utils.type_check([(line_color, tuple)], name=_name) + except TypeError: + _err(_tr(_info_color)) + return None + + if not all(isinstance(color, (int, float)) for color in line_color): + _err(_tr(_info_color)) + return None + else: + c = view_group.GetUnsigned("DefaultShapeLineColor", 255) + line_color = (((c >> 24) & 0xFF) / 255, + ((c >> 16) & 0xFF) / 255, + ((c >> 8) & 0xFF) / 255) + + if shape_color: + _msg("shape_color: {}".format(shape_color)) + try: + utils.type_check([(shape_color, tuple)], name=_name) + except TypeError: + _err(_tr(_info_color)) + return None + + if not all(isinstance(color, (int, float)) for color in shape_color): + _err(_tr(_info_color)) + return None + else: + c = view_group.GetUnsigned("DefaultShapeColor", 4294967295) + shape_color = (((c >> 24) & 0xFF) / 255, + ((c >> 16) & 0xFF) / 255, + ((c >> 8) & 0xFF) / 255) + + _msg("line_width: {}".format(line_width)) + try: + utils.type_check([(line_width, (int, float))], name=_name) + line_width = float(abs(line_width)) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + _info_style = ("Wrong input: " + "must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.") + _msg("draw_style: {}".format(draw_style)) + try: + utils.type_check([(draw_style, str)], name=_name) + except TypeError: + _err(_tr(_info_style)) + return None + + if draw_style not in ('Solid', 'Dashed', 'Dotted', 'Dashdot'): + _err(_tr(_info_style)) + return None + + _msg("transparency: {}".format(transparency)) + try: + utils.type_check([(transparency, (int, float))], name=_name) + transparency = int(abs(transparency)) + except TypeError: + _err(_tr("Wrong input: must be a number between 0 and 100.")) + return None + + new_obj = doc.addObject("App::FeaturePython", "Layer") + Layer(new_obj) + + new_obj.Label = name + + if App.GuiUp: + ViewProviderLayer(new_obj.ViewObject) + + new_obj.ViewObject.LineColor = line_color + new_obj.ViewObject.ShapeColor = shape_color + new_obj.ViewObject.LineWidth = line_width + new_obj.ViewObject.DrawStyle = draw_style + new_obj.ViewObject.Transparency = transparency + + container = get_layer_container() + container.addObject(new_obj) + + return new_obj + + +def makeLayer(name=None, linecolor=None, drawstyle=None, + shapecolor=None, transparency=None): + """Create a Layer. DEPRECATED. Use 'make_layer'.""" + utils.use_instead("make_layer") + + return make_layer(name, + linecolor, shapecolor, + draw_style=drawstyle, transparency=transparency) + +## @} diff --git a/src/Mod/Draft/draftobjects/layer.py b/src/Mod/Draft/draftobjects/layer.py new file mode 100644 index 000000000000..a6a75d8649a5 --- /dev/null +++ b/src/Mod/Draft/draftobjects/layer.py @@ -0,0 +1,120 @@ +# *************************************************************************** +# * Copyright (c) 2014 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the object code for the Layer object.""" +## @package layer +# \ingroup draftobjects +# \brief Provides the object code for the Layer object. + +## \addtogroup draftobjects +# @{ +from PySide.QtCore import QT_TRANSLATE_NOOP + + +class Layer: + """The Layer object. + + This class is normally used to extend a base `App::FeaturePython` object. + """ + + def __init__(self, obj): + self.Type = "Layer" + self.Object = obj + self.set_properties(obj) + + obj.Proxy = self + + def onDocumentRestored(self, obj): + """Execute code when the document is restored. + + Add properties that don't exist. + """ + self.set_properties(obj) + + def set_properties(self, obj): + """Set properties only if they don't exist.""" + if "Group" not in obj.PropertiesList: + _tip = QT_TRANSLATE_NOOP("App::Property", + "The objects that are part of this layer") + obj.addProperty("App::PropertyLinkList", + "Group", + "Layer", + _tip) + + def __getstate__(self): + """Return a tuple of objects to save or None.""" + return self.Type + + def __setstate__(self, state): + """Set the internal properties from the restored state.""" + if state: + self.Type = state + + def execute(self, obj): + """Execute when the object is created or recomputed. Do nothing.""" + pass + + 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 + + +# Alias for compatibility with v0.18 and earlier +_VisGroup = Layer + + +class LayerContainer: + """The container object for layers. + + This class is normally used to extend + a base `App::DocumentObjectGroupPython` object. + """ + + def __init__(self, obj): + self.Type = "LayerContainer" + obj.Proxy = self + + def execute(self, obj): + """Execute when the object is created or recomputed. + + 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 + + def __getstate__(self): + """Return a tuple of objects to save or None.""" + if hasattr(self, "Type"): + return self.Type + + def __setstate__(self, state): + """Set the internal properties from the restored state.""" + if state: + self.Type = state + +## @} diff --git a/src/Mod/Draft/draftviewproviders/view_layer.py b/src/Mod/Draft/draftviewproviders/view_layer.py new file mode 100644 index 000000000000..740cb77a83fc --- /dev/null +++ b/src/Mod/Draft/draftviewproviders/view_layer.py @@ -0,0 +1,457 @@ +# *************************************************************************** +# * Copyright (c) 2014 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the viewprovider code for the Layer object.""" +## @package view_layer +# \ingroup draftviewproviders +# \brief Provides the viewprovider code for the Layer object. + +## \addtogroup draftviewproviders +# @{ +import pivy.coin as coin +import PySide.QtCore as QtCore +import PySide.QtGui as QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui + +from draftutils.messages import _msg +from draftutils.translate import translate +from draftobjects.layer import Layer + + +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) + vobj.OverrideLineColorChildren = True + + if "OverrideShapeColorChildren" 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", + "OverrideShapeColorChildren", + "Layer", + _tip) + vobj.OverrideShapeColorChildren = True + + def set_visual_properties(self, vobj, properties): + """Set visual properties only if they don't already exist.""" + view_group = App.ParamGet("User parameter:BaseApp/Preferences/View") + + 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) + + c = view_group.GetUnsigned("DefaultShapeLineColor", 255) + vobj.LineColor = (((c >> 24) & 0xFF) / 255, + ((c >> 16) & 0xFF) / 255, + ((c >> 8) & 0xFF) / 255) + + 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) + + c = view_group.GetUnsigned("DefaultShapeColor", 4294967295) + vobj.ShapeColor = (((c >> 24) & 0xFF) / 255, + ((c >> 16) & 0xFF) / 255, + ((c >> 8) & 0xFF) / 255) + + 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) + + width = view_group.GetInt("DefaultShapeLineWidth", 2) + vobj.LineWidth = width + + 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) + vobj.DrawStyle = ["Solid", "Dashed", "Dotted", "Dashdot"] + vobj.DrawStyle = "Solid" + + if "Transparency" not in properties: + _tip = QT_TRANSLATE_NOOP("App::Property", + "The transparency of the objects " + "contained within this layer") + vobj.addProperty("App::PropertyInteger", + "Transparency", + "Layer", + _tip) + vobj.Transparency = 0 + + 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"): + 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 __getstate__(self): + """Return a tuple of objects to save or None.""" + return None + + def __setstate__(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 == "Group": + for _prop in ("LineColor", "ShapeColor", "LineWidth", + "DrawStyle", "Transparency", "Visibility"): + self.onChanged(obj.ViewObject, _prop) + + def change_view_properties(self, vobj, prop): + """Iterate over the contents and change the properties.""" + obj = vobj.Object + + # Return if the property does not exist + if not hasattr(vobj, prop): + return + + for target_obj in obj.Group: + target_vobj = target_obj.ViewObject + + # If the override properties are not set return without change + if prop == "LineColor" and not vobj.OverrideLineColorChildren: + return + elif prop == "ShapeColor" and not vobj.OverrideShapeColorChildren: + return + + # 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)) + else: + continue + + # Use the line color for the text color if it exists + if prop == "LineColor": + if hasattr(target_vobj, "TextColor"): + target_vobj.TextColor = vobj.LineColor + if hasattr(target_vobj, "FontColor"): + target_vobj.FontColor = vobj.LineColor + + def onChanged(self, vobj, prop): + """Execute when a view property is changed.""" + if prop in ("LineColor", "ShapeColor", "LineWidth", + "DrawStyle", "Transparency", "Visibility"): + self.change_view_properties(vobj, prop) + + if (prop in ("LineColor", "ShapeColor") + and hasattr(vobj, "LineColor") + and hasattr(vobj, "ShapeColor")): + # This doesn't do anything to the objects inside the layer, + # it just uses the defined Line and Shape colors + # to paint the layer icon accordingly in the tree view + l_color = vobj.LineColor + s_color = vobj.ShapeColor + + 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.""" + return True + + def canDragObjects(self): + """Return True to allow dragging many objects from the Layer.""" + return True + + def dragObject(self, vobj, otherobj): + """Remove the object that was dragged from the layer.""" + if hasattr(vobj.Object, "Group") and otherobj in vobj.Object.Group: + group = vobj.Object.Group + group.remove(otherobj) + vobj.Object.Group = group + 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. + """ + if hasattr(obj, "Proxy") and isinstance(obj.Proxy, Layer): + return False + return True + + def canDropObjects(self): + """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. + + If the object being dropped is itself a `'Layer'`, + return immediately to prevent dropping a layer inside a layer, + at least for now. + """ + if hasattr(otherobj, "Proxy") and isinstance(otherobj.Proxy, Layer): + return + + obj = vobj.Object + + if hasattr(obj, "Group") and otherobj not in obj.Group: + group = obj.Group + group.append(otherobj) + obj.Group = group + + # Remove from all other layers (not automatic) + for parent in otherobj.InList: + if (hasattr(parent, "Proxy") + and isinstance(parent.Proxy, Layer) + and otherobj in parent.Group + and parent != obj): + p_group = parent.Group + p_group.remove(otherobj) + parent.Group = p_group + + App.ActiveDocument.recompute() + + def setupContextMenu(self, vobj, menu): + """Set up actions to perform in the context menu.""" + action1 = QtGui.QAction(QtGui.QIcon(":/icons/button_right.svg"), + translate("draft", "Activate this layer"), + menu) + action1.triggered.connect(self.activate) + menu.addAction(action1) + + action2 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_SelectGroup.svg"), + translate("draft", "Select layer contents"), + menu) + action2.triggered.connect(self.select_contents) + menu.addAction(action2) + + def activate(self): + """Activate the selected layer, it becomes the Autogroup.""" + if hasattr(self, "Object"): + Gui.Selection.clearSelection() + Gui.Selection.addSelection(self.Object) + Gui.runCommand("Draft_AutoGroup") + + def select_contents(self): + """Select the contents of the layer.""" + if hasattr(self, "Object"): + Gui.Selection.clearSelection() + for layer_obj in self.Object.Group: + Gui.Selection.addSelection(layer_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_Layer.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.""" + action1 = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Layer.svg"), + translate("Draft", "Merge layer duplicates"), + menu) + action1.triggered.connect(self.merge_by_name) + menu.addAction(action1) + + def merge_by_name(self): + """Merge the layers that have the same name.""" + if not hasattr(self, "Object") or not hasattr(self.Object, "Group"): + return + + obj = self.Object + + layers = list() + for iobj in obj.Group: + if hasattr(iobj, "Proxy") and isinstance(iobj.Proxy, Layer): + layers.append(iobj) + + to_delete = list() + for layer in layers: + # Test the last three characters of the layer's Label to see + # if it's a number, like `'Layer017'` + if (layer.Label[-1].isdigit() + and layer.Label[-2].isdigit() + and layer.Label[-3].isdigit()): + # If the object inside the layer has the same Label + # as the layer, save this object + orig = None + for ol in layer.OutList: + if ol.Label == layer.Label[:-3].strip(): + orig = ol + break + + # Go into the objects that reference this layer object + # and set the layer property with the previous `orig` + # object found + # Editor: when is this possible? Maybe if a layer is inside + # another layer? Currently the code doesn't allow this + # so maybe this was a previous behavior that was disabled + # in `ViewProviderLayer`. + if orig: + for par in layer.InList: + for prop in par.PropertiesList: + if getattr(par, prop) == layer: + _msg("Changed property '" + prop + + "' of object " + par.Label + + " from " + layer.Label + + " to " + orig.Label) + setattr(par, prop, orig) + to_delete.append(layer) + + for layer in to_delete: + if not layer.InList: + _msg("Merging duplicate layer: " + layer.Label) + App.ActiveDocument.removeObject(layer.Name) + elif len(layer.InList) == 1: + first = layer.InList[0] + + if first.isDerivedFrom("App::DocumentObjectGroup"): + _msg("Merging duplicate layer: " + layer.Label) + App.ActiveDocument.removeObject(layer.Name) + else: + _msg("InList not empty. " + "Unable to delete layer: " + layer.Label) + + def __getstate__(self): + """Return a tuple of objects to save or None.""" + return None + + def __setstate__(self, state): + """Set the internal properties from the restored state.""" + return None + + +# Alias for compatibility with v0.18 and earlier +_ViewProviderVisGroup = ViewProviderLayer + +## @}