Files
create/src/Mod/BIM/ArchTruss.py
2025-04-22 23:52:17 +02:00

334 lines
14 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2020 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__title__ = "FreeCAD Arch Truss"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
## @package ArchTruss
# \ingroup ARCH
# \brief The Truss object and tools
#
# This module provides tools to build Truss objects.
import math
import FreeCAD
import ArchComponent
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui
from draftutils.translate import translate
else:
# \cond
def translate(ctxt,txt):
return txt
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
rodmodes = ("/|/|/|",
"/\\/\\/\\",
"/|\\|/|\\",
)
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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
if not "StrutEndOffset" in pl:
obj.addProperty("App::PropertyDistance","StrutEndOffset","Truss",
QT_TRANSLATE_NOOP("App::Property","An optional end offset for the top strut"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
obj.RodSize = 2
if not "RodSections" in pl:
obj.addProperty("App::PropertyInteger","RodSections","Truss",
QT_TRANSLATE_NOOP("App::Property","The number of rod sections"), locked=True)
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"), locked=True)
if not "RodMode" in pl:
obj.addProperty("App::PropertyEnumeration","RodMode","Truss",
QT_TRANSLATE_NOOP("App::Property","How to draw the rods"), locked=True)
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
if not self.ensureBase(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"