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 @@
+
+
+
+
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 @@
+
+
+
+