From 0c96f77184edc0a6747483b2ff1462fc6dae46ff Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Thu, 23 Apr 2020 13:18:09 +0200 Subject: [PATCH] Arch: Added Truss tool --- src/Mod/Arch/Arch.py | 1 + src/Mod/Arch/ArchTruss.py | 416 ++++++++++++++++++ src/Mod/Arch/CMakeLists.txt | 1 + src/Mod/Arch/InitGui.py | 3 +- src/Mod/Arch/Resources/Arch.qrc | 2 + src/Mod/Arch/Resources/icons/Arch_Truss.svg | 246 +++++++++++ .../Arch/Resources/icons/Arch_Truss_Tree.svg | 224 ++++++++++ 7 files changed, 892 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Arch/ArchTruss.py create mode 100644 src/Mod/Arch/Resources/icons/Arch_Truss.svg create mode 100644 src/Mod/Arch/Resources/icons/Arch_Truss_Tree.svg diff --git a/src/Mod/Arch/Arch.py b/src/Mod/Arch/Arch.py index 1373b5ee31..8273a7acc3 100644 --- a/src/Mod/Arch/Arch.py +++ b/src/Mod/Arch/Arch.py @@ -64,3 +64,4 @@ from ArchPrecast import * from ArchPipe import * from ArchBuildingPart import * from ArchReference import * +from ArchTruss import * diff --git a/src/Mod/Arch/ArchTruss.py b/src/Mod/Arch/ArchTruss.py new file mode 100644 index 0000000000..d330ae8e8f --- /dev/null +++ b/src/Mod/Arch/ArchTruss.py @@ -0,0 +1,416 @@ +# -*- coding: utf8 -*- + +#*************************************************************************** +#* Copyright (c) 2020 Yorik van Havre * +#* * +#* 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 * +#* * +#*************************************************************************** + +__title__="FreeCAD Arch Truss" +__author__ = "Yorik van Havre" +__url__ = "http://www.freecadweb.org" + +import math,sys +import FreeCAD +import Draft +import ArchComponent +import ArchCommands + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + from DraftTools import translate + from PySide.QtCore import QT_TRANSLATE_NOOP +else: + # \cond + def translate(ctxt,txt): + return txt + def QT_TRANSLATE_NOOP(ctxt,txt): + return txt + # \endcond + +## @package ArchSpace +# \ingroup ARCH +# \brief The Truss object and tools +# +# This module provides tools to build Truss objects. + +rodmodes = ("/|/|/|", + "/\\/\\/\\", + "/|\\|/|\\", + ) + +def makeTruss(baseobj=None,name="Truss"): + + """ + makeTruss([baseobj]): Creates a space object from the given object (a line) + """ + + if not FreeCAD.ActiveDocument: + FreeCAD.Console.PrintError("No active document. Aborting\n") + return + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Truss") + obj.Label = translate("Arch","Truss") + Truss(obj) + if FreeCAD.GuiUp: + ViewProviderTruss(obj.ViewObject) + if baseobj: + obj.Base = baseobj + if FreeCAD.GuiUp: + baseobj.ViewObject.hide() + return obj + + +class CommandArchTruss: + + "the Arch Truss command definition" + + def GetResources(self): + + return {'Pixmap' : 'Arch_Truss', + 'MenuText': QT_TRANSLATE_NOOP("Arch_Truss","Truss"), + 'Accel': "T, U", + 'ToolTip': QT_TRANSLATE_NOOP("Arch_Truss","Creates a truss object from selected line or from scratch")} + + def IsActive(self): + + return not FreeCAD.ActiveDocument is None + + def Activated(self): + + sel = FreeCADGui.Selection.getSelection() + if len(sel) > 1: + FreeCAD.Console.PrintError(translate("Arch","Please select only one base object or none")+"\n") + elif len(sel) == 1: + # build on selection + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Truss")) + FreeCADGui.addModule("Draft") + FreeCADGui.addModule("Arch") + FreeCADGui.doCommand("obj = Arch.makeTruss(FreeCAD.ActiveDocument."+FreeCADGui.Selection.getSelection()[0].Name+")") + FreeCADGui.doCommand("Draft.autogroup(obj)") + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + else: + # interactive line drawing + self.points = [] + if hasattr(FreeCAD,"DraftWorkingPlane"): + FreeCAD.DraftWorkingPlane.setup() + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.getPoint(callback=self.getPoint) + + def getPoint(self,point=None,obj=None): + + """Callback for clicks during interactive mode""" + + if point is None: + # cancelled + return + self.points.append(point) + if len(self.points) == 1: + FreeCADGui.Snapper.getPoint(last=self.points[0],callback=self.getPoint) + elif len(self.points) == 2: + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Truss")) + FreeCADGui.addModule("Draft") + FreeCADGui.addModule("Arch") + FreeCADGui.doCommand("base = Draft.makeLine(FreeCAD."+str(self.points[0])+",FreeCAD."+str(self.points[1])+")") + FreeCADGui.doCommand("obj = Arch.makeTruss(base)") + FreeCADGui.doCommand("Draft.autogroup(obj)") + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +class Truss(ArchComponent.Component): + + "The truss object" + + def __init__(self,obj): + + ArchComponent.Component.__init__(self,obj) + self.setProperties(obj) + obj.IfcType = "Beam" + + def setProperties(self,obj): + + pl = obj.PropertiesList + if not "TrussAngle" in pl: + obj.addProperty("App::PropertyAngle","TrussAngle","Truss", + QT_TRANSLATE_NOOP("App::Property","The angle of the truss")) + obj.setEditorMode("TrussAngle",1) + if not "SlantType" in pl: + obj.addProperty("App::PropertyEnumeration","SlantType","Truss", + QT_TRANSLATE_NOOP("App::Property","The slant type of this truss")) + obj.SlantType = ["Simple","Double"] + if not "Normal" in pl: + obj.addProperty("App::PropertyVector","Normal","Truss", + QT_TRANSLATE_NOOP("App::Property","The normal direction of this truss")) + obj.Normal = FreeCAD.Vector(0,0,1) + if not "HeightStart" in pl: + obj.addProperty("App::PropertyLength","HeightStart","Truss", + QT_TRANSLATE_NOOP("App::Property","The height of the truss at the start position")) + obj.HeightStart = 100 + if not "HeightEnd" in pl: + obj.addProperty("App::PropertyLength","HeightEnd","Truss", + QT_TRANSLATE_NOOP("App::Property","The height of the truss at the end position")) + obj.HeightEnd = 200 + if not "StrutStartOffset" in pl: + obj.addProperty("App::PropertyDistance","StrutStartOffset","Truss", + QT_TRANSLATE_NOOP("App::Property","An optional start offset for the top strut")) + if not "StrutEndOffset" in pl: + obj.addProperty("App::PropertyDistance","StrutEndOffset","Truss", + QT_TRANSLATE_NOOP("App::Property","An optional end offset for the top strut")) + if not "StrutHeight" in pl: + obj.addProperty("App::PropertyLength","StrutHeight","Truss", + QT_TRANSLATE_NOOP("App::Property","The height of the main top and bottom elements of the truss")) + obj.StrutHeight = 10 + if not "StrutWidth" in pl: + obj.addProperty("App::PropertyLength","StrutWidth","Truss", + QT_TRANSLATE_NOOP("App::Property","The width of the main top and bottom elements of the truss")) + obj.StrutWidth = 5 + if not "RodType" in pl: + obj.addProperty("App::PropertyEnumeration","RodType","Truss", + QT_TRANSLATE_NOOP("App::Property","The type of the middle element of the truss")) + obj.RodType = ["Round","Square"] + if not "RodDirection" in pl: + obj.addProperty("App::PropertyEnumeration","RodDirection","Truss", + QT_TRANSLATE_NOOP("App::Property","The direction of the rods")) + obj.RodDirection = ["Forward","Backward"] + if not "RodSize" in pl: + obj.addProperty("App::PropertyLength","RodSize","Truss", + QT_TRANSLATE_NOOP("App::Property","The diameter or side of the rods")) + obj.RodSize = 2 + if not "RodSections" in pl: + obj.addProperty("App::PropertyInteger","RodSections","Truss", + QT_TRANSLATE_NOOP("App::Property","The number of rod sections")) + obj.RodSections = 3 + if not "RodEnd" in pl: + obj.addProperty("App::PropertyBool","RodEnd","Truss", + QT_TRANSLATE_NOOP("App::Property","If the truss has a rod at its endpoint or not")) + if not "RodMode" in pl: + obj.addProperty("App::PropertyEnumeration","RodMode","Truss", + QT_TRANSLATE_NOOP("App::Property","How to draw the rods")) + obj.RodMode = rodmodes + self.Type = "Truss" + + def onDocumentRestored(self,obj): + + ArchComponent.Component.onDocumentRestored(self,obj) + self.setProperties(obj) + + def onChanged(self,obj,prop): + + ArchComponent.Component.onChanged(self,obj,prop) + + def execute(self,obj): + + if self.clone(obj): + return + + import Part + + pl = obj.Placement + + # test properties + if not obj.StrutWidth.Value or not obj.StrutHeight.Value: + FreeCAD.Console.PrintLog(obj.Label+": Invalid strut size\n") + return + if not obj.HeightStart.Value or not obj.HeightEnd.Value: + FreeCAD.Console.PrintLog(obj.Label+": Invalid truss heights\n") + return + if not obj.RodSections or not obj.RodSize.Value: + FreeCAD.Console.PrintLog(obj.Label+": Invalid rod config\n") + return + if not obj.Base: + FreeCAD.Console.PrintLog(obj.Label+": No base\n") + return + if (not hasattr(obj.Base,"Shape")) or (len(obj.Base.Shape.Vertexes) > 2): + FreeCAD.Console.PrintLog(obj.Label+": No base edge\n") + return + + # baseline + v0 = obj.Base.Shape.Vertexes[0].Point + v1 = obj.Base.Shape.Vertexes[-1].Point + + if obj.SlantType == "Simple": + topstrut,bottomstrut,rods,angle = self.makeTruss(obj,v0,v1) + else: + v3 = v0.add((v1.sub(v0)).multiply(0.5)) + topstrut1,bottomstrut1,rods1,angle = self.makeTruss(obj,v0,v3) + topstrut2,bottomstrut2,rods2,angle = self.makeTruss(obj,v1,v3) + topstrut = topstrut1.fuse(topstrut2) + topstrut = topstrut.removeSplitter() + bottomstrut = bottomstrut1.fuse(bottomstrut2) + bottomstrut = bottomstrut.removeSplitter() + if obj.RodEnd: + rods2 = rods2[:-1] + rods = rods1 + rods2 + + # mount shape + shape = Part.makeCompound([topstrut,bottomstrut]+rods) + shape = self.processSubShapes(obj,shape,pl) + self.applyShape(obj,shape,pl) + obj.TrussAngle = angle + + def makeTruss(self,obj,v0,v1): + + import Part + + # get normal direction + normal = obj.Normal + if not normal.Length: + normal = FreeCAD.Vector(0,0,1) + + # create base profile + maindir = v1.sub(v0) + sidedir = normal.cross(maindir) + if not sidedir.Length: + FreeCAD.Console.PrintLog(obj.Label+": normal and base are parallel\n") + return + sidedir.normalize() + p0 = v0.add(FreeCAD.Vector(sidedir).negative().multiply(obj.StrutWidth.Value/2)) + p1 = p0.add(FreeCAD.Vector(sidedir).multiply(obj.StrutWidth.Value/2).multiply(2)) + p2 = p1.add(FreeCAD.Vector(normal).multiply(obj.StrutHeight)) + p3 = p0.add(FreeCAD.Vector(normal).multiply(obj.StrutHeight)) + trussprofile = Part.Face(Part.makePolygon([p0,p1,p2,p3,p0])) + + # create bottom strut + bottomstrut = trussprofile.extrude(maindir) + + # create top strut + v2 = v0.add(FreeCAD.Vector(normal).multiply(obj.HeightStart.Value)) + v3 = v1.add(FreeCAD.Vector(normal).multiply(obj.HeightEnd.Value)) + topdir = v3.sub(v2) + if obj.StrutStartOffset.Value: + v2f = v2.add((v2.sub(v3)).normalize().multiply(obj.StrutStartOffset.Value)) + else: + v2f = v2 + if obj.StrutEndOffset.Value: + v3f = v3.add((v3.sub(v2)).normalize().multiply(obj.StrutEndOffset.Value)) + else: + v3f = v3 + offtopdir = v3f.sub(v2f) + topstrut = trussprofile.extrude(offtopdir) + topstrut.translate(v2f.sub(v0)) + topstrut.translate(FreeCAD.Vector(normal).multiply(-obj.StrutHeight.Value)) + angle = math.degrees(topdir.getAngle(maindir)) + + # create rod profile on the XY plane + if obj.RodType == "Round": + rodprofile = Part.Face(Part.Wire([Part.makeCircle(obj.RodSize/2)])) + else: + rodprofile = Part.Face(Part.makePlane(obj.RodSize,obj.RodSize,FreeCAD.Vector(-obj.RodSize/2,-obj.RodSize/2,0))) + + # create rods + rods = [] + bottomrodstart = v0.add(FreeCAD.Vector(normal).multiply(obj.StrutHeight.Value/2)) + toprodstart = v2.add(FreeCAD.Vector(normal).multiply(obj.StrutHeight.Value/2).negative()) + bottomrodvec = FreeCAD.Vector(maindir).multiply(1/obj.RodSections) + toprodvec = FreeCAD.Vector(topdir).multiply(1/obj.RodSections) + bottomrodpos = [bottomrodstart] + toprodpos = [toprodstart] + + if obj.RodMode == rodmodes[0]: + # /|/|/| + for i in range(1,obj.RodSections+1): + if (i > 1) or (obj.StrutStartOffset.Value >= 0): + # do not add first vert rod if negative offset + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-1])) + bottomrodpos.append(bottomrodpos[-1].add(bottomrodvec)) + toprodpos.append(toprodpos[-1].add(toprodvec)) + if obj.RodDirection == "Forward": + rods.append(self.makeRod(rodprofile,bottomrodpos[-2],toprodpos[-1])) + else: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-2])) + + elif obj.RodMode == rodmodes[1]: + # /\/\/\ + fw = True + for i in range(1,obj.RodSections+1): + bottomrodpos.append(bottomrodpos[-1].add(bottomrodvec)) + toprodpos.append(toprodpos[-1].add(toprodvec)) + if obj.RodDirection == "Forward": + if fw: + rods.append(self.makeRod(rodprofile,bottomrodpos[-2],toprodpos[-1])) + else: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-2])) + else: + if fw: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-2])) + else: + rods.append(self.makeRod(rodprofile,bottomrodpos[-2],toprodpos[-1])) + fw = not fw + + elif obj.RodMode == rodmodes[2]: + # /|\|/|\ + fw = True + for i in range(1,obj.RodSections+1): + if (i > 1) or (obj.StrutStartOffset.Value >= 0): + # do not add first vert rod if negative offset + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-1])) + bottomrodpos.append(bottomrodpos[-1].add(bottomrodvec)) + toprodpos.append(toprodpos[-1].add(toprodvec)) + if obj.RodDirection == "Forward": + if fw: + rods.append(self.makeRod(rodprofile,bottomrodpos[-2],toprodpos[-1])) + else: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-2])) + else: + if fw: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-2])) + else: + rods.append(self.makeRod(rodprofile,bottomrodpos[-2],toprodpos[-1])) + fw = not fw + + # add end rod + if obj.RodEnd: + rods.append(self.makeRod(rodprofile,bottomrodpos[-1],toprodpos[-1])) + + # trim rods + rods = [rod.cut(topstrut).cut(bottomstrut) for rod in rods] + + return topstrut,bottomstrut,rods,angle + + def makeRod(self,profile,p1,p2): + + """makes a rod by extruding profile between p1 and p2""" + + rot = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),p2.sub(p1)) + rod = profile.copy().translate(p1) + rod = rod.rotate(p1,rot.Axis,math.degrees(rot.Angle)) + rod = rod.extrude(p2.sub(p1)) + return rod + + +class ViewProviderTruss(ArchComponent.ViewProviderComponent): + + + "A View Provider for the Truss object" + + def __init__(self,vobj): + + ArchComponent.ViewProviderComponent.__init__(self,vobj) + + def getIcon(self): + + import Arch_rc + return ":/icons/Arch_Truss_Tree.svg" + + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Arch_Truss',CommandArchTruss()) diff --git a/src/Mod/Arch/CMakeLists.txt b/src/Mod/Arch/CMakeLists.txt index 093ba9e7f7..d5ca3e8e63 100644 --- a/src/Mod/Arch/CMakeLists.txt +++ b/src/Mod/Arch/CMakeLists.txt @@ -51,6 +51,7 @@ SET(Arch_SRCS ArchFence.py OfflineRenderingUtils.py exportIFC.py + ArchTruss.py ) SET(Dice3DS_SRCS diff --git a/src/Mod/Arch/InitGui.py b/src/Mod/Arch/InitGui.py index 3791fba309..353b8d2278 100644 --- a/src/Mod/Arch/InitGui.py +++ b/src/Mod/Arch/InitGui.py @@ -64,7 +64,8 @@ class ArchWorkbench(FreeCADGui.Workbench): "Arch_Window", "Arch_Roof", "Arch_AxisTools", "Arch_SectionPlane", "Arch_Space", "Arch_Stairs", "Arch_PanelTools", "Arch_Equipment", - "Arch_Frame", "Arch_Fence", "Arch_MaterialTools", + "Arch_Frame", "Arch_Fence", "Arch_Truss", + "Arch_MaterialTools", "Arch_Schedule", "Arch_PipeTools", "Arch_CutPlane", "Arch_CutLine", "Arch_Add", "Arch_Remove", "Arch_Survey"] diff --git a/src/Mod/Arch/Resources/Arch.qrc b/src/Mod/Arch/Resources/Arch.qrc index fa7a65c2a0..77b5223c0a 100644 --- a/src/Mod/Arch/Resources/Arch.qrc +++ b/src/Mod/Arch/Resources/Arch.qrc @@ -75,6 +75,8 @@ icons/Arch_Survey.svg icons/Arch_ToggleIfcBrepFlag.svg icons/Arch_ToggleSubs.svg + icons/Arch_Truss.svg + icons/Arch_Truss_Tree.svg icons/Arch_Wall.svg icons/Arch_Wall_Clone.svg icons/Arch_Wall_Tree.svg diff --git a/src/Mod/Arch/Resources/icons/Arch_Truss.svg b/src/Mod/Arch/Resources/icons/Arch_Truss.svg new file mode 100644 index 0000000000..c685abd912 --- /dev/null +++ b/src/Mod/Arch/Resources/icons/Arch_Truss.svg @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [yorikvanhavre] + + + Arch_Axis + 2011-12-12 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Arch/Resources/icons/Arch_Axis.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + diff --git a/src/Mod/Arch/Resources/icons/Arch_Truss_Tree.svg b/src/Mod/Arch/Resources/icons/Arch_Truss_Tree.svg new file mode 100644 index 0000000000..cf70a1a009 --- /dev/null +++ b/src/Mod/Arch/Resources/icons/Arch_Truss_Tree.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [yorikvanhavre] + + + Arch_Axis + 2011-12-12 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Arch/Resources/icons/Arch_Axis.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + +