Merge pull request #3229 from alafr/alafr-arch-structure
[0.20] [Arch] Structure: use of specific edges for Tool, add option BasePerpendicularToTool
This commit is contained in:
@@ -185,6 +185,77 @@ def placeAlongEdge(p1,p2,horizontal=False):
|
||||
return pl
|
||||
|
||||
|
||||
class CommandStructuresFromSelection:
|
||||
""" The Arch Structures from selection command definition. """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Arch_MultipleStructures',
|
||||
'MenuText': QT_TRANSLATE_NOOP("Arch_Structure", "Multiple Structures"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Arch_Structure", "Create multiple Arch Structure objects from a selected base, using each selected edge as an extrusion path")}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
selex = FreeCADGui.Selection.getSelectionEx()
|
||||
if len(selex) >= 2:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Structures From Selection"))
|
||||
FreeCADGui.addModule("Arch")
|
||||
FreeCADGui.addModule("Draft")
|
||||
base = selex[0].Object # The first selected object is the base for the Structure objects
|
||||
for selexi in selex[1:]: # All the edges from the other objects are used as a Tool (extrusion paths)
|
||||
if len(selexi.SubElementNames) == 0:
|
||||
subelement_names = ["Edge" + str(i) for i in range(1, len(selexi.Object.Shape.Edges) + 1)]
|
||||
else:
|
||||
subelement_names = [sub for sub in selexi.SubElementNames if sub.startswith("Edge")]
|
||||
for sub in subelement_names:
|
||||
FreeCADGui.doCommand("structure = Arch.makeStructure(FreeCAD.ActiveDocument." + base.Name + ")")
|
||||
FreeCADGui.doCommand("structure.Tool = (FreeCAD.ActiveDocument." + selexi.Object.Name + ", '" + sub + "')")
|
||||
FreeCADGui.doCommand("structure.BasePerpendicularToTool = True")
|
||||
FreeCADGui.doCommand("Draft.autogroup(structure)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
else:
|
||||
FreeCAD.Console.PrintError(translate("Arch", "Please select the base object first and then the edges to use as extrusion paths") + "\n")
|
||||
|
||||
|
||||
class CommandStructuralSystem:
|
||||
""" The Arch Structural System command definition. """
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Arch_StructuralSystem',
|
||||
'MenuText': QT_TRANSLATE_NOOP("Arch_Structure", "Structural System"),
|
||||
'ToolTip': QT_TRANSLATE_NOOP("Arch_Structure", "Create a structural system object from a selected structure and axis")}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelection()
|
||||
if sel:
|
||||
st = Draft.getObjectsOfType(sel, "Structure")
|
||||
ax = Draft.getObjectsOfType(sel, "Axis")
|
||||
if ax:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch", "Create Structural System"))
|
||||
FreeCADGui.addModule("Arch")
|
||||
if st:
|
||||
FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(" + ArchCommands.getStringList(st) + ", " + ArchCommands.getStringList(ax) + ")")
|
||||
else:
|
||||
FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(axes = " + ArchCommands.getStringList(ax) + ")")
|
||||
FreeCADGui.addModule("Draft")
|
||||
FreeCADGui.doCommand("Draft.autogroup(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
else:
|
||||
FreeCAD.Console.PrintError(translate("Arch", "Please select at least an axis object") + "\n")
|
||||
|
||||
|
||||
class _CommandStructure:
|
||||
|
||||
"the Arch Structure command definition"
|
||||
@@ -224,16 +295,7 @@ class _CommandStructure:
|
||||
st = Draft.getObjectsOfType(sel,"Structure")
|
||||
ax = Draft.getObjectsOfType(sel,"Axis")
|
||||
if ax:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Structural System"))
|
||||
FreeCADGui.addModule("Arch")
|
||||
if st:
|
||||
FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(" + ArchCommands.getStringList(st) + "," + ArchCommands.getStringList(ax) + ")")
|
||||
else:
|
||||
FreeCADGui.doCommand("obj = Arch.makeStructuralSystem(axes=" + ArchCommands.getStringList(ax) + ")")
|
||||
FreeCADGui.addModule("Draft")
|
||||
FreeCADGui.doCommand("Draft.autogroup(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
FreeCADGui.runCommand("Arch_StructuralSystem")
|
||||
return
|
||||
elif not(ax) and not(st):
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Structure"))
|
||||
@@ -611,7 +673,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,31 +738,38 @@ 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):
|
||||
evi = ev[i]
|
||||
else:
|
||||
evi = FreeCAD.Vector(ev[-1])
|
||||
evi = ev[-1]
|
||||
if isinstance(evi, FreeCAD.Vector):
|
||||
evi = FreeCAD.Vector(evi)
|
||||
else:
|
||||
evi = evi.copy()
|
||||
if i < len(pla):
|
||||
pli = pla[i]
|
||||
else:
|
||||
pli = pla[-1].copy()
|
||||
shi.Placement = pli.multiply(shi.Placement)
|
||||
if not isinstance(evi, FreeCAD.Vector):
|
||||
if isinstance(evi, FreeCAD.Vector):
|
||||
extv = pla[0].Rotation.multVec(evi)
|
||||
shi = shi.extrude(extv)
|
||||
else:
|
||||
try:
|
||||
shi = evi.makePipe(shi)
|
||||
except Part.OCCError:
|
||||
FreeCAD.Console.PrintError(translate("Arch","Error: The base shape couldn't be extruded along this tool object")+"\n")
|
||||
return
|
||||
else:
|
||||
extv = pla[0].Rotation.multVec(evi)
|
||||
shi = shi.extrude(extv)
|
||||
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():
|
||||
@@ -712,9 +797,7 @@ class _Structure(ArchComponent.Component):
|
||||
self.applyShape(obj,base,pl)
|
||||
|
||||
def getExtrusionData(self,obj):
|
||||
|
||||
"""returns (shape,extrusion vector,placement) or None"""
|
||||
|
||||
"""returns (shape,extrusion vector or path,placement) or None"""
|
||||
if hasattr(obj,"IfcType"):
|
||||
IfcType = obj.IfcType
|
||||
else:
|
||||
@@ -728,11 +811,11 @@ class _Structure(ArchComponent.Component):
|
||||
length = obj.Length.Value
|
||||
width = obj.Width.Value
|
||||
height = obj.Height.Value
|
||||
normal = None
|
||||
if not height:
|
||||
height = self.getParentHeight(obj)
|
||||
base = None
|
||||
placement = None
|
||||
baseface = None
|
||||
extrusion = None
|
||||
normal = None
|
||||
if obj.Base:
|
||||
if hasattr(obj.Base,'Shape'):
|
||||
if obj.Base.Shape:
|
||||
@@ -742,22 +825,8 @@ class _Structure(ArchComponent.Component):
|
||||
if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces,tol=0.01):
|
||||
return None
|
||||
else:
|
||||
base,placement = self.rebase(obj.Base.Shape)
|
||||
normal = obj.Base.Shape.Faces[0].normalAt(0,0)
|
||||
normal = placement.inverse().Rotation.multVec(normal)
|
||||
if (len(obj.Shape.Solids) > 1) and (len(obj.Shape.Solids) == len(obj.Base.Shape.Faces)):
|
||||
# multiple extrusions
|
||||
b = []
|
||||
p = []
|
||||
hint = obj.Base.Shape.Faces[0].normalAt(0,0)
|
||||
for f in obj.Base.Shape.Faces:
|
||||
bf,pf = self.rebase(f,hint)
|
||||
b.append(bf)
|
||||
p.append(pf)
|
||||
base = b
|
||||
placement = p
|
||||
baseface = obj.Base.Shape.copy()
|
||||
elif obj.Base.Shape.Wires:
|
||||
baseface = None
|
||||
if hasattr(obj,"FaceMaker"):
|
||||
if obj.FaceMaker != "None":
|
||||
try:
|
||||
@@ -765,9 +834,6 @@ class _Structure(ArchComponent.Component):
|
||||
except Exception:
|
||||
FreeCAD.Console.PrintError(translate("Arch","Facemaker returned an error")+"\n")
|
||||
return None
|
||||
if len(baseface.Faces) > 1:
|
||||
baseface = baseface.Faces[0]
|
||||
normal = baseface.normalAt(0,0)
|
||||
if not baseface:
|
||||
for w in obj.Base.Shape.Wires:
|
||||
if not w.isClosed():
|
||||
@@ -781,15 +847,7 @@ class _Structure(ArchComponent.Component):
|
||||
if baseface:
|
||||
baseface = baseface.fuse(f)
|
||||
else:
|
||||
baseface = f
|
||||
normal = f.normalAt(0,0)
|
||||
base,placement = self.rebase(baseface)
|
||||
normal = placement.inverse().Rotation.multVec(normal)
|
||||
elif (len(obj.Base.Shape.Edges) == 1) and (len(obj.Base.Shape.Vertexes) == 1):
|
||||
# closed edge
|
||||
w = Part.Wire(obj.Base.Shape.Edges[0])
|
||||
baseface = Part.Face(w)
|
||||
base,placement = self.rebase(baseface)
|
||||
baseface = f.copy()
|
||||
elif length and width and height:
|
||||
if (length > height) and (IfcType != "Slab"):
|
||||
h2 = height/2 or 0.5
|
||||
@@ -807,22 +865,58 @@ class _Structure(ArchComponent.Component):
|
||||
v4 = Vector(-l2,w2,0)
|
||||
import Part
|
||||
baseface = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1]))
|
||||
base,placement = self.rebase(baseface)
|
||||
if base and placement:
|
||||
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 baseface:
|
||||
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()
|
||||
if isinstance(placement,list):
|
||||
normal = placement[0].inverse().Rotation.multVec(normal)
|
||||
else:
|
||||
normal = placement.inverse().Rotation.multVec(normal)
|
||||
else:
|
||||
normal = baseface.Faces[0].normalAt(0, 0)
|
||||
base = None
|
||||
placement = None
|
||||
inverse_placement = None
|
||||
if len(baseface.Faces) > 1:
|
||||
base = []
|
||||
placement = []
|
||||
hint = baseface.Faces[0].normalAt(0, 0)
|
||||
for f in baseface.Faces:
|
||||
bf, pf = self.rebase(f, hint)
|
||||
base.append(bf)
|
||||
placement.append(pf)
|
||||
inverse_placement = placement[0].inverse()
|
||||
else:
|
||||
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], True)
|
||||
if isinstance(extrusion, FreeCAD.Vector):
|
||||
extrusion = inverse_placement.Rotation.multVec(extrusion)
|
||||
elif normal:
|
||||
normal = inverse_placement.Rotation.multVec(normal)
|
||||
if not normal:
|
||||
normal = Vector(0,0,1)
|
||||
if not normal.Length:
|
||||
@@ -834,7 +928,8 @@ class _Structure(ArchComponent.Component):
|
||||
else:
|
||||
if height:
|
||||
extrusion = normal.multiply(height)
|
||||
return (base,extrusion,placement)
|
||||
if extrusion:
|
||||
return (base, extrusion, placement)
|
||||
return None
|
||||
|
||||
def onChanged(self,obj,prop):
|
||||
@@ -850,15 +945,15 @@ class _Structure(ArchComponent.Component):
|
||||
extdata = self.getExtrusionData(obj)
|
||||
if extdata and not isinstance(extdata[0],list):
|
||||
nodes = extdata[0]
|
||||
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
|
||||
nodes = Part.LineSegment(nodes.CenterOfMass,nodes.CenterOfMass.add(ev)).toShape()
|
||||
nodes = Part.LineSegment(nodes.CenterOfMass,nodes.CenterOfMass.add(extdata[1])).toShape()
|
||||
if isinstance(extdata[1], FreeCAD.Vector):
|
||||
nodes.Placement = nodes.Placement.multiply(extdata[2])
|
||||
offset = FreeCAD.Vector()
|
||||
if hasattr(obj,"NodesOffset"):
|
||||
offset = FreeCAD.Vector(0,0,obj.NodesOffset.Value)
|
||||
@@ -1049,45 +1144,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
|
||||
@@ -1182,6 +1288,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:
|
||||
@@ -1315,4 +1457,19 @@ class _ViewProviderStructuralSystem(ArchComponent.ViewProviderComponent):
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Arch_Structure',_CommandStructure())
|
||||
FreeCADGui.addCommand("Arch_Structure", _CommandStructure())
|
||||
FreeCADGui.addCommand("Arch_StructuralSystem", CommandStructuralSystem())
|
||||
FreeCADGui.addCommand("Arch_StructuresFromSelection", CommandStructuresFromSelection())
|
||||
|
||||
class _ArchStructureGroupCommand:
|
||||
|
||||
def GetCommands(self):
|
||||
return ("Arch_Structure", "Arch_StructuralSystem", "Arch_StructuresFromSelection")
|
||||
def GetResources(self):
|
||||
return { "MenuText": QT_TRANSLATE_NOOP("Arch_Structure", "Structure tools"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Arch_Structure", "Structure tools")
|
||||
}
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
FreeCADGui.addCommand("Arch_StructureTools", _ArchStructureGroupCommand())
|
||||
|
||||
@@ -59,7 +59,7 @@ class ArchWorkbench(FreeCADGui.Workbench):
|
||||
import Arch
|
||||
|
||||
# Set up command lists
|
||||
self.archtools = ["Arch_Wall", "Arch_Structure", "Arch_Rebar",
|
||||
self.archtools = ["Arch_Wall", "Arch_StructureTools", "Arch_Rebar",
|
||||
"Arch_CurtainWall","Arch_BuildingPart",
|
||||
"Arch_Project", "Arch_Site", "Arch_Building",
|
||||
"Arch_Floor", "Arch_Reference",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<file>icons/Arch_Material_Multi.svg</file>
|
||||
<file>icons/Arch_MergeWalls.svg</file>
|
||||
<file>icons/Arch_MeshToShape.svg</file>
|
||||
<file>icons/Arch_MultipleStructures.svg</file>
|
||||
<file>icons/Arch_Nest.svg</file>
|
||||
<file>icons/Arch_Panel.svg</file>
|
||||
<file>icons/Arch_Panel_Clone.svg</file>
|
||||
|
||||
264
src/Mod/Arch/Resources/icons/Arch_MultipleStructures.svg
Normal file
264
src/Mod/Arch/Resources/icons/Arch_MultipleStructures.svg
Normal file
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="svg2985"
|
||||
height="64px"
|
||||
width="64px">
|
||||
<defs
|
||||
id="defs2987">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="Arrow1Lstart"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto">
|
||||
<path
|
||||
transform="scale(0.8) translate(12.5,0)"
|
||||
style="fill-rule:evenodd;stroke:#729fcf;stroke-width:1pt;stroke-opacity:1;fill:#729fcf;fill-opacity:1"
|
||||
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
|
||||
id="path4859" />
|
||||
</marker>
|
||||
<linearGradient
|
||||
id="linearGradient3850-6">
|
||||
<stop
|
||||
id="stop3852-2"
|
||||
offset="0"
|
||||
style="stop-color:#c4a000;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3854-9"
|
||||
offset="1"
|
||||
style="stop-color:#edd400;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3858-2">
|
||||
<stop
|
||||
id="stop3860-7"
|
||||
offset="0"
|
||||
style="stop-color:#ffc900;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3862-0"
|
||||
offset="1"
|
||||
style="stop-color:#fce94f;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="27.481174"
|
||||
x2="66.151985"
|
||||
y1="54.851124"
|
||||
x1="69.848015"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3972"
|
||||
xlink:href="#linearGradient3850-6" />
|
||||
<linearGradient
|
||||
y2="26.598274"
|
||||
x2="55.563385"
|
||||
y1="56.224525"
|
||||
x1="59.417618"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3974"
|
||||
xlink:href="#linearGradient3858-2" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(-1,0,0,1,111.00667,0.294905)"
|
||||
y2="21.705095"
|
||||
x2="66.006668"
|
||||
y1="53"
|
||||
x1="69"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3986"
|
||||
xlink:href="#linearGradient3850-6" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(-1,0,0,1,111.00667,0.294905)"
|
||||
y2="20.705095"
|
||||
x2="55.006672"
|
||||
y1="54"
|
||||
x1="57"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3988"
|
||||
xlink:href="#linearGradient3858-2" />
|
||||
<linearGradient
|
||||
y2="27.481174"
|
||||
x2="66.151985"
|
||||
y1="51.449608"
|
||||
x1="69.018059"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3986-6"
|
||||
xlink:href="#linearGradient3850-6-1" />
|
||||
<linearGradient
|
||||
id="linearGradient3850-6-1">
|
||||
<stop
|
||||
id="stop3852-2-8"
|
||||
offset="0"
|
||||
style="stop-color:#c4a000;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3854-9-7"
|
||||
offset="1"
|
||||
style="stop-color:#edd400;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="26.598274"
|
||||
x2="55.563385"
|
||||
y1="52.449608"
|
||||
x1="57.018063"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3988-9"
|
||||
xlink:href="#linearGradient3858-2-2" />
|
||||
<linearGradient
|
||||
id="linearGradient3858-2-2">
|
||||
<stop
|
||||
id="stop3860-7-0"
|
||||
offset="0"
|
||||
style="stop-color:#ffc900;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3862-0-2"
|
||||
offset="1"
|
||||
style="stop-color:#fce94f;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
y2="21.705095"
|
||||
x2="66.006668"
|
||||
y1="53"
|
||||
x1="69"
|
||||
gradientTransform="matrix(-1,0,0,1,111.00667,0.294905)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4819"
|
||||
xlink:href="#linearGradient3850-6" />
|
||||
<linearGradient
|
||||
y2="20.705095"
|
||||
x2="55.006672"
|
||||
y1="54"
|
||||
x1="57"
|
||||
gradientTransform="matrix(-1,0,0,1,111.00667,0.294905)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient4821"
|
||||
xlink:href="#linearGradient3858-2" />
|
||||
<linearGradient
|
||||
y2="27.481174"
|
||||
x2="66.151985"
|
||||
y1="51.449608"
|
||||
x1="69.018059"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient5160"
|
||||
xlink:href="#linearGradient3850-6-1" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Antoine Lafr</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Arch_Structure</dc:title>
|
||||
<dc:date>2020-04-11</dc:date>
|
||||
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Arch/Resources/icons/Arch_MultipleStructures.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1">
|
||||
<g
|
||||
transform="translate(2.010672,1.479651)"
|
||||
id="g4817">
|
||||
<path
|
||||
id="path3927-5-6"
|
||||
style="color:#000000;visibility:visible;fill:url(#linearGradient4819);fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal"
|
||||
d="m 50,24 0.0067,35.294905 -10,-4 V 24 c 0,-5 -4.017372,-11.479651 -7.017372,-14.479651 L 40.0067,2.294905 C 46.0067,7.294905 50,16 50,24 Z" />
|
||||
<path
|
||||
id="path3929-3-0"
|
||||
style="color:#000000;visibility:visible;fill:url(#linearGradient4821);fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke"
|
||||
d="m 59.0067,20.520349 c -0.01737,-5.479651 -4.017372,-16 -10.017372,-19 L 40.0067,2.294905 C 45.0067,6.294905 50,16 50,24 l 0.0067,35.294905 9,-4 z" />
|
||||
<path
|
||||
id="path3846-6"
|
||||
d="m 57.0067,21.294905 v 32.539551 l -5.00003,2.460449 3e-5,-33 z"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3848-2"
|
||||
d="m 48,24 -5.9933,-2 v 32 l 5.982628,2.520349 z"
|
||||
style="fill:none;stroke:#edd400;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,61.006671,2.294905)"
|
||||
id="g3978-3">
|
||||
<g
|
||||
transform="translate(-22,2)"
|
||||
id="g3866-9-7">
|
||||
<g
|
||||
transform="translate(2.006671,-0.294905)"
|
||||
id="g5158">
|
||||
<path
|
||||
id="path3925-7-3-5"
|
||||
style="color:#000000;visibility:visible;fill:#fce94f;fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
d="m 52,23 9,2 9,-2 -8,-2 z" />
|
||||
<path
|
||||
id="path3927-5-6-9"
|
||||
style="color:#000000;visibility:visible;fill:url(#linearGradient5160);fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
d="m 61,25 v 32 l 9,-4 V 23 Z" />
|
||||
<path
|
||||
id="path3929-3-0-2"
|
||||
style="color:#000000;visibility:visible;fill:url(#linearGradient3988-9);fill-opacity:1;fill-rule:evenodd;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
|
||||
d="m 52,23 9,2 v 32 l -9,-4 z" />
|
||||
<path
|
||||
id="path3846-6-2"
|
||||
d="M 54,26 V 51.539551 L 59,54 V 27 Z"
|
||||
style="fill:none;stroke:#fce94f;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3848-2-8"
|
||||
d="m 63,27 5,-1.550391 v 26.255486 l -4.982658,2.520349 z"
|
||||
style="fill:none;stroke:#edd400;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="translate(0,4)"
|
||||
id="g5168">
|
||||
<path
|
||||
style="fill:#00ffff;stroke:#0b1521;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 8;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 5,52 V 23"
|
||||
id="path3878-4" />
|
||||
<path
|
||||
style="fill:#00ffff;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4, 8;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 5,52 V 22"
|
||||
id="path3878" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(2)"
|
||||
id="g5172">
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:4, 8;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 34,56 V 25 c 0,-3 -5,-9 -8,-12"
|
||||
id="path3878-2-8" />
|
||||
<path
|
||||
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:4, 8;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 34,56 V 25 c 0,-3 -5,-9 -8,-12"
|
||||
id="path3878-2" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
@@ -62,6 +62,7 @@ SET(Draft_tests
|
||||
drafttests/test_dwg.py
|
||||
drafttests/test_oca.py
|
||||
drafttests/test_airfoildat.py
|
||||
drafttests/test_draftgeomutils.py
|
||||
drafttests/draft_test_objects.py
|
||||
drafttests/README.md
|
||||
)
|
||||
|
||||
@@ -86,7 +86,8 @@ from draftgeoutils.edges import (findEdge,
|
||||
is_line,
|
||||
invert,
|
||||
findMidpoint,
|
||||
getTangent)
|
||||
getTangent,
|
||||
get_referenced_edges)
|
||||
|
||||
from draftgeoutils.faces import (concatenate,
|
||||
getBoundary,
|
||||
@@ -129,7 +130,9 @@ from draftgeoutils.wires import (findWires,
|
||||
rebaseWire,
|
||||
removeInterVertices,
|
||||
cleanProjection,
|
||||
tessellateProjection)
|
||||
tessellateProjection,
|
||||
get_placement_perpendicular_to_wire,
|
||||
get_extended_wire)
|
||||
|
||||
# Needs wires functions
|
||||
from draftgeoutils.fillets import (fillet,
|
||||
|
||||
@@ -101,12 +101,15 @@ from drafttests.test_import import DraftImport as DraftTest01
|
||||
from drafttests.test_creation import DraftCreation as DraftTest02
|
||||
from drafttests.test_modification import DraftModification as DraftTest03
|
||||
|
||||
# Testing the utils module
|
||||
from drafttests.test_draftgeomutils import TestDraftGeomUtils as DraftTest04
|
||||
|
||||
# Handling of file formats tests
|
||||
from drafttests.test_svg import DraftSVG as DraftTest04
|
||||
from drafttests.test_dxf import DraftDXF as DraftTest05
|
||||
from drafttests.test_dwg import DraftDWG as DraftTest06
|
||||
# from drafttests.test_oca import DraftOCA as DraftTest07
|
||||
# from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest08
|
||||
from drafttests.test_svg import DraftSVG as DraftTest05
|
||||
from drafttests.test_dxf import DraftDXF as DraftTest06
|
||||
from drafttests.test_dwg import DraftDWG as DraftTest07
|
||||
# from drafttests.test_oca import DraftOCA as DraftTest08
|
||||
# from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest09
|
||||
|
||||
# Use the modules so that code checkers don't complain (flake8)
|
||||
True if DraftTest01 else False
|
||||
@@ -115,5 +118,6 @@ True if DraftTest03 else False
|
||||
True if DraftTest04 else False
|
||||
True if DraftTest05 else False
|
||||
True if DraftTest06 else False
|
||||
# True if DraftTest07 else False
|
||||
True if DraftTest07 else False
|
||||
# True if DraftTest08 else False
|
||||
# True if DraftTest09 else False
|
||||
|
||||
@@ -221,8 +221,31 @@ def getTangent(edge, from_point=None):
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_referenced_edges(property_value):
|
||||
"""Return the Edges referenced by the value of a App:PropertyLink, App::PropertyLinkList,
|
||||
App::PropertyLinkSub or App::PropertyLinkSubList property."""
|
||||
edges = []
|
||||
if not isinstance(property_value, list):
|
||||
property_value = [property_value]
|
||||
for element in property_value:
|
||||
if hasattr(element, "Shape") and element.Shape:
|
||||
edges += shape.Edges
|
||||
elif isinstance(element, tuple) and len(element) == 2:
|
||||
object, subelement_names = element
|
||||
if hasattr(object, "Shape") and object.Shape:
|
||||
if len(subelement_names) == 1 and subelement_names[0] == "":
|
||||
edges += object.Shape.Edges
|
||||
else:
|
||||
for subelement_name in subelement_names:
|
||||
if subelement_name.startswith("Edge"):
|
||||
edge_number = int(subelement_name.lstrip("Edge")) - 1
|
||||
if edge_number < len(object.Shape.Edges):
|
||||
edges.append(object.Shape.Edges[edge_number])
|
||||
return edges
|
||||
|
||||
# compatibility layer
|
||||
|
||||
isLine = is_line
|
||||
|
||||
## @}
|
||||
## @}
|
||||
@@ -59,11 +59,17 @@ def precision():
|
||||
return precisionInt # return PARAMGRP.GetInt("precision", 6)
|
||||
|
||||
|
||||
def vec(edge):
|
||||
"""Return a vector from an edge or a Part.LineSegment."""
|
||||
# if edge is not straight, you'll get strange results!
|
||||
def vec(edge, use_orientation = False):
|
||||
"""Return a vector from an edge or a Part.LineSegment.
|
||||
|
||||
If use_orientation is True, it takes into account the edges orientation.
|
||||
If edge is not straight, you'll get strange results!
|
||||
"""
|
||||
if isinstance(edge, Part.Shape):
|
||||
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
|
||||
if use_orientation and isinstance(edge, Part.Edge) and edge.Orientation == "Reversed":
|
||||
return edge.Vertexes[0].Point.sub(edge.Vertexes[-1].Point)
|
||||
else:
|
||||
return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point)
|
||||
elif isinstance(edge, Part.LineSegment):
|
||||
return edge.EndPoint.sub(edge.StartPoint)
|
||||
else:
|
||||
|
||||
@@ -32,6 +32,7 @@ import lazy_loader.lazy_loader as lz
|
||||
import FreeCAD as App
|
||||
import DraftVecUtils
|
||||
import WorkingPlane
|
||||
import FreeCAD as App
|
||||
|
||||
from draftgeoutils.general import geomType, vec, precision
|
||||
from draftgeoutils.geometry import get_normal
|
||||
@@ -435,4 +436,95 @@ def tessellateProjection(shape, seglen):
|
||||
|
||||
return Part.makeCompound(newedges)
|
||||
|
||||
## @}
|
||||
def get_placement_perpendicular_to_wire(wire):
|
||||
"""Return the placement whose base is the wire's first vertex and it's z axis aligned to the wire's tangent."""
|
||||
pl = App.Placement()
|
||||
if wire.Length > 0.0:
|
||||
pl.Base = wire.OrderedVertexes[0].Point
|
||||
first_edge = wire.OrderedEdges[0]
|
||||
if first_edge.Orientation == "Forward":
|
||||
zaxis = -first_edge.tangentAt(first_edge.FirstParameter)
|
||||
else:
|
||||
zaxis = first_edge.tangentAt(first_edge.LastParameter)
|
||||
pl.Rotation = App.Rotation(App.Vector(1, 0, 0), App.Vector(0, 0, 1), zaxis, "ZYX")
|
||||
else:
|
||||
App.Console.PrintError("debug: get_placement_perpendicular_to_wire called with a zero-length wire.\n")
|
||||
return pl
|
||||
|
||||
|
||||
def get_extended_wire(wire, offset_start, offset_end):
|
||||
"""Return a wire trimmed (negative offset) or extended (positive offset) at its first vertex, last vertex or both ends.
|
||||
|
||||
get_extended_wire(wire, -100.0, 0.0) -> returns a copy of the wire with its first 100 mm removed
|
||||
get_extended_wire(wire, 0.0, 100.0) -> returns a copy of the wire extended by 100 mm after it's last vertex
|
||||
"""
|
||||
if min(offset_start, offset_end, offset_start + offset_end) <= -wire.Length:
|
||||
App.Console.PrintError("debug: get_extended_wire error, wire's length insufficient for trimming.\n")
|
||||
return wire
|
||||
if offset_start < 0: # Trim the wire from the first vertex
|
||||
offset_start = -offset_start
|
||||
out_edges = []
|
||||
for edge in wire.OrderedEdges:
|
||||
if offset_start >= edge.Length: # Remove entire edge
|
||||
offset_start -= edge.Length
|
||||
elif round(offset_start, precision()) > 0: # Split edge, to remove the required length
|
||||
if edge.Orientation == "Forward":
|
||||
new_edge = edge.split(edge.getParameterByLength(offset_start)).OrderedEdges[1]
|
||||
else:
|
||||
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_start)).OrderedEdges[0]
|
||||
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
|
||||
new_edge.Orientation = edge.Orientation
|
||||
out_edges.append(new_edge)
|
||||
offset_start = 0
|
||||
else: # Keep the remaining entire edges
|
||||
out_edges.append(edge)
|
||||
wire = Part.Wire(out_edges)
|
||||
elif offset_start > 0: # Extend the first edge along its normal
|
||||
first_edge = wire.OrderedEdges[0]
|
||||
if first_edge.Orientation == "Forward":
|
||||
start, end = first_edge.FirstParameter, first_edge.LastParameter
|
||||
vec = first_edge.tangentAt(start).multiply(offset_start)
|
||||
else:
|
||||
start, end = first_edge.LastParameter, first_edge.FirstParameter
|
||||
vec = -first_edge.tangentAt(start).multiply(offset_start)
|
||||
if geomType(first_edge) == "Line": # Replace first edge with the extended new edge
|
||||
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(end)).toShape()
|
||||
wire = Part.Wire([new_edge] + wire.OrderedEdges[1:])
|
||||
else: # Add a straight edge before the first vertex
|
||||
new_edge = Part.LineSegment(first_edge.valueAt(start).sub(vec), first_edge.valueAt(start)).toShape()
|
||||
wire = Part.Wire([new_edge] + wire.OrderedEdges)
|
||||
if offset_end < 0: # Trim the wire from the last vertex
|
||||
offset_end = -offset_end
|
||||
out_edges = []
|
||||
for edge in reversed(wire.OrderedEdges):
|
||||
if offset_end >= edge.Length: # Remove entire edge
|
||||
offset_end -= edge.Length
|
||||
elif round(offset_end, precision()) > 0: # Split edge, to remove the required length
|
||||
if edge.Orientation == "Forward":
|
||||
new_edge = edge.split(edge.getParameterByLength(edge.Length - offset_end)).OrderedEdges[0]
|
||||
else:
|
||||
new_edge = edge.split(edge.getParameterByLength(offset_end)).OrderedEdges[1]
|
||||
new_edge.Placement = edge.Placement # Strangely, edge.split discards the placement and orientation
|
||||
new_edge.Orientation = edge.Orientation
|
||||
out_edges.insert(0, new_edge)
|
||||
offset_end = 0
|
||||
else: # Keep the remaining entire edges
|
||||
out_edges.insert(0, edge)
|
||||
wire = Part.Wire(out_edges)
|
||||
elif offset_end > 0: # Extend the last edge along its normal
|
||||
last_edge = wire.OrderedEdges[-1]
|
||||
if last_edge.Orientation == "Forward":
|
||||
start, end = last_edge.FirstParameter, last_edge.LastParameter
|
||||
vec = last_edge.tangentAt(end).multiply(offset_end)
|
||||
else:
|
||||
start, end = last_edge.LastParameter, last_edge.FirstParameter
|
||||
vec = -last_edge.tangentAt(end).multiply(offset_end)
|
||||
if geomType(last_edge) == "Line": # Replace last edge with the extended new edge
|
||||
new_edge = Part.LineSegment(last_edge.valueAt(start), last_edge.valueAt(end).add(vec)).toShape()
|
||||
wire = Part.Wire(wire.OrderedEdges[:-1] + [new_edge])
|
||||
else: # Add a straight edge after the last vertex
|
||||
new_edge = Part.LineSegment(last_edge.valueAt(end), last_edge.valueAt(end).add(vec)).toShape()
|
||||
wire = Part.Wire(wire.OrderedEdges + [new_edge])
|
||||
return wire
|
||||
|
||||
## @}
|
||||
142
src/Mod/Draft/drafttests/test_draftgeomutils.py
Normal file
142
src/Mod/Draft/drafttests/test_draftgeomutils.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 Antoine Lafr *
|
||||
# * *
|
||||
# * 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. *
|
||||
# * *
|
||||
# * FreeCAD 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 FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
"""Unit test for the DraftGeomUtils module."""
|
||||
|
||||
import unittest
|
||||
import FreeCAD
|
||||
import Part
|
||||
import DraftGeomUtils
|
||||
import drafttests.auxiliary as aux
|
||||
from draftutils.messages import _msg
|
||||
|
||||
class TestDraftGeomUtils(unittest.TestCase):
|
||||
"""Testing the functions in the file DraftGeomUtils.py"""
|
||||
|
||||
def setUp(self):
|
||||
"""Prepare the test. Nothing to do here, DraftGeomUtils doesn't need a document."""
|
||||
aux.draw_header()
|
||||
|
||||
def test_get_extended_wire(self):
|
||||
"""Test the DraftGeomUtils.get_extended_wire function."""
|
||||
operation = "DraftGeomUtils.get_extended_wire"
|
||||
_msg(" Test '{}'".format(operation))
|
||||
|
||||
# Build wires made with straight edges and various combination of Orientation: the wires 1-4 are all equivalent
|
||||
points = [FreeCAD.Vector(0.0, 0.0, 0.0),
|
||||
FreeCAD.Vector(1500.0, 2000.0, 0.0),
|
||||
FreeCAD.Vector(4500.0, 2000.0, 0.0),
|
||||
FreeCAD.Vector(4500.0, 2000.0, 2500.0)]
|
||||
|
||||
edges = []
|
||||
for start, end in zip(points[:-1], points[1:]):
|
||||
edge = Part.makeLine(start, end)
|
||||
edges.append(edge)
|
||||
wire1 = Part.Wire(edges)
|
||||
|
||||
edges = []
|
||||
for start, end in zip(points[:-1], points[1:]):
|
||||
edge = Part.makeLine(end, start)
|
||||
edge.Orientation = "Reversed"
|
||||
edges.append(edge)
|
||||
wire2 = Part.Wire(edges)
|
||||
|
||||
edges = []
|
||||
for start, end in zip(points[:-1], points[1:]):
|
||||
edge = Part.makeLine(start, end)
|
||||
edge.Orientation = "Reversed"
|
||||
edges.insert(0, edge)
|
||||
wire3 = Part.Wire(edges)
|
||||
wire3.Orientation = "Reversed"
|
||||
|
||||
edges = []
|
||||
for start, end in zip(points[:-1], points[1:]):
|
||||
edge = Part.makeLine(end, start)
|
||||
edges.insert(0, edge)
|
||||
wire4 = Part.Wire(edges)
|
||||
wire4.Orientation = "Reversed"
|
||||
|
||||
# Build wires made with arcs and various combination of Orientation: the wires 5-8 are all equivalent
|
||||
points = [FreeCAD.Vector(0.0, 0.0, 0.0),
|
||||
FreeCAD.Vector(1000.0, 1000.0, 0.0),
|
||||
FreeCAD.Vector(2000.0, 0.0, 0.0),
|
||||
FreeCAD.Vector(3000.0, 0.0, 1000.0),
|
||||
FreeCAD.Vector(4000.0, 0.0, 0.0)]
|
||||
|
||||
edges = []
|
||||
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
|
||||
edge = Part.Arc(start, mid, end).toShape()
|
||||
edges.append(edge)
|
||||
wire5 = Part.Wire(edges)
|
||||
|
||||
edges = []
|
||||
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
|
||||
edge = Part.Arc(end, mid, start).toShape()
|
||||
edge.Orientation = "Reversed"
|
||||
edges.append(edge)
|
||||
wire6 = Part.Wire(edges)
|
||||
|
||||
edges = []
|
||||
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
|
||||
edge = Part.Arc(start, mid, end).toShape()
|
||||
edge.Orientation = "Reversed"
|
||||
edges.insert(0, edge)
|
||||
wire7 = Part.Wire(edges)
|
||||
wire7.Orientation = "Reversed"
|
||||
|
||||
edges = []
|
||||
for start, mid, end in zip(points[:-2], points[1:-1], points[2:]):
|
||||
edge = Part.Arc(end, mid, start).toShape()
|
||||
edges.insert(0, edge)
|
||||
wire8 = Part.Wire(edges)
|
||||
wire8.Orientation = "Reversed"
|
||||
|
||||
# Run "get_extended_wire" for all the wires with various offset_start, offset_end combinations
|
||||
num_subtests = 0
|
||||
offset_values = (2000.0, 0.0, -1000, -2000, -3000, -5500)
|
||||
for i, wire in enumerate((wire1, wire2, wire3, wire4, wire5, wire6, wire7, wire8)):
|
||||
_msg(" Running tests with wire{}".format(i + 1))
|
||||
for offset_start in offset_values:
|
||||
for offset_end in offset_values:
|
||||
if offset_start + offset_end > -wire.Length:
|
||||
subtest = "get_extended_wire(wire{0}, {1}, {2})".format(i + 1, offset_start, offset_end)
|
||||
num_subtests += 1 # TODO: it should be "with self.subtest(subtest):" but then it doesn't report failures.
|
||||
extended = DraftGeomUtils.get_extended_wire(wire, offset_start, offset_end)
|
||||
# Test that the extended wire's length is correctly changed
|
||||
self.assertAlmostEqual(extended.Length, wire.Length + offset_start + offset_end,
|
||||
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
|
||||
if offset_start == 0.0:
|
||||
# If offset_start is 0.0, check that the wire's start point is unchanged
|
||||
self.assertAlmostEqual(extended.OrderedVertexes[0].Point.distanceToPoint(wire.OrderedVertexes[0].Point), 0.0,
|
||||
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
|
||||
if offset_end == 0.0:
|
||||
# If offset_end is 0.0, check that the wire's end point is unchanged
|
||||
self.assertAlmostEqual(extended.OrderedVertexes[-1].Point.distanceToPoint(wire.OrderedVertexes[-1].Point), 0.0,
|
||||
DraftGeomUtils.precision(), "'{0}.{1}' failed".format(operation, subtest))
|
||||
_msg(" Test completed, {} subtests run".format(num_subtests))
|
||||
|
||||
def tearDown(self):
|
||||
"""Finish the test. Nothing to do here, DraftGeomUtils doesn't need a document."""
|
||||
pass
|
||||
|
||||
# suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestDraftGeomUtils)
|
||||
# unittest.TextTestRunner().run(suite)
|
||||
Reference in New Issue
Block a user