From 7f83d8f0b034a783594817dcbb54ea148ebb52e2 Mon Sep 17 00:00:00 2001 From: alafr Date: Sat, 14 Mar 2020 18:32:19 +0100 Subject: [PATCH] Arch Structure : use Edges as a Tool, add options * New widget for Tool selection: this enables the use of some Edges and not only entire Shapes - it makes possible to build Structures from a master Sketch. * Add option BasePerpendicularToTool : option to create multiple Structures with a single Base (profile), along different Tools (paths). A copy of the profile is created and aligned perpendicular to the path at it start point. This can already be done with multiple Draft Clones attached using the "NormalToEdge" mode, but this new option will make it more straightforward and with less objects in the document. * Group properties related to the Tool in a group "Extrusion Path" * Add a readonly property ComputedLength (it will be needed in BIM schedules) --- src/Mod/Arch/ArchStructure.py | 121 +++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 18 deletions(-) diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index fe8adf90a3..81de3c8b52 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -611,7 +611,23 @@ class _Structure(ArchComponent.Component): pl = obj.PropertiesList if not "Tool" in pl: - obj.addProperty("App::PropertyLink","Tool","Structure",QT_TRANSLATE_NOOP("App::Property","An optional extrusion path for this element")) + obj.addProperty("App::PropertyLinkSubList", "Tool", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "An optional extrusion path for this element")) + if not "ComputedLength" in pl: + obj.addProperty("App::PropertyDistance", "ComputedLength", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "The computed length of the extrusion path"), 1) + if not "ToolOffsetFirst" in pl: + obj.addProperty("App::PropertyDistance", "ToolOffsetFirst", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Start offset distance along the extrusion path (positive: extend, negative: trim")) + if not "ToolOffsetLast" in pl: + obj.addProperty("App::PropertyDistance", "ToolOffsetLast", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "End offset distance along the extrusion path (positive: extend, negative: trim")) + if not "BasePerpendicularToTool" in pl: + obj.addProperty("App::PropertyBool", "BasePerpendicularToTool", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Automatically align the Base of the Structure perpendicular to the Tool axis")) + if not "BaseOffsetX" in pl: + obj.addProperty("App::PropertyDistance", "BaseOffsetX", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "X offset between the Base origin and the Tool axis (only used if BasePerpendicularToTool is True)")) + if not "BaseOffsetY" in pl: + obj.addProperty("App::PropertyDistance", "BaseOffsetY", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Y offset between the Base origin and the Tool axis (only used if BasePerpendicularToTool is True)")) + if not "BaseMirror" in pl: + obj.addProperty("App::PropertyBool", "BaseMirror", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Mirror the Base along its Y axis (only used if BasePerpendicularToTool is True)")) + if not "BaseRotation" in pl: + obj.addProperty("App::PropertyAngle", "BaseRotation", "ExtrusionPath", QT_TRANSLATE_NOOP("App::Property", "Base rotation around the Tool axis (only used if BasePerpendicularToTool is True)")) if not "Length" in pl: obj.addProperty("App::PropertyLength","Length","Structure",QT_TRANSLATE_NOOP("App::Property","The length of this element, if not based on a profile")) if not "Width" in pl: @@ -660,6 +676,7 @@ class _Structure(ArchComponent.Component): if not isinstance(pla,list): pla = [pla] base = [] + extrusion_length = 0.0 for i in range(len(sh)): shi = sh[i] if i < len(ev): @@ -685,10 +702,12 @@ class _Structure(ArchComponent.Component): FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n") return base.append(shi) + extrusion_length += evi.Length if len(base) == 1: base = base[0] else: base = Part.makeCompound(base) + obj.ComputedLength = FreeCAD.Units.Quantity(extrusion_length, FreeCAD.Units.Length) if obj.Base: if hasattr(obj.Base,'Shape'): if obj.Base.Shape.isNull(): @@ -785,13 +804,30 @@ class _Structure(ArchComponent.Component): import Part baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1])) if baseface: - if obj.Tool: - if obj.Tool.Shape: - edges = obj.Tool.Shape.Edges - if len(edges) == 1 and DraftGeomUtils.geomType(edges[0]) == "Line": - extrusion = DraftGeomUtils.vec(edges[0]) + if hasattr(obj, "Tool") and obj.Tool: + tool = obj.Tool + edges = DraftGeomUtils.get_referenced_edges(tool) + if len(edges) > 0: + extrusion = Part.Wire(Part.__sortEdges__(edges)) + if hasattr(obj, "ToolOffsetFirst"): + offset_start = float(obj.ToolOffsetFirst.getValueAs("mm")) else: - extrusion = obj.Tool.Shape.copy() + offset_start = 0.0 + if hasattr(obj, "ToolOffsetLast"): + offset_end = float(obj.ToolOffsetLast.getValueAs("mm")) + else: + offset_end = 0.0 + if offset_start != 0.0 or offset_end != 0.0: + extrusion = DraftGeomUtils.get_extended_wire(extrusion, offset_start, offset_end) + if hasattr(obj, "BasePerpendicularToTool") and obj.BasePerpendicularToTool: + pl = FreeCAD.Placement() + if hasattr(obj, "BaseRotation"): + pl.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), -obj.BaseRotation) + if hasattr(obj, "BaseOffsetX") and hasattr(obj, "BaseOffsetY"): + pl.translate(FreeCAD.Vector(obj.BaseOffsetX, obj.BaseOffsetY, 0)) + if hasattr(obj, "BaseMirror"): + pl.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 1, 0), 180) + baseface.Placement = DraftGeomUtils.get_placement_perpendicular_to_wire(extrusion).multiply(pl) else: if obj.Normal.Length: normal = Vector(obj.Normal).normalize() @@ -813,6 +849,8 @@ class _Structure(ArchComponent.Component): base, placement = self.rebase(baseface) inverse_placement = placement.inverse() if extrusion: + if len(extrusion.Edges) == 1 and DraftGeomUtils.geomType(extrusion.Edges[0]) == "Line": + extrusion = DraftGeomUtils.vec(extrusion.Edges[0]) if isinstance(extrusion, FreeCAD.Vector): extrusion = inverse_placement.Rotation.multVec(extrusion) elif normal: @@ -848,8 +886,8 @@ class _Structure(ArchComponent.Component): ev = extdata[2].Rotation.multVec(extdata[1]) nodes.Placement = nodes.Placement.multiply(extdata[2]) if IfcType not in ["Slab"]: - if obj.Tool: - nodes = obj.Tool.Shape + if not isinstance(extdata[1], FreeCAD.Vector): + nodes = extdata[1] elif extdata[1].Length > 0: if hasattr(nodes,"CenterOfMass"): import Part @@ -1044,45 +1082,56 @@ class StructureTaskPanel(ArchComponent.ComponentTaskPanel): def __init__(self,obj): ArchComponent.ComponentTaskPanel.__init__(self) - self.optwid = QtGui.QWidget() - self.optwid.setWindowTitle(QtGui.QApplication.translate("Arch", "Node Tools", None)) - lay = QtGui.QVBoxLayout(self.optwid) + self.nodes_widget = QtGui.QWidget() + self.nodes_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Node Tools", None)) + lay = QtGui.QVBoxLayout(self.nodes_widget) - self.resetButton = QtGui.QPushButton(self.optwid) + self.resetButton = QtGui.QPushButton(self.nodes_widget) self.resetButton.setIcon(QtGui.QIcon(":/icons/edit-undo.svg")) self.resetButton.setText(QtGui.QApplication.translate("Arch", "Reset nodes", None)) lay.addWidget(self.resetButton) QtCore.QObject.connect(self.resetButton, QtCore.SIGNAL("clicked()"), self.resetNodes) - self.editButton = QtGui.QPushButton(self.optwid) + self.editButton = QtGui.QPushButton(self.nodes_widget) self.editButton.setIcon(QtGui.QIcon(":/icons/Draft_Edit.svg")) self.editButton.setText(QtGui.QApplication.translate("Arch", "Edit nodes", None)) lay.addWidget(self.editButton) QtCore.QObject.connect(self.editButton, QtCore.SIGNAL("clicked()"), self.editNodes) - self.extendButton = QtGui.QPushButton(self.optwid) + self.extendButton = QtGui.QPushButton(self.nodes_widget) self.extendButton.setIcon(QtGui.QIcon(":/icons/Snap_Perpendicular.svg")) self.extendButton.setText(QtGui.QApplication.translate("Arch", "Extend nodes", None)) self.extendButton.setToolTip(QtGui.QApplication.translate("Arch", "Extends the nodes of this element to reach the nodes of another element", None)) lay.addWidget(self.extendButton) QtCore.QObject.connect(self.extendButton, QtCore.SIGNAL("clicked()"), self.extendNodes) - self.connectButton = QtGui.QPushButton(self.optwid) + self.connectButton = QtGui.QPushButton(self.nodes_widget) self.connectButton.setIcon(QtGui.QIcon(":/icons/Snap_Intersection.svg")) self.connectButton.setText(QtGui.QApplication.translate("Arch", "Connect nodes", None)) self.connectButton.setToolTip(QtGui.QApplication.translate("Arch", "Connects nodes of this element with the nodes of another element", None)) lay.addWidget(self.connectButton) QtCore.QObject.connect(self.connectButton, QtCore.SIGNAL("clicked()"), self.connectNodes) - self.toggleButton = QtGui.QPushButton(self.optwid) + self.toggleButton = QtGui.QPushButton(self.nodes_widget) self.toggleButton.setIcon(QtGui.QIcon(":/icons/dagViewVisible.svg")) self.toggleButton.setText(QtGui.QApplication.translate("Arch", "Toggle all nodes", None)) self.toggleButton.setToolTip(QtGui.QApplication.translate("Arch", "Toggles all structural nodes of the document on/off", None)) lay.addWidget(self.toggleButton) QtCore.QObject.connect(self.toggleButton, QtCore.SIGNAL("clicked()"), self.toggleNodes) - self.form = [self.form,self.optwid] + self.extrusion_widget = QtGui.QWidget() + self.extrusion_widget.setWindowTitle(QtGui.QApplication.translate("Arch", "Extrusion Tools", None)) + lay = QtGui.QVBoxLayout(self.extrusion_widget) + + self.selectToolButton = QtGui.QPushButton(self.extrusion_widget) + self.selectToolButton.setIcon(QtGui.QIcon()) + self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Select tool...", None)) + self.selectToolButton.setToolTip(QtGui.QApplication.translate("Arch", "Select object or edges to be used as a Tool (extrusion path)", None)) + lay.addWidget(self.selectToolButton) + QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool) + + self.form = [self.form, self.nodes_widget, self.extrusion_widget] self.Object = obj self.observer = None self.nodevis = None @@ -1177,6 +1226,42 @@ class StructureTaskPanel(ArchComponent.ComponentTaskPanel): self.nodevis.append([obj,obj.ViewObject.ShowNodes]) obj.ViewObject.ShowNodes = True + def setSelectionFromTool(self): + FreeCADGui.Selection.clearSelection() + if hasattr(self.Object, "Tool"): + tool = self.Object.Tool + if hasattr(tool, "Shape") and tool.Shape: + FreeCADGui.Selection.addSelection(tool) + else: + if not isinstance(tool, list): + tool = [tool] + for o, subs in tool: + FreeCADGui.Selection.addSelection(o, subs) + QtCore.QObject.disconnect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool) + QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setToolFromSelection) + self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Done", None)) + + def setToolFromSelection(self): + objectList = [] + selEx = FreeCADGui.Selection.getSelectionEx() + for selExi in selEx: + if len(selExi.SubElementNames) == 0: + # Add entirely selected objects + objectList.append(selExi.Object) + else: + subElementsNames = [subElementName for subElementName in selExi.SubElementNames if subElementName.startswith("Edge")] + # Check that at least an edge is selected from the object's shape + if len(subElementsNames) > 0: + objectList.append((selExi.Object, subElementsNames)) + if self.Object.getTypeIdOfProperty("Tool") != "App::PropertyLinkSubList": + # Upgrade property Tool from App::PropertyLink to App::PropertyLinkSubList (note: Undo/Redo fails) + self.Object.removeProperty("Tool") + self.Object.addProperty("App::PropertyLinkSubList", "Tool", "Structure", QT_TRANSLATE_NOOP("App::Property", "An optional extrusion path for this element")) + self.Object.Tool = objectList + QtCore.QObject.disconnect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setToolFromSelection) + QtCore.QObject.connect(self.selectToolButton, QtCore.SIGNAL("clicked()"), self.setSelectionFromTool) + self.selectToolButton.setText(QtGui.QApplication.translate("Arch", "Select tool...", None)) + def accept(self): if self.observer: