From 41a4ef6674f9fd2aa1913d4e1687736086f48f48 Mon Sep 17 00:00:00 2001 From: furti Date: Fri, 3 May 2019 15:07:10 +0200 Subject: [PATCH] Add Arch Fence builder This tool can build a Fence by giving it a single Section, a single Post and a Path. It will calculate the number of posts and sections needed and repeat them along the path so that a full fence is build. --- src/Mod/Arch/Arch.py | 1 + src/Mod/Arch/ArchFence.py | 313 ++++++++++++++++++ src/Mod/Arch/InitGui.py | 2 +- src/Mod/Arch/Resources/icons/Arch_Fence.svg | 218 ++++++++++++ .../Arch/Resources/icons/Arch_Fence_Tree.svg | 223 +++++++++++++ 5 files changed, 756 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Arch/ArchFence.py create mode 100644 src/Mod/Arch/Resources/icons/Arch_Fence.svg create mode 100644 src/Mod/Arch/Resources/icons/Arch_Fence_Tree.svg diff --git a/src/Mod/Arch/Arch.py b/src/Mod/Arch/Arch.py index a5770527ea..78cc22f692 100644 --- a/src/Mod/Arch/Arch.py +++ b/src/Mod/Arch/Arch.py @@ -42,6 +42,7 @@ if FreeCAD.GuiUp: from ArchWall import * from ArchFloor import * +from ArchFence import * from ArchSite import * from ArchBuilding import * from ArchStructure import * diff --git a/src/Mod/Arch/ArchFence.py b/src/Mod/Arch/ArchFence.py new file mode 100644 index 0000000000..da66c3eb2d --- /dev/null +++ b/src/Mod/Arch/ArchFence.py @@ -0,0 +1,313 @@ +import math + +import FreeCAD +import ArchComponent +import Draft + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide.QtCore import QT_TRANSLATE_NOOP + import PySide.QtGui as QtGui +else: + # \cond + def translate(ctxt, txt): + return txt + + def QT_TRANSLATE_NOOP(ctxt, txt): + return txt + # \endcond + +EAST = FreeCAD.Vector(1, 0, 0) + + +class _Fence(ArchComponent.Component): + def __init__(self, obj): + + ArchComponent.Component.__init__(self, obj) + self.setProperties(obj) + # Does a IfcType exist? + # obj.IfcType = "Fence" + obj.MoveWithHost = False + + def setProperties(self, obj): + ArchComponent.Component.setProperties(self, obj) + + pl = obj.PropertiesList + + if not "Section" in pl: + obj.addProperty("App::PropertyLink", "Section", "Fence", QT_TRANSLATE_NOOP( + "App::Property", "A single section of the fence")) + + if not "Post" in pl: + obj.addProperty("App::PropertyLink", "Post", "Fence", QT_TRANSLATE_NOOP( + "App::Property", "A single fence post")) + + if not "Path" in pl: + obj.addProperty("App::PropertyLink", "Path", "Fence", QT_TRANSLATE_NOOP( + "App::Property", "The Path the fence should follow")) + + if not "NumberOfSections" in pl: + obj.addProperty("App::PropertyInteger", "NumberOfSections", "Fence", QT_TRANSLATE_NOOP( + "App::Property", "The number of sections the fence is built of")) + obj.setEditorMode("NumberOfSections", 1) + + if not "NumberOfPosts" in pl: + obj.addProperty("App::PropertyInteger", "NumberOfPosts", "Fence", QT_TRANSLATE_NOOP( + "App::Property", "The number of posts used to build the fence")) + obj.setEditorMode("NumberOfPosts", 1) + + self.Type = "Fence" + + def execute(self, obj): + import Part + + pathwire = self.calculatePathWire(obj) + + if not pathwire: + FreeCAD.Console.PrintLog( + "ArchFence.execute: path " + obj.Path.Name + " has no edges\n") + + return + + if not obj.Section: + FreeCAD.Console.PrintLog( + "ArchFence.execute: Section not set\n") + + return + + if not obj.Post: + FreeCAD.Console.PrintLog( + "ArchFence.execute: Post not set\n") + + return + + pathLength = pathwire.Length + sectionLength = obj.Section.Shape.BoundBox.XMax + postLength = obj.Post.Shape.BoundBox.XMax + + obj.NumberOfSections = self.calculateNumberOfSections( + pathLength, sectionLength, postLength) + obj.NumberOfPosts = obj.NumberOfSections + 1 + + # We assume that the section was drawn in front view + # We have to rotate the shape down so that it is aligned correctly by the algorithm later on + downRotation = FreeCAD.Rotation(FreeCAD.Vector(1, 0, 0), -90) + + postPlacements = self.calculatePostPlacements( + obj, pathwire, downRotation) + + postShapes = self.calculatePosts(obj, postPlacements) + sectionShapes = self.calculateSections(obj, postPlacements, postLength) + + allShapes = [] + allShapes.extend(postShapes) + allShapes.extend(sectionShapes) + + compound = Part.makeCompound(allShapes) + + self.applyShape(obj, compound, obj.Placement, + allowinvalid=True, allownosolid=True) + + def calculateNumberOfSections(self, pathLength, sectionLength, postLength): + withoutLastPost = pathLength - postLength + realSectionLength = sectionLength + postLength + + return math.ceil(withoutLastPost / realSectionLength) + + def calculatePostPlacements(self, obj, pathwire, rotation): + postWidth = obj.Post.Shape.BoundBox.YMax + + # We want to center the posts on the path. So move them the half width in + transformationVector = FreeCAD.Vector(0, - postWidth / 2, 0) + + placements = Draft.calculatePlacementsOnPath( + rotation, pathwire, obj.NumberOfSections + 1, transformationVector, True) + + # The placement of the last object is always the second entry in the list. + # So we move it to the end + placements.append(placements.pop(1)) + + return placements + + def calculatePosts(self, obj, postPlacements): + posts = [] + + for placement in postPlacements: + postCopy = obj.Post.Shape.copy() + postCopy.Placement = placement + + posts.append(postCopy) + + return posts + + def calculateSections(self, obj, postPlacements, postLength): + import Part + + shapes = [] + + for i in range(obj.NumberOfSections): + startPlacement = postPlacements[i] + endPlacement = postPlacements[i + 1] + + # print("start: %s, end: %s\n" % (startPlacement, endPlacement)) + + sectionLine = Part.LineSegment( + startPlacement.Base, endPlacement.Base) + sectionBase = sectionLine.value(postLength) + + if startPlacement.Rotation.isSame(endPlacement.Rotation): + sectionRotation = endPlacement.Rotation + else: + direction = endPlacement.Base.sub(startPlacement.Base) + + sectionRotation = FreeCAD.Rotation(EAST, direction) + + placement = FreeCAD.Placement() + placement.Base = sectionBase + placement.Rotation = sectionRotation + + sectionCopy = obj.Section.Shape.copy() + sectionCopy.Placement = placement + + shapes.append(sectionCopy) + + return shapes + + def calculatePathWire(self, obj): + if (hasattr(obj.Path.Shape, 'Wires') and obj.Path.Shape.Wires): + return obj.Path.Shape.Wires[0] + elif obj.Path.Shape.Edges: + return Part.Wire(obj.Path.Shape.Edges) + + return None + + +class _ViewProviderFence(ArchComponent.ViewProviderComponent): + + "A View Provider for the Fence object" + + def __init__(self, vobj): + ArchComponent.ViewProviderComponent.__init__(self, vobj) + + def getIcon(self): + import Arch_rc + + return ":/icons/Arch_Fence_Tree.svg" + + def claimChildren(self): + children = [] + + if self.Object.Section: + children.append(self.Object.Section) + + if self.Object.Post: + children.append(self.Object.Post) + + if self.Object.Path: + children.append(self.Object.Path) + + return children + + +class _CommandFence: + "the Arch Fence command definition" + + def GetResources(self): + return {'Pixmap': 'Arch_Fence', + 'MenuText': QT_TRANSLATE_NOOP("Arch_Fence", "Fence"), + 'ToolTip': QT_TRANSLATE_NOOP("Arch_Fence", "Creates a fence object from a selected section, post and path")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + sel = FreeCADGui.Selection.getSelection() + + if len(sel) != 3: + QtGui.QMessageBox.information(QtGui.QApplication.activeWindow( + ), 'Arch Fence selection', 'Select a section, post and path in exactly this order to build a fence.') + + return + + section = sel[0] + post = sel[1] + path = sel[2] + + buildFence(section, post, path) + + +def buildFence(section, post, path): + obj = FreeCAD.ActiveDocument.addObject( + 'Part::FeaturePython', 'Fence') + + _Fence(obj) + obj.Section = section + obj.Post = post + obj.Path = path + + if FreeCAD.GuiUp: + _ViewProviderFence(obj.ViewObject) + + hide(section) + hide(post) + hide(path) + + FreeCAD.ActiveDocument.recompute() + + +def hide(obj): + if hasattr(obj, 'ViewObject') and obj.ViewObject: + obj.ViewObject.Visibility = False + + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Arch_Fence', _CommandFence()) + +if __name__ == '__main__': + # For testing purposes. When someone runs the File as a macro a default fence will be generated + import Part + + def buildSection(): + parts = [] + + parts.append(Part.makeBox( + 2000, 50, 30, FreeCAD.Vector(0, 0, 1000 - 30))) + parts.append(Part.makeBox(2000, 50, 30)) + parts.append(Part.makeBox(20, 20, 1000 - + 60, FreeCAD.Vector(0, 15, 30))) + parts.append(Part.makeBox(20, 20, 1000 - 60, + FreeCAD.Vector(1980, 15, 30))) + + for i in range(8): + parts.append(Part.makeBox(20, 20, 1000 - 60, + FreeCAD.Vector((2000 / 9 * (i + 1)) - 10, 15, 30))) + + Part.show(Part.makeCompound(parts), "Section") + + return FreeCAD.ActiveDocument.getObject('Section') + + def buildPath(): + sketch = FreeCAD.ActiveDocument.addObject( + 'Sketcher::SketchObject', 'Path') + sketch.Placement = FreeCAD.Placement( + FreeCAD.Vector(0, 0, 0), FreeCAD.Rotation(0, 0, 0, 1)) + + sketch.addGeometry(Part.LineSegment(FreeCAD.Vector( + 0, 0, 0), FreeCAD.Vector(20000, 0, 0)), False) + sketch.addGeometry(Part.LineSegment(FreeCAD.Vector( + 20000, 0, 0), FreeCAD.Vector(20000, 20000, 0)), False) + + return sketch + + def buildPost(): + post = Part.makeBox(100, 100, 1000, FreeCAD.Vector(0, 0, 0)) + + Part.show(post, "Post") + + return FreeCAD.ActiveDocument.getObject('Post') + + section = buildSection() + path = buildPath() + post = buildPost() + + buildFence(section, post, path) diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py index 73e862bf9b..f73f6d5b4b 100644 --- a/src/Mod/Arch/InitGui.py +++ b/src/Mod/Arch/InitGui.py @@ -38,7 +38,7 @@ class ArchWorkbench(Workbench): "Arch_Window","Arch_Roof","Arch_AxisTools", "Arch_SectionPlane","Arch_Space","Arch_Stairs", "Arch_PanelTools","Arch_Equipment", - "Arch_Frame","Arch_MaterialTools","Arch_Schedule","Arch_PipeTools", + "Arch_Frame", "Arch_Fence", "Arch_MaterialTools","Arch_Schedule","Arch_PipeTools", "Arch_CutPlane","Arch_Add","Arch_Remove","Arch_Survey"] self.utilities = ["Arch_Component","Arch_CloneComponent","Arch_SplitMesh","Arch_MeshToShape", "Arch_SelectNonSolidMeshes","Arch_RemoveShape", diff --git a/src/Mod/Arch/Resources/icons/Arch_Fence.svg b/src/Mod/Arch/Resources/icons/Arch_Fence.svg new file mode 100644 index 0000000000..473deba48e --- /dev/null +++ b/src/Mod/Arch/Resources/icons/Arch_Fence.svg @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [yorikvanhavre] + + + Arch_Site_Tree + 2011-12-06 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Arch/Resources/icons/Arch_Site_Tree.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Arch/Resources/icons/Arch_Fence_Tree.svg b/src/Mod/Arch/Resources/icons/Arch_Fence_Tree.svg new file mode 100644 index 0000000000..4b4cf0f339 --- /dev/null +++ b/src/Mod/Arch/Resources/icons/Arch_Fence_Tree.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [yorikvanhavre] + + + Arch_Site_Tree + 2011-12-06 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Arch/Resources/icons/Arch_Site_Tree.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + +