freecad-cam/Mod/BIM/ArchStairs.py
2026-02-01 01:59:24 +01:00

1336 lines
67 KiB
Python

#***************************************************************************
#* Copyright (c) 2013 Yorik van Havre <yorik@uncreated.net> *
#* *
#* 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 Stairs"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
import FreeCAD,ArchComponent,Draft,DraftVecUtils,math,ArchPipe
import Part, DraftGeomUtils
from FreeCAD import Vector
from draftutils import params
if FreeCAD.GuiUp:
import FreeCADGui
from draftutils.translate 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 ArchStairs
# \ingroup ARCH
# \brief The Stairs object and tools
#
# This module provides tools to build Stairs objects.
zeroMM = FreeCAD.Units.Quantity('0mm')
class _Stairs(ArchComponent.Component):
"A stairs object"
def __init__(self,obj):
ArchComponent.Component.__init__(self,obj)
self.setProperties(obj)
obj.IfcType = "Stair"
def setProperties(self,obj):
# http://en.wikipedia.org/wiki/Stairs
pl = obj.PropertiesList
# base properties
if not "Length" in pl:
obj.addProperty("App::PropertyLength","Length","Stairs",QT_TRANSLATE_NOOP("App::Property","The length of these stairs, if no baseline is defined"))
if not "Width" in pl:
obj.addProperty("App::PropertyLength","Width","Stairs",QT_TRANSLATE_NOOP("App::Property","The width of these stairs"))
if not "Height" in pl:
obj.addProperty("App::PropertyLength","Height","Stairs",QT_TRANSLATE_NOOP("App::Property","The total height of these stairs"))
if not "Align" in pl:
obj.addProperty("App::PropertyEnumeration","Align","Stairs",QT_TRANSLATE_NOOP("App::Property","The alignment of these stairs on their baseline, if applicable"))
obj.Align = ['Left','Right','Center']
# TODO - To be combined into Width when PropertyLengthList is available
if not "WidthOfLanding" in pl:
obj.addProperty("App::PropertyFloatList","WidthOfLanding","Stairs",QT_TRANSLATE_NOOP("App::Property","The width of a Landing (Second edge and after - First edge follows Width property)"))
# steps and risers properties
if not "NumberOfSteps" in pl:
obj.addProperty("App::PropertyInteger","NumberOfSteps","Steps",QT_TRANSLATE_NOOP("App::Property","The number of risers in these stairs"))
if not "TreadDepth" in pl:
obj.addProperty("App::PropertyLength","TreadDepth","Steps",QT_TRANSLATE_NOOP("App::Property","The depth of the treads of these stairs"))
obj.setEditorMode("TreadDepth",1)
if not "RiserHeight" in pl:
obj.addProperty("App::PropertyLength","RiserHeight","Steps",QT_TRANSLATE_NOOP("App::Property","The height of the risers of these stairs"))
obj.setEditorMode("RiserHeight",1)
if not "Nosing" in pl:
obj.addProperty("App::PropertyLength","Nosing","Steps",QT_TRANSLATE_NOOP("App::Property","The size of the nosing"))
if not "TreadThickness" in pl:
obj.addProperty("App::PropertyLength","TreadThickness","Steps",QT_TRANSLATE_NOOP("App::Property","The thickness of the treads"))
if not "BlondelRatio" in pl:
obj.addProperty("App::PropertyFloat","BlondelRatio","Steps",QT_TRANSLATE_NOOP("App::Property","The Blondel ratio indicates comfortable stairs and should be between 62 and 64cm or 24.5 and 25.5in"))
obj.setEditorMode("BlondelRatio",1)
if not "RiserThickness" in pl:
obj.addProperty("App::PropertyLength","RiserThickness","Steps",QT_TRANSLATE_NOOP("App::Property","The thickness of the risers"))
if not hasattr(obj,"LandingDepth"):
obj.addProperty("App::PropertyLength","LandingDepth","Steps",QT_TRANSLATE_NOOP("App::Property","The depth of the landing of these stairs"))
if not hasattr(obj,"TreadDepthEnforce"):
obj.addProperty("App::PropertyLength","TreadDepthEnforce","Steps",QT_TRANSLATE_NOOP("App::Property","The depth of the treads of these stairs - Enforced regardless of Length or edge's Length"))
if not hasattr(obj,"RiserHeightEnforce"):
obj.addProperty("App::PropertyLength","RiserHeightEnforce","Steps",QT_TRANSLATE_NOOP("App::Property","The height of the risers of these stairs - Enforced regardless of Height or edge's Height"))
if not hasattr(obj,"Flight"):
obj.addProperty("App::PropertyEnumeration","Flight","Structure",QT_TRANSLATE_NOOP("App::Property","The direction of flight after landing"))
obj.Flight = ["Straight","HalfTurnLeft","HalfTurnRight"]
# Segment and Parts properties
if not hasattr(obj,"LastSegment"):
obj.addProperty("App::PropertyLink","LastSegment","Segment and Parts","Last Segment (Flight or Landing) of Arch Stairs connecting to This Segment")
if not hasattr(obj,"AbsTop"):
obj.addProperty("App::PropertyVector","AbsTop","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'absolute' top level of a flight of stairs leads to"))
obj.setEditorMode("AbsTop",1)
if not hasattr(obj,"OutlineLeft"):
obj.addProperty("App::PropertyVectorList","OutlineLeft","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' of stairs")) # Used for Outline of Railing
obj.setEditorMode("OutlineLeft",1)
if not hasattr(obj,"OutlineRight"):
obj.addProperty("App::PropertyVectorList","OutlineRight","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' of stairs"))
obj.setEditorMode("OutlineRight",1)
# Can't accept 'None' in list, need NaN
#if not hasattr(obj,"OutlineRailArcLeft"):
#obj.addProperty("App::PropertyVectorList","OutlineRailArcLeft","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' 'arc points' of stairs railing"))
#obj.setEditorMode("OutlineRailArcLeft",1)
#if not hasattr(obj,"OutlineRailArcRight"):
#obj.addProperty("App::PropertyVectorList","OutlineRailArcRight","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'right outline' 'arc points of stairs railing"))
#obj.setEditorMode("OutlineRailArcRight",1)
if not hasattr(self,"OutlineRailArcLeft"):
self.OutlineRailArcLeft = []
if not hasattr(self,"OutlineRailArcRight"):
self.OutlineRailArcRight = []
if not hasattr(obj,"RailingLeft"):
obj.addProperty("App::PropertyLinkHidden","RailingLeft","Segment and Parts","Name of Railing object (left) created")
if not hasattr(obj,"RailingRight"):
obj.addProperty("App::PropertyLinkHidden","RailingRight","Segment and Parts","Name of Railing object (right) created")
if not hasattr(obj,"OutlineLeftAll"):
obj.addProperty("App::PropertyVectorList","OutlineLeftAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' of all segments of stairs"))
obj.setEditorMode("OutlineLeftAll",1) # Used for Outline of Railing
if not hasattr(obj,"OutlineRightAll"):
obj.addProperty("App::PropertyVectorList","OutlineRightAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'right outline' of all segments of stairs"))
obj.setEditorMode("OutlineRightAll",1)
# Can't accept 'None' in list, need NaN
#if not hasattr(obj,"OutlineRailArcLeftAll"):
#obj.addProperty("App::PropertyVectorList","OutlineRailArcLeftAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' 'arc points' of all segments of stairs railing"))
#obj.setEditorMode("OutlineRailArcLeftAll",1) # Used for Outline of Railing
#if not hasattr(obj,"OutlineRailArcRightAll"):
#obj.addProperty("App::PropertyVectorList","OutlineRailArcRightAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'right outline' 'arc points' of all segments of stairs railing"))
#obj.setEditorMode("OutlineRailArcRightAll",1)
if not hasattr(self,"OutlineRailArcLeftAll"):
self.OutlineRailArcLeftAll = []
if not hasattr(self,"OutlineRailArcRightAll"):
self.OutlineRailArcRightAll = []
if not hasattr(obj,"RailingHeightLeft"):
obj.addProperty("App::PropertyLength","RailingHeightLeft","Segment and Parts","Height of Railing on Left hand side from Stairs or Landing ")
if not hasattr(obj,"RailingHeightRight"):
obj.addProperty("App::PropertyLength","RailingHeightRight","Segment and Parts","Height of Railing on Right hand side from Stairs or Landing ")
if not hasattr(obj,"RailingOffsetLeft"):
obj.addProperty("App::PropertyLength","RailingOffsetLeft","Segment and Parts","Offset of Railing on Left hand side from stairs or landing Edge ")
if not hasattr(obj,"RailingOffsetRight"):
obj.addProperty("App::PropertyLength","RailingOffsetRight","Segment and Parts","Offset of Railing on Right hand side from stairs or landing Edge ")
# structural properties
if not "Landings" in pl:
obj.addProperty("App::PropertyEnumeration","Landings","Structure",QT_TRANSLATE_NOOP("App::Property","The type of landings of these stairs"))
obj.Landings = ["None","At center","At each corner"]
if not "Winders" in pl:
obj.addProperty("App::PropertyEnumeration","Winders","Structure",QT_TRANSLATE_NOOP("App::Property","The type of winders in these stairs"))
obj.Winders = ["None","All","Corners strict","Corners relaxed"]
if not "Structure" in pl:
obj.addProperty("App::PropertyEnumeration","Structure","Structure",QT_TRANSLATE_NOOP("App::Property","The type of structure of these stairs"))
obj.Structure = ["None","Massive","One stringer","Two stringers"]
if not "StructureThickness" in pl:
obj.addProperty("App::PropertyLength","StructureThickness","Structure",QT_TRANSLATE_NOOP("App::Property","The thickness of the massive structure or of the stringers"))
if not "StringerWidth" in pl:
obj.addProperty("App::PropertyLength","StringerWidth","Structure",QT_TRANSLATE_NOOP("App::Property","The width of the stringers"))
if not "StructureOffset" in pl:
obj.addProperty("App::PropertyLength","StructureOffset","Structure",QT_TRANSLATE_NOOP("App::Property","The offset between the border of the stairs and the structure"))
if not "StringerOverlap" in pl:
obj.addProperty("App::PropertyLength","StringerOverlap","Structure",QT_TRANSLATE_NOOP("App::Property","The overlap of the stringers above the bottom of the treads"))
if not "DownSlabThickness" in pl:
obj.addProperty("App::PropertyLength","DownSlabThickness","Structure",QT_TRANSLATE_NOOP("App::Property","The thickness of the lower floor slab"))
if not "UpSlabThickness" in pl:
obj.addProperty("App::PropertyLength","UpSlabThickness","Structure",QT_TRANSLATE_NOOP("App::Property","The thickness of the upper floor slab"))
if not "ConnectionDownStartStairs" in pl:
obj.addProperty("App::PropertyEnumeration","ConnectionDownStartStairs","Structure",QT_TRANSLATE_NOOP("App::Property","The type of connection between the lower floor slab and the start of the stairs"))
obj.ConnectionDownStartStairs = ["HorizontalCut","VerticalCut","HorizontalVerticalCut"]
if not "ConnectionEndStairsUp" in pl:
obj.addProperty("App::PropertyEnumeration","ConnectionEndStairsUp","Structure",QT_TRANSLATE_NOOP("App::Property","The type of connection between the end of the stairs and the upper floor slab"))
obj.ConnectionEndStairsUp = ["toFlightThickness","toSlabThickness"]
self.Type = "Stairs"
def onDocumentRestored(self,obj):
ArchComponent.Component.onDocumentRestored(self,obj)
self.setProperties(obj)
if hasattr(obj,"OutlineWireLeft"):
self.update_properties_0v18_to_0v20(obj)
if obj.getTypeIdOfProperty("RailingLeft") == "App::PropertyString":
self.update_properties_0v19_to_0v20(obj)
def update_properties_0v18_to_0v20(self, obj):
doc = FreeCAD.ActiveDocument
outlineWireLeftObject = doc.getObject(obj.OutlineWireLeft)
outlineWireRightObject = doc.getObject(obj.OutlineWireRight)
try:
obj.RailingLeft = outlineWireLeftObject.InList[0]
except Exception:
pass
try:
obj.RailingRight = outlineWireRightObject.InList[0]
except Exception:
pass
obj.removeProperty("OutlineWireLeft")
obj.removeProperty("OutlineWireRight")
self.update_properties_to_0v20(obj)
from draftutils.messages import _wrn
_wrn("v0.20.3, " + obj.Label + ", "
+ translate("Arch", "removed properties 'OutlineWireLeft' and 'OutlineWireRight', and added properties 'RailingLeft' and 'RailingRight'"))
def update_properties_0v19_to_0v20(self, obj):
doc = FreeCAD.ActiveDocument
railingLeftObject = doc.getObject(obj.RailingLeft)
railingRightObject = doc.getObject(obj.RailingRight)
obj.removeProperty("RailingLeft")
obj.removeProperty("RailingRight")
self.setProperties(obj)
obj.RailingLeft = railingLeftObject
obj.RailingRight = railingRightObject
self.update_properties_to_0v20(obj)
from draftutils.messages import _wrn
_wrn("v0.20.3, " + obj.Label + ", "
+ translate("Arch", "changed the type of properties 'RailingLeft' and 'RailingRight'"))
def update_properties_to_0v20(self, obj):
additions = obj.Additions
if obj.RailingLeft in additions:
additions.remove(obj.RailingLeft)
if obj.RailingRight in additions:
additions.remove(obj.RailingRight)
obj.Additions = additions
if obj.RailingLeft is not None:
obj.RailingLeft.Visibility = True
if obj.RailingRight is not None:
obj.RailingRight.Visibility = True
def execute(self,obj):
"constructs the shape of the stairs"
if self.clone(obj):
return
self.steps = []
self.risers = []
self.pseudosteps = []
self.structures = []
pl = obj.Placement
landings = 0 # ? Any use - paul 2018.7.15
base = None
if obj.Base:
if hasattr(obj.Base,"Shape"):
if obj.Base.Shape:
if obj.Base.Shape.Solids:
base = obj.Base.Shape.copy()
# special case NumberOfSteps = 1 : multi-edges landing
if (not base) and obj.Width.Value and obj.Height.Value and (obj.NumberOfSteps > 0):
if obj.Base:
if not hasattr(obj.Base,'Shape'):
return
if obj.Base.Shape.Solids:
obj.Shape = obj.Base.Shape.copy()
obj.Placement = FreeCAD.Placement(obj.Base.Placement).multiply(pl)
obj.TreadDepth = 0.0
obj.RiserHeight = 0.0
return
if not obj.Base.Shape.Edges:
return
if obj.Base.Shape.Faces:
return
if (len(obj.Base.Shape.Edges) == 1):
edge = obj.Base.Shape.Edges[0]
if isinstance(edge.Curve,(Part.LineSegment,Part.Line)):
# preparing for multi-edges landing / segment staircase
if obj.NumberOfSteps > 1:
# if the edge has a delta Z the start point must have lowest Z:
if edge.Vertexes[0].Point.z > edge.Vertexes[1].Point.z:
edge = Part.LineSegment(edge.Vertexes[1].Point,edge.Vertexes[0].Point).toShape()
self.makeStraightStairsWithLanding(obj,edge) # all cases use makeStraightStairsWithLanding()
# preparing for multi-edges landing / segment staircase
if obj.NumberOfSteps == 1:
# TODO - All use self.makeMultiEdgesLanding(obj,edges) ?
self.makeStraightLanding(obj,edge)
if obj.NumberOfSteps == 0:
pass # Should delete the whole shape
else:
if obj.Landings == "At center":
landings = 1
self.makeCurvedStairsWithLanding(obj,edge)
else:
self.makeCurvedStairs(obj,edge)
elif len(obj.Base.Shape.Edges) >= 1:
#if obj.NumberOfSteps == 1:
# Sort the edges so each vertex tested of its tangent direction in order
## TODO - Found Part.sortEdges() occasionally return less edges then 'input'
edges = Part.sortEdges(obj.Base.Shape.Edges)[0]
self.makeMultiEdgesLanding(obj,edges)
else:
if not obj.Length.Value:
return
edge = Part.LineSegment(Vector(0,0,0),Vector(obj.Length.Value,0,0)).toShape()
self.makeStraightStairsWithLanding(obj,edge)
if self.structures or self.steps or self.risers:
base = Part.makeCompound(self.structures + self.steps + self.risers)
elif self.pseudosteps:
shape = Part.makeCompound(self.pseudosteps)
obj.Shape = shape
obj.Placement = pl
return
base = self.processSubShapes(obj,base,pl)
if base:
if not base.isNull():
obj.Shape = base
obj.Placement = pl
railingLeftObject, railWireL = None, None
railingRightObject, railWireR = None, None
doc = FreeCAD.ActiveDocument
if obj.RailingLeft:
railingLeftObject = obj.RailingLeft
if obj.OutlineLeftAll:
railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeftAll, self.OutlineRailArcLeftAll, mode = "notFaceAlso")
elif obj.OutlineLeft:
railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcLeft, mode = "notFaceAlso")
else:
print (" No obj.OutlineLeftAll or obj.OutlineLeft")
if railWireL:
if Draft.getType(railingLeftObject.Base) != "Part::Feature": # Base can have wrong type or be None.
if railingLeftObject.Base:
doc.removeObject(railingLeftObject.Base.Name)
railingLeftWireObject = doc.addObject("Part::Feature","RailingWire")
railingLeftObject.Base = railingLeftWireObject
# update the Base object shape
railingLeftObject.Base.Shape = railWireL
else:
print (" No railWireL created ")
if obj.RailingRight:
railingRightObject = obj.RailingRight
if obj.OutlineRightAll:
railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineRightAll, self.OutlineRailArcRightAll, mode = "notFaceAlso")
elif obj.OutlineLeft:
railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcRight, mode = "notFaceAlso")
else:
print (" No obj.OutlineRightAll or obj.OutlineLeft")
if railWireR:
if Draft.getType(railingRightObject.Base) != "Part::Feature": # Base can have wrong type or be None.
if railingRightObject.Base:
doc.removeObject(railingRightObject.Base.Name)
railingRightWireObject = doc.addObject("Part::Feature","RailingWire")
railingRightObject.Base = railingRightWireObject
# update the Base object shape
railingRightObject.Base.Shape = railWireR
else:
print (" No railWireL created ")
# compute step data
#if obj.NumberOfSteps > 1:
if False: # TODO - To be deleted
l = obj.Length.Value
h = obj.Height.Value
if obj.Base:
if hasattr(obj.Base,'Shape'):
l = obj.Base.Shape.Length
if obj.Base.Shape.BoundBox.ZLength:
h = obj.Base.Shape.BoundBox.ZLength
if obj.LandingDepth:
obj.TreadDepth = float(l-(landings*obj.LandingDepth.Value))/(obj.NumberOfSteps-(1+landings))
else:
obj.TreadDepth = float(l-(landings*obj.Width.Value))/(obj.NumberOfSteps-(1+landings))
obj.RiserHeight = float(h)/obj.NumberOfSteps
obj.BlondelRatio = obj.RiserHeight.Value*2+obj.TreadDepth.Value
@staticmethod
def align(basepoint,align,widthvec):
"moves a given basepoint according to the alignment"
if align == "Center":
basepoint = basepoint.add(DraftVecUtils.scale(widthvec,-0.5))
elif align == "Right":
basepoint = basepoint.add(DraftVecUtils.scale(widthvec,-1))
return basepoint
def makeMultiEdgesLanding(self,obj,edges):
"builds a 'multi-edges' landing from edges" # 'copying' from makeStraightLanding()
outline, outlineL, outlineR, vBase1, outlineP1P2ClosedNU, outlineP3P4ClosedNU, NU, pArc, pArcL, pArcR = self.returnOutlines(obj, edges, obj.Align, None, obj.Width, obj.WidthOfLanding,
obj.TreadThickness, zeroMM, zeroMM, zeroMM, zeroMM, zeroMM, True)
obj.AbsTop = vBase1[0]
stepWire, stepFace = _Stairs.returnOutlineWireFace(outline, pArc, mode = "faceAlso") #(outlinePoints, pArc, mode="wire or faceAlso")
if obj.TreadThickness.Value:
step = stepFace.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
self.steps.append(step)
else:
self.pseudosteps.append(stepFace)
if obj.StructureThickness.Value:
landingFace = stepFace
struct = landingFace.extrude(Vector(0,0,-abs(obj.StructureThickness.Value)))
if struct:
self.structures.append(struct)
self.makeRailingOutline(obj,edges)
def makeRailingOutline(self,obj,edges):
"builds railing outline "
outlineNotUsed, outlineRailL, outlineRailR, vBase2, outlineP1P2ClosedNU, outlineP3P4ClosedNU, NU, NU, pArcRailL, pArcRailR = self.returnOutlines(obj, edges, obj.Align, None, obj.Width,
obj.WidthOfLanding, obj.TreadThickness, zeroMM,
obj.RailingOffsetLeft, obj.RailingOffsetRight,
obj.RailingHeightLeft, obj.RailingHeightRight, True)
self.connectRailingVector(obj,outlineRailL,outlineRailR, pArcRailL, pArcRailR)
@staticmethod
def returnOutlineWireFace(outlinePoints, pArc, mode="wire or faceAlso"):
stepFace = None
if not any(pArc): # i.e. no arc ... though any([0, '', False]):- is False
stepWire = Part.makePolygon(outlinePoints)
if mode == "faceAlso":
stepFace = Part.Face(stepWire)
else:
edges = []
enum_outlinePoints = enumerate(outlinePoints)
lenoutlinePoints = len(outlinePoints)
for k, a in enum_outlinePoints:
if k < (lenoutlinePoints-1): # iterate to last but 1: [k], [k+1] ... len() is +1 over index
if pArc[k] is None:
edges.append(Part.LineSegment(outlinePoints[k],outlinePoints[k+1]).toShape())
else:
edges.append(Part.Arc(outlinePoints[k],pArc[k],outlinePoints[k+1]).toShape())
stepWire = Part.Wire(edges)
if mode == "faceAlso":
stepFace = Part.Face(stepWire)
return stepWire, stepFace
@staticmethod # obj become stairsObj
def returnOutlines(stairsObj, edges, align="Left", mode=None, widthFirstSegment=zeroMM, widthOtherSegments=[], treadThickness=zeroMM,
railStartRiser=zeroMM, offsetHLeft=zeroMM, offsetHRight=zeroMM, offsetVLeft=zeroMM, offsetVRight=zeroMM, widthFirstSegmentDefault=False):
''' Construct outline of stairs landing or the like from Edges - Side effect is vertexes are 'ordered' in series of findIntersection() functions '''
''' outlineP1P2Ordered seem no use at the moment '''
#import DraftGeomUtils
v, vLength, vWidth, vBase = [], [], [], []
p1, p2, p3, p4, pArc, pArc1, pArc2 = [], [], [], [], [], [], [] # p1o, p2o - Not used
outline, outlineP1P2, outlineP3P4, outlineP1P2Closed, outlineP3P4Closed, outlineP1P2Ordered = [], [], [], [], [], []
if not isinstance(edges, list):
edges = [edges]
enum_edges = enumerate(edges)
for i, edge in enum_edges:
isLine = isinstance(edge.Curve,(Part.Line, Part.LineSegment))
isArc = isinstance(edge.Curve,Part.Circle) # why it is Part.Circle for an Arc Edge? - why Part.ArcOfCircle Not Working?
''' (1) append v (vec) '''
v.append(DraftGeomUtils.vec(edge)) # TODO check all function below ok with curve?
''' (2) get netWidthI '''
netWidthI = 0
if i > 0:
try:
if widthOtherSegments[i-1] > 0 or (not widthFirstSegmentDefault):
netWidthI = widthOtherSegments[i-1] - offsetHLeft.Value - offsetHRight.Value #2*offsetH
else: # i.e. elif widthFirstSegmentDefault:
netWidthI = widthFirstSegment.Value - offsetHLeft.Value - offsetHRight.Value #2*offsetH
except Exception:
if widthFirstSegmentDefault:
netWidthI = widthFirstSegment.Value - offsetHLeft.Value - offsetHRight.Value #2*offsetH
else:
netWidthI = widthFirstSegment.Value - offsetHLeft.Value - offsetHRight.Value #2*offsetH
''' (3) append vBase '''
vBase.append(edges[i].Vertexes[0].Point)
if isArc:
vBase1 = edge.Vertexes[1].Point
vBase2 = (edge.valueAt((edge.LastParameter+edge.FirstParameter)/2))
#vBase2vec = (vBase2-vBase[i]) # - would not be correct if Align is not Left
''' (1a) calc & append vLength - Need v (vec) '''
vLength.append(Vector(v[i].x,v[i].y,v[i].z)) # TODO check all function below ok with curve? # TODO vLength in this f() is 3d
''' (1b, 2a) calc & append vWidth - Need vLength, netWidthI '''
#vWidth.append(DraftVecUtils.scaleTo(vLength[i].cross(Vector(0,0,1)),netWidthI))
if isLine:
dvec = vLength[i].cross(Vector(0,0,1))
elif isArc:
#dvec = edge.Vertexes[0].Point.sub(edge.Curve.Center) # TODO - how to determine direction? - Reference from ArchWall; used tangentAt instead
#dvec1 = edge.Vertexes[1].Point.sub(edge.Curve.Center)
dvec = edge.tangentAt(edge.FirstParameter).cross(Vector(0,0,1))
dvec1 = edge.tangentAt(edge.LastParameter).cross(Vector(0,0,1))
dvec2 = edge.tangentAt((edge.LastParameter+edge.FirstParameter)/2).cross(Vector(0,0,1))
vWidth.append(DraftVecUtils.scaleTo(dvec,netWidthI))
if isArc:
vWidth1=DraftVecUtils.scaleTo(dvec1,netWidthI)
vWidth2=DraftVecUtils.scaleTo(dvec2,netWidthI)
''' (3a) alter vBase '''
if stairsObj:
vBase[i] = stairsObj.Proxy.vbaseFollowLastSegment(stairsObj, vBase[i])
if isArc:
vBase1 = stairsObj.Proxy.vbaseFollowLastSegment(stairsObj, vBase1)
vBase2 = stairsObj.Proxy.vbaseFollowLastSegment(stairsObj, vBase2)
vBase[i] = vBase[i].add(Vector(0,0,offsetVLeft.Value))
vBase[i] = vBase[i].add(Vector(0,0,railStartRiser.Value))
if isArc:
vBase1 = vBase1.add(Vector(0,0,offsetVLeft.Value)) # TODO - if arc is flight (sloping then), arc would be ellipse, so the following become incorrect?
vBase1 = vBase1.add(Vector(0,0,railStartRiser.Value))
vBase2 = vBase2.add(Vector(0,0,offsetVLeft.Value))
vBase2 = vBase2.add(Vector(0,0,railStartRiser.Value))
vOffsetH = DraftVecUtils.scaleTo(dvec,offsetHLeft.Value)
if isArc:
vOffsetH1 = DraftVecUtils.scaleTo(dvec1,offsetHLeft.Value)
vOffsetH2 = DraftVecUtils.scaleTo(dvec2,offsetHLeft.Value)
if align == "Left":
vBase[i] = _Stairs.align(vBase[i], "Right", -vOffsetH)
if isArc:
vBase1 = _Stairs.align(vBase1, "Right", -vOffsetH1)
vBase2 = _Stairs.align(vBase2, "Right", -vOffsetH2)
elif align == "Right":
vBase[i] = _Stairs.align(vBase[i], "Right", vOffsetH)
if isArc:
vBase1 = _Stairs.align(vBase1, "Right", vOffsetH1)
vBase2 = _Stairs.align(vBase2, "Right", vOffsetH2)
''' (3b, 2b/1c) get + alter [p1, p2, p3, p4] - Need vBase '''
p1.append(_Stairs.align(vBase[i], align, vWidth[i]).add(Vector(0,0,-abs(treadThickness.Value)))) # vWidth already calculated above against arc geometry
if isLine:
p2.append(p1[i].add(vLength[i]).add(Vector(0,0,-railStartRiser.Value)))
p3.append(p2[i].add(vWidth[i]).add(Vector(0,0,(offsetVRight-offsetVLeft).Value)))
p4.append(p3[i].add(DraftVecUtils.neg(vLength[i])).add(Vector(0,0,railStartRiser.Value)))
pArc1.append(None)
pArc2.append(None)
elif isArc:
p2.append(_Stairs.align(vBase1, align, vWidth1).add(Vector(0,0,-abs(treadThickness.Value))).add(Vector(0,0,-railStartRiser.Value)))
p3.append(p2[i].add(vWidth1.add(Vector(0,0,(offsetVRight-offsetVLeft).Value))))
p4.append(p1[i].add(vWidth[i].add(Vector(0,0,(offsetVRight-offsetVLeft).Value))))
pArc1.append(_Stairs.align(vBase2, align, vWidth2).add(Vector(0,0,-abs(treadThickness.Value))).add(Vector(0,0,-railStartRiser.Value)))
pArc2.append(pArc1[i].add(vWidth2.add(Vector(0,0,(offsetVRight-offsetVLeft).Value))))
''' (3c, 2c/2d) from [p1, p2, p3, p4] - calc outlineP1P2, outlineP3P4 '''
if i > 0:
lastEdge = edges[i-1] # thisEdge = edge
p1last = p1[i-1]
p2last = p2[i-1]
p3last = p3[i-1]
p4last = p4[i-1]
p1this = p1[i]
p2this = p2[i]
p3this = p3[i]
p4this = p4[i]
pArc1last = pArc1[i-1]
pArc2last = pArc2[i-1]
pArc1this = pArc1[i]
pArc2this = pArc2[i]
lastEdgeIsLineSegmentBool = isinstance(lastEdge.Curve,(Part.Line, Part.LineSegment))
thisEdgeIsLineSegmentBool = isinstance(edge.Curve,(Part.Line, Part.LineSegment))
lastEdgeIsCircleBool = isinstance(lastEdge.Curve,(Part.Circle)) # why it is Part.Circle for an Arc Edge? - why Part.ArcOfCircle Not Working?
thisEdgeIsCircleBool = isinstance(edge.Curve,(Part.Circle))
intersectionP1P2, intersectionP3P4 = _Stairs.findLineArcIntersections(p1last, p2last, p3last, p4last, p1this, p2this, p3this, p4this, lastEdgeIsLineSegmentBool, thisEdgeIsLineSegmentBool,
lastEdgeIsCircleBool, thisEdgeIsCircleBool, pArc1last, pArc2last, pArc1this, pArc2this)
outlineP1P2.append(intersectionP1P2)
outlineP3P4.insert(0, intersectionP3P4)
else:
outlineP1P2.append(p1[i])
outlineP3P4.insert(0, p4[i])
# add back last/first 'missing' point(s)
outlineP1P2.append(p2[i])
outlineP3P4.insert(0, p3[i])
outline = outlineP1P2 + outlineP3P4
outline.append(p1[0])
pArc1.append(None)
pArc2 = pArc2[::-1] # pArcReverse = pArc2[::-1]
pArc2.append(None)
pArc.extend(pArc1)
pArc.extend(pArc2) # pArc.extend(pArcReverse)
firstEdgeIsLineSegmentBool = isinstance(edges[0].Curve,(Part.Line, Part.LineSegment))
firstEdgeIsCircleBool = isinstance(edges[0].Curve,(Part.Circle)) # why it is Part.Circle for an Arc Edge? - why Part.ArcOfCircle Not Working?
if mode in ["OrderedClose", "OrderedCloseAndOrderedOpen"]: # seem only using 'OrderedClose'
intersectionP1P2, intersectionP3P4 = _Stairs.findLineArcIntersections(p1this, p2this, p3this, p4this, p1[0], p2[0], p3[0], p4[0], thisEdgeIsLineSegmentBool, firstEdgeIsLineSegmentBool,
thisEdgeIsCircleBool, firstEdgeIsCircleBool, pArc1this, pArc2this, pArc1[0], pArc2[0])
outlineP1P2Closed = list(outlineP1P2)
outlineP1P2Closed[0] = intersectionP1P2 #intersection[0]
outlineP1P2Closed[i+1] = intersectionP1P2 #intersection[0]
outlineP3P4Closed = list(outlineP3P4)
outlineP3P4Closed[0] = intersectionP3P4 #intersection[0]
outlineP3P4Closed[i+1] = intersectionP3P4 #intersection[0]
if mode in ["OrderedOpen", "OrderedCloseAndOrderedOpen"]:
if i > 0: # Multi-edge, otherwise no use
outlineP1P2Ordered = list(outlineP1P2)
''' Guessing the 1st Start Point based on Intersection '''
vx1 = Vector(outlineP1P2[1].x, outlineP1P2[1].y, 0)
l0 = Part.LineSegment(edges[0].Vertexes[0].Point, edges[0].Vertexes[1].Point)
try:
distFrom1stParameter = l0.parameter(vx1)
distFrom2ndParameter = l0.length()-distFrom1stParameter
''' Further point of this line from intersection '''
if distFrom2ndParameter > distFrom1stParameter:
foundStart = edges[0].Vertexes[1].Point
else: # if distFrom2ndParameter = / < distFrom1stParameter (i.e. if equal, Vertexes[0].Point is taken ?)
foundStart = edges[0].Vertexes[0].Point
except Exception:
print('Intersection point Not on this edge')
''' Guessing the last End Point based on Intersection '''
vx99 = Vector(outlineP1P2[i].x, outlineP1P2[i].y, 0)
l99 = Part.LineSegment(edges[i].Vertexes[0].Point, edges[i].Vertexes[1].Point)
try:
distFrom1stParameter = l99.parameter(vx99)
distFrom2ndParameter = l99.length()-distFrom1stParameter
if distFrom2ndParameter > distFrom1stParameter:
foundEnd = edges[i].Vertexes[1].Point
else:
foundEnd = edges[i].Vertexes[0].Point
except Exception:
print('Intersection point Not on this edge')
outlineP1P2Ordered[0] = foundStart
outlineP1P2Ordered[i+1] = foundEnd
return outline, outlineP1P2, outlineP3P4, vBase, outlineP1P2Closed, outlineP3P4Closed, outlineP1P2Ordered, pArc, pArc1, pArc2
@staticmethod
def findLineArcIntersections(p1last, p2last, p3last, p4last, p1this, p2this, p3this, p4this, lastEdgeIsLineSegmentBool, thisEdgeIsLineSegmentBool, lastEdgeIsCircleBool, thisEdgeIsCircleBool,
pArc1last, pArc2last, pArc1this, pArc2this):
if lastEdgeIsLineSegmentBool and thisEdgeIsLineSegmentBool:
intersectionsP1P2 = DraftGeomUtils.findIntersection(p1last,p2last,p1this,p2this,True,True)
intersectionsP3P4 = DraftGeomUtils.findIntersection(p3last,p4last,p3this,p4this,True,True)
return intersectionsP1P2[0], intersectionsP3P4[0]
else:
if lastEdgeIsCircleBool:
edge1 = Part.Arc(p1last,pArc1last,p2last).toShape() # edge1 = Part.Arc(p1[i-1],pArc1[i-1],p2[i-1]).toShape()
edge1a = Part.Arc(p3last,pArc2last,p4last).toShape() # edge1a = Part.Arc(p3[i-1],pArc2[i-1],p4[i-1]).toShape()
else:
edge1 = Part.LineSegment(p1last,p2last).toShape() # edge1 = Part.LineSegment(p1[i-1],p2[i-1]).toShape()
edge1a = Part.LineSegment(p3last,p4last).toShape() # edge1a = Part.LineSegment(p3[i-1],p4[i-1]).toShape()
if thisEdgeIsCircleBool: # why it is Part.Circle for an Arc Edge? - why Part.ArcOfCircle Not Working?
edge2 = Part.Arc(p1this,pArc1this,p2this).toShape() # edge2 = Part.Arc(p1[i],pArc1[i],p2[i]).toShape()
edge2a = Part.Arc(p3this,pArc2this,p4this).toShape() # edge2a = Part.Arc(p3[i],pArc2[i],p4[i]).toShape()
else:
edge2 = Part.LineSegment(p1this,p2this).toShape() # edge2 = Part.LineSegment(p1[i],p2[i]).toShape()
edge2a = Part.LineSegment(p3this,p4this).toShape() # edge2a = Part.LineSegment(p3[i],p4[i]).toShape()
intersections = DraftGeomUtils.findIntersection(edge1, edge2, True,True)
enum_intersections = enumerate(intersections)
distList = []
for n, intersectionI in enum_intersections:
distList.append((intersectionI-p1this).Length) # distList.append((intersectionI-p1[i]).Length)) # TODO just use p1[i] for test; may be p2[i-1]...?
# TODO - To test and follow up if none intersection is found
nearestIntersectionIndex = distList.index(min(distList))
nearestIntersectionP1P2 = intersections[nearestIntersectionIndex]
intersections = DraftGeomUtils.findIntersection(edge1a, edge2a, True,True)
enum_intersections = enumerate(intersections)
distList = []
for n, intersectionI in enum_intersections:
distList.append((intersectionI-p4this).Length) # distList.append((intersectionI-p4[i]).Length)) # TODO just use p4[i] for test; may be p3[i-1]...?
nearestIntersectionIndex = distList.index(min(distList))
nearestIntersectionP3P4 = intersections[nearestIntersectionIndex]
return nearestIntersectionP1P2, nearestIntersectionP3P4
@staticmethod
def vbaseFollowLastSegment(obj, vBase):
if obj.LastSegment:
lastSegmentAbsTop = obj.LastSegment.AbsTop
vBase = Vector(vBase.x, vBase.y,lastSegmentAbsTop.z) # use Last Segment top's z-coordinate
return vBase
# Add flag (temporarily?) for indicating which method call this to determine whether the landing has been 're-based' before or not
def makeStraightLanding(self,obj,edge,numberofsteps=None, callByMakeStraightStairsWithLanding=False): # what is use of numberofsteps ?
"builds a landing from a straight edge"
# general data
if not numberofsteps:
numberofsteps = obj.NumberOfSteps
v = DraftGeomUtils.vec(edge)
vLength = Vector(v.x,v.y,0)
vWidth = vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
vBase = edge.Vertexes[0].Point
# if not call by makeStraightStairsWithLanding() - not 're-base' in function there, then 're-base' here
if not callByMakeStraightStairsWithLanding:
vBase = self.vbaseFollowLastSegment(obj, vBase)
obj.AbsTop = vBase
if not obj.Flight in ["HalfTurnLeft","HalfTurnRight"]:
vNose = DraftVecUtils.scaleTo(vLength,-abs(obj.Nosing.Value))
else:
vNose = Vector(0,0,0)
h = 0
l = 0
if obj.RiserHeightEnforce != 0:
h = obj.RiserHeightEnforce * numberofsteps
elif obj.Base: # TODO - should this happen? - though in original code
if hasattr(obj.Base,'Shape'):
#l = obj.Base.Shape.Length
#if obj.Base.Shape.BoundBox.ZLength:
if round(obj.Base.Shape.BoundBox.ZLength,Draft.precision()) != 0: # ? - need precision
h = obj.Base.Shape.BoundBox.ZLength #.Value?
else:
print ("obj.Base has 0 z-value")
print (h)
if (h == 0) and obj.Height.Value != 0:
h = obj.Height.Value
else:
print (h)
if obj.TreadDepthEnforce != 0:
l = obj.TreadDepthEnforce.Value * (numberofsteps-2)
if obj.LandingDepth:
l += obj.LandingDepth.Value
else:
l += obj.Width.Value
elif obj.Base:
if hasattr(obj.Base,'Shape'):
l = obj.Base.Shape.Length #.Value?
elif obj.Length.Value != 0:
l = obj.Length.Value
if obj.LandingDepth:
fLength = float(l-obj.LandingDepth.Value)/(numberofsteps-2)
else:
fLength = float(l-obj.Width.Value)/(numberofsteps-2)
fHeight = float(h)/numberofsteps
a = math.atan(fHeight/fLength)
print("landing data:",fLength,":",fHeight)
# step
p1 = self.align(vBase,obj.Align,vWidth)
p1o = p1.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
p1 = p1.add(vNose).add(Vector(0,0,-abs(obj.TreadThickness.Value)))
p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
p3 = p2.add(vWidth)
p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
p4o = p3.add(DraftVecUtils.neg(vLength))
if not callByMakeStraightStairsWithLanding:
p2o = p2
p3o = p3
if callByMakeStraightStairsWithLanding:
if obj.Flight == "HalfTurnLeft":
p1 = p1.add(-vWidth)
p2 = p2.add(-vWidth)
elif obj.Flight == "HalfTurnRight":
p3 = p3.add(vWidth)
p4 = p4.add(vWidth)
step = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
if obj.TreadThickness.Value:
step = step.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
self.steps.append(step)
else:
self.pseudosteps.append(step)
# structure
struct = None
p1 = p1.add(DraftVecUtils.neg(vNose))
p2 = p1.add(Vector(0,0,-(abs(fHeight) - obj.TreadThickness.Value)))
p3 = p2.add(vLength)
p4 = p1.add(vLength)
if obj.Structure == "Massive":
if obj.StructureThickness.Value:
struct = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
evec = vWidth
mvec = FreeCAD.Vector(0,0,0)
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
struct.translate(mvec)
if obj.Flight in ["HalfTurnLeft","HalfTurnRight"]:
evec = DraftVecUtils.scaleTo(evec,2*evec.Length-2*mvec.Length)
else:
evec = DraftVecUtils.scaleTo(evec,evec.Length-(2*mvec.Length))
struct = struct.extrude(evec)
elif obj.Structure in ["One stringer","Two stringers"]:
if obj.StringerWidth.Value and obj.StructureThickness.Value:
reslength = fHeight/math.tan(a)
p1b = p1.add(DraftVecUtils.scaleTo(vLength,reslength))
p1c = p1.add(Vector(0,0,-fHeight))
reslength = obj.StructureThickness.Value/math.cos(a)
p1d = p1c.add(Vector(0,0,-reslength))
reslength = obj.StructureThickness.Value*math.tan(a/2)
p2 = p1b.add(DraftVecUtils.scaleTo(vLength,reslength)).add(Vector(0,0,-obj.StructureThickness.Value))
p3 = p4.add(DraftVecUtils.scaleTo(vLength,reslength)).add(Vector(0,0,-obj.StructureThickness.Value))
if obj.TreadThickness.Value:
reslength = obj.TreadThickness.Value/math.tan(a)
p3c = p4.add(DraftVecUtils.scaleTo(vLength,reslength)).add(Vector(0,0,obj.TreadThickness.Value))
reslength = obj.StructureThickness.Value/math.sin(a)
p3b = p3c.add(DraftVecUtils.scaleTo(vLength,reslength))
pol = Part.Face(Part.makePolygon([p1b,p1c,p1d,p2,p3,p3b,p3c,p4,p1b]))
else:
reslength = obj.StructureThickness.Value/math.sin(a)
p3b = p4.add(DraftVecUtils.scaleTo(vLength,reslength))
pol = Part.Face(Part.makePolygon([p1b,p1c,p1d,p2,p3,p3b,p1b]))
evec = DraftVecUtils.scaleTo(vWidth,obj.StringerWidth.Value)
if obj.Structure == "One stringer":
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
else:
mvec = DraftVecUtils.scaleTo(vWidth,(vWidth.Length/2)-obj.StringerWidth.Value/2)
pol.translate(mvec)
struct = pol.extrude(evec)
elif obj.Structure == "Two stringers":
pol2 = pol.copy()
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
pol.translate(mvec)
mvec = vWidth.add(mvec.negative())
pol2.translate(mvec)
else:
pol2.translate(vWidth)
s1 = pol.extrude(evec)
s2 = pol2.extrude(evec.negative())
struct = Part.makeCompound([s1,s2])
# Overwriting result of above functions if case fit - should better avoid running the above in first place (better rewrite later)
if not callByMakeStraightStairsWithLanding:
if obj.StructureThickness.Value:
struct = None
landingFace = Part.Face(Part.makePolygon([p1o,p2o,p3o,p4o,p1o]))
struct = landingFace.extrude(Vector(0,0,-abs(obj.StructureThickness.Value)))
if struct:
self.structures.append(struct)
def makeStraightStairs(self,obj,edge,s1,s2,numberofsteps=None,downstartstairs=None,endstairsup=None):
"builds a simple, straight staircase from a straight edge"
# Upgrade obj if it is from an older version of FreeCAD
if not hasattr(obj, "StringerOverlap"):
obj.addProperty("App::PropertyLength","StringerOverlap","Structure",QT_TRANSLATE_NOOP("App::Property","The overlap of the stringers above the bottom of the treads"))
# general data
if not numberofsteps:
numberofsteps = obj.NumberOfSteps
# if not numberofsteps - not call by makeStraightStairsWithLanding()
# if not 're-base' there (StraightStair is part of StraightStairsWithLanding 'flight'), then 're-base' here (StraightStair is individual 'flight')
callByMakeStraightStairsWithLanding = False
else:
callByMakeStraightStairsWithLanding = True
if not downstartstairs:
downstartstairs = obj.ConnectionDownStartStairs
if not endstairsup:
endstairsup = obj.ConnectionEndStairsUp
v = DraftGeomUtils.vec(edge)
vLength = DraftVecUtils.scaleTo(v,float(edge.Length)/(numberofsteps-1))
vLength = Vector(vLength.x,vLength.y,0)
if round(v.z,Draft.precision()) != 0:
h = v.z
else:
h = obj.Height.Value
vHeight = Vector(0,0,float(h)/numberofsteps)
vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
vBase = edge.Vertexes[0].Point
if not callByMakeStraightStairsWithLanding:
if obj.LastSegment:
print("obj.LastSegment is: " )
print(obj.LastSegment.Name)
lastSegmentAbsTop = obj.LastSegment.AbsTop
print("lastSegmentAbsTop is: ")
print(lastSegmentAbsTop)
vBase = Vector(vBase.x, vBase.y,lastSegmentAbsTop.z) # use Last Segment top's z-coordinate
obj.AbsTop = vBase.add(Vector(0,0,h))
vNose = DraftVecUtils.scaleTo(vLength,-abs(obj.Nosing.Value))
a = math.atan(vHeight.Length/vLength.Length)
vBasedAligned = self.align(vBase,obj.Align,vWidth)
vRiserThickness = DraftVecUtils.scaleTo(vLength,obj.RiserThickness.Value) # 50)
# steps and risers
for i in range(numberofsteps-1):
#p1 = vBase.add((Vector(vLength).multiply(i)).add(Vector(vHeight).multiply(i+1)))
p1 = vBasedAligned.add((Vector(vLength).multiply(i)).add(Vector(vHeight).multiply(i+1)))
#p1 = self.align(p1,obj.Align,vWidth)
#p1 = p1.add(vNose).add(Vector(0,0,-abs(obj.TreadThickness.Value)))
p1 = p1.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
r1 = p1
p1 = p1.add(vNose)
p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
p3 = p2.add(vWidth)
p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
step = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
if obj.TreadThickness.Value:
step = step.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
self.steps.append(step)
else:
self.pseudosteps.append(step)
''' risers - add to steps or pseudosteps in the meantime before adding self.risers / self.pseudorisers '''
#vResHeight = vHeight.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
r2 = r1.add(DraftVecUtils.neg(vHeight)) #vResHeight
if i == 0:
r2 = r2.add(Vector(0,0,abs(obj.TreadThickness.Value)))
r3 = r2.add(vWidth)
r4 = r3.add(vHeight) #vResHeight
if i == 0:
r4 = r4.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
riser = Part.Face(Part.makePolygon([r1,r2,r3,r4,r1]))
if obj.RiserThickness.Value:
riser = riser.extrude(vRiserThickness) #Vector(0,100,0))
self.steps.append(riser)
else:
self.pseudosteps.append(riser)
##
# structure
lProfile = []
struct = None
if obj.Structure == "Massive":
if obj.StructureThickness.Value:
# '# Massive Structure to respect 'align' attribute'
vBase = vBasedAligned.add(vRiserThickness)
for i in range(numberofsteps-1):
if not lProfile:
lProfile.append(vBase)
last = lProfile[-1]
if len(lProfile) == 1:
last = last.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
lProfile.append(last.add(vHeight))
lProfile.append(lProfile[-1].add(vLength))
lProfile[-1] = lProfile[-1].add(-vRiserThickness)
resHeight1 = obj.StructureThickness.Value/math.cos(a)
dh = s2 - float(h)/numberofsteps
resHeight2 = ((numberofsteps-1)*vHeight.Length) - dh
if endstairsup == "toFlightThickness":
lProfile.append(lProfile[-1].add(Vector(0,0,-resHeight1)))
resHeight2 = ((numberofsteps-1)*vHeight.Length)-(resHeight1+obj.TreadThickness.Value)
resLength = (vLength.Length/vHeight.Length)*resHeight2
h = DraftVecUtils.scaleTo(vLength,-resLength)
elif endstairsup == "toSlabThickness":
resLength = (vLength.Length/vHeight.Length) * resHeight2
h = DraftVecUtils.scaleTo(vLength,-resLength)
th = (resHeight1 + obj.TreadThickness.Value) - dh
resLength2 = th / math.tan(a)
lProfile.append(lProfile[-1].add(Vector(0,0,obj.TreadThickness.Value - dh)))
lProfile.append(lProfile[-1].add(DraftVecUtils.scaleTo(vLength,resLength2)))
if s1 > resHeight1:
downstartstairs = "VerticalCut"
if downstartstairs == "VerticalCut":
dh = obj.DownSlabThickness.Value - resHeight1 - obj.TreadThickness.Value
resHeight2 = resHeight2 + obj.DownSlabThickness.Value - dh
resLength = (vLength.Length/vHeight.Length)*resHeight2
lProfile.append(lProfile[-1].add(DraftVecUtils.scaleTo(vLength,-resLength)).add(Vector(0,0,-resHeight2)))
elif downstartstairs == "HorizontalVerticalCut":
temp_s1 = s1
if obj.UpSlabThickness.Value > resHeight1:
s1 = temp_s1
resHeight2 = resHeight2 + s1
resLength = (vLength.Length/vHeight.Length) * resHeight2
th = (resHeight1 - s1) + obj.TreadThickness.Value
resLength2 = th / math.tan(a)
lProfile.append(lProfile[-1].add(DraftVecUtils.scaleTo(vLength,-resLength)).add(Vector(0,0,-resHeight2)))
lProfile.append(lProfile[-1].add(DraftVecUtils.scaleTo(vLength,-resLength2)))
else:
lProfile.append(lProfile[-1].add(Vector(h.x,h.y,-resHeight2)))
lProfile.append(vBase)
pol = Part.makePolygon(lProfile)
struct = Part.Face(pol)
evec = vWidth
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
struct.translate(mvec)
evec = DraftVecUtils.scaleTo(evec,evec.Length-(2*mvec.Length))
struct = struct.extrude(evec)
elif obj.Structure in ["One stringer","Two stringers"]:
if obj.StringerWidth.Value and obj.StructureThickness.Value:
hyp = math.sqrt(vHeight.Length**2 + vLength.Length**2)
l1 = Vector(vLength).multiply(numberofsteps-1)
h1 = Vector(vHeight).multiply(numberofsteps-1).add(Vector(0,0,-abs(obj.TreadThickness.Value)+obj.StringerOverlap.Value))
p1 = vBase.add(l1).add(h1)
p1 = self.align(p1,obj.Align,vWidth)
if obj.StringerOverlap.Value <= float(h)/numberofsteps:
lProfile.append(p1)
else:
p1b = vBase.add(l1).add(Vector(0,0,float(h)))
p1a = p1b.add(Vector(vLength).multiply((p1b.z-p1.z)/vHeight.Length))
lProfile.append(p1a)
lProfile.append(p1b)
h2 = (obj.StructureThickness.Value/vLength.Length)*hyp
lProfile.append(p1.add(Vector(0,0,-abs(h2))))
h3 = lProfile[-1].z-vBase.z
l3 = (h3/vHeight.Length)*vLength.Length
v3 = DraftVecUtils.scaleTo(vLength,-l3)
lProfile.append(lProfile[-1].add(Vector(0,0,-abs(h3))).add(v3))
l4 = (obj.StructureThickness.Value/vHeight.Length)*hyp
v4 = DraftVecUtils.scaleTo(vLength,-l4)
lProfile.append(lProfile[-1].add(v4))
lProfile.append(lProfile[0])
#print(lProfile)
pol = Part.makePolygon(lProfile)
pol = Part.Face(pol)
evec = DraftVecUtils.scaleTo(vWidth,obj.StringerWidth.Value)
if obj.Structure == "One stringer":
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
else:
mvec = DraftVecUtils.scaleTo(vWidth,(vWidth.Length/2)-obj.StringerWidth.Value/2)
pol.translate(mvec)
struct = pol.extrude(evec)
elif obj.Structure == "Two stringers":
pol2 = pol.copy()
if obj.StructureOffset.Value:
mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
pol.translate(mvec)
mvec = vWidth.add(mvec.negative())
pol2.translate(mvec)
else:
pol2.translate(vWidth)
s1 = pol.extrude(evec)
s2 = pol2.extrude(evec.negative())
struct = Part.makeCompound([s1,s2])
if struct:
self.structures.append(struct)
def makeStraightStairsWithLanding(self,obj,edge):
"builds a straight staircase with/without a landing in the middle"
if obj.NumberOfSteps < 2:
print("Fewer than 2 steps, unable to create/update stairs")
return
v = DraftGeomUtils.vec(edge)
v_proj = Vector(v.x, v.y, 0) # Projected on XY plane.
landing = 0
if obj.TreadDepthEnforce == 0:
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.LandingDepth:
reslength = v_proj.Length - obj.LandingDepth.Value
else:
reslength = v_proj.Length - obj.Width.Value
treadDepth = reslength/(obj.NumberOfSteps-2)
else:
reslength = v_proj.Length
treadDepth = reslength/(obj.NumberOfSteps-1)
obj.TreadDepth = treadDepth
vLength = DraftVecUtils.scaleTo(v_proj,treadDepth)
else:
obj.TreadDepth = obj.TreadDepthEnforce
vLength = DraftVecUtils.scaleTo(v_proj,obj.TreadDepthEnforce.Value)
vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
p1 = edge.Vertexes[0].Point
if obj.RiserHeightEnforce == 0:
if round(v.z,Draft.precision()) != 0:
h = v.z
else:
h = obj.Height.Value
hstep = h/obj.NumberOfSteps
obj.RiserHeight = hstep
else:
h = obj.RiserHeightEnforce.Value * (obj.NumberOfSteps)
hstep = obj.RiserHeightEnforce.Value
obj.RiserHeight = hstep
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
landing = int(obj.NumberOfSteps/2)
else:
landing = obj.NumberOfSteps
if obj.LastSegment:
lastSegmentAbsTop = obj.LastSegment.AbsTop
p1 = Vector(p1.x, p1.y,lastSegmentAbsTop.z) # use Last Segment top's z-coordinate
obj.AbsTop = p1.add(Vector(0,0,h))
p2 = p1.add(DraftVecUtils.scale(vLength,landing-1).add(Vector(0,0,landing*hstep)))
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.LandingDepth:
p3 = p2.add(DraftVecUtils.scaleTo(vLength,obj.LandingDepth.Value))
else:
p3 = p2.add(DraftVecUtils.scaleTo(vLength,obj.Width.Value))
if obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
if (obj.Align == "Left" and obj.Flight == "HalfTurnLeft") or (obj.Align == "Right" and obj.Flight == "HalfTurnRight"):
p3r = p2
elif (obj.Align == "Left" and obj.Flight == "HalfTurnRight"):
p3r = self.align(p2,"Right",-2*vWidth) # -ve / opposite direction of "Right" - no "Left" in _Stairs.Align()
elif (obj.Align == "Right" and obj.Flight == "HalfTurnLeft"):
p3r = self.align(p2,"Right",2*vWidth)
elif (obj.Align == "Center" and obj.Flight == "HalfTurnLeft"):
p3r = self.align(p2,"Right",vWidth)
elif (obj.Align == "Center" and obj.Flight == "HalfTurnRight"):
p3r = self.align(p2,"Right",-vWidth) # -ve / opposite direction of "Right" - no "Left" in _Stairs.Align()
else:
print("Should have a bug here, if see this")
if p3r:
p4r = p3r.add(DraftVecUtils.scale(-vLength,obj.NumberOfSteps-(landing+1)).add(Vector(0,0,(obj.NumberOfSteps-landing)*hstep)))
else:
p4 = p3.add(DraftVecUtils.scale(vLength,obj.NumberOfSteps-(landing+1)).add(Vector(0,0,(obj.NumberOfSteps-landing)*hstep)))
self.makeStraightLanding(obj,Part.LineSegment(p2,p3).toShape(), None, True)
if obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
self.makeStraightStairs(obj,Part.LineSegment(p3r,p4r).toShape(),obj.RiserHeight.Value,obj.UpSlabThickness.Value,obj.NumberOfSteps-landing,"HorizontalVerticalCut",None)
else:
self.makeStraightStairs(obj,Part.LineSegment(p3,p4).toShape(),obj.RiserHeight.Value,obj.UpSlabThickness.Value,obj.NumberOfSteps-landing,"HorizontalVerticalCut",None)
self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.RiserHeight.Value,landing,None,'toSlabThickness')
else:
if obj.Landings == "At center":
print("Fewer than 4 steps, unable to create landing")
self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.UpSlabThickness.Value,landing,None,None)
print (p1, p2)
if obj.Landings == "At center" and obj.NumberOfSteps > 3:
if obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3, p4)
elif obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
print (p3r, p4r)
edge = Part.LineSegment(p1,p2).toShape()
outlineNotUsed, outlineRailL, outlineRailR, vBase2, outlineP1P2ClosedNU, outlineP3P4ClosedNU, NU, pArc, pArc1, pArc2 = self.returnOutlines(obj, edge, obj.Align, None, obj.Width, obj.WidthOfLanding,
obj.TreadThickness, obj.RiserHeight, obj.RailingOffsetLeft,
obj.RailingOffsetRight, obj.RailingHeightLeft,
obj.RailingHeightRight,True)
self.connectRailingVector(obj, outlineRailL, outlineRailR, pArc1, pArc2)
def connectRailingVector(self, obj, outlineRailL, outlineRailR, pArcRailL, pArcRailR):
obj.OutlineLeft = outlineRailL # outlineL # outlineP1P2
obj.OutlineRight = outlineRailR # outlineR # outlineP3P4
self.OutlineRailArcLeft = pArcRailL #obj.OutlineRailArcLeft = pArcRailL
self.OutlineRailArcRight = pArcRailR #obj.OutlineRailArcRight = pArcRailR
outlineLeftAll, outlineRightAll, outlineRailArcLeftAll, outlineRailArcRightAll = [], [], [], []
outlineRightAll.extend(obj.OutlineRight)
outlineRailArcRightAll = self.OutlineRailArcRight
if obj.LastSegment:
if obj.LastSegment.OutlineLeftAll:
outlineLeftAll.extend(obj.LastSegment.OutlineLeftAll)
if obj.LastSegment.Proxy.OutlineRailArcLeftAll: # need if?
outlineRailArcLeftAll.extend(obj.LastSegment.Proxy.OutlineRailArcLeftAll)
if (outlineLeftAll[-1] - obj.OutlineLeft[0]).Length < 0.01: # To avoid 2 points overlapping fail creating LineSegment # TODO to allow tolerance Part.LineSegment / edge.toShape() allow?
# no need abs() after .Length right?
del outlineLeftAll[-1]
del outlineRailArcLeftAll[-1]
if (outlineRightAll[-1] - obj.LastSegment.OutlineRightAll[0]).Length < 0.01: # See above
del outlineRightAll[-1]
del outlineRailArcRightAll[-1]
if obj.LastSegment.OutlineRightAll: # need if?
outlineRightAll.extend(obj.LastSegment.OutlineRightAll)
if obj.LastSegment.Proxy.OutlineRailArcRightAll: # need if?
outlineRailArcRightAll.extend(obj.LastSegment.Proxy.OutlineRailArcRightAll)
outlineLeftAll.extend(obj.OutlineLeft)
outlineRailArcLeftAll.extend(self.OutlineRailArcLeft)
obj.OutlineLeftAll = outlineLeftAll
obj.OutlineRightAll = outlineRightAll
self.OutlineRailArcLeftAll = outlineRailArcLeftAll
self.OutlineRailArcRightAll = outlineRailArcRightAll
def makeCurvedStairs(self,obj,edge):
print("Not yet implemented!")
def makeCurvedStairsWithLanding(self,obj,edge):
print("Not yet implemented!")
class _ViewProviderStairs(ArchComponent.ViewProviderComponent):
"A View Provider for Stairs"
def __init__(self,vobj):
ArchComponent.ViewProviderComponent.__init__(self,vobj)
def getIcon(self):
import Arch_rc
return ":/icons/Arch_Stairs_Tree.svg"
def claimChildren(self):
"Define which objects will appear as children in the tree view"
if hasattr(self, "Object"):
obj = self.Object
lst = []
if hasattr(obj, "Base"):
lst.append(obj.Base)
if hasattr(obj, "RailingLeft"):
lst.append(obj.RailingLeft)
if hasattr(obj, "RailingRight"):
lst.append(obj.RailingRight)
if hasattr(obj, "Additions"):
lst.extend(obj.Additions)
if hasattr(obj, "Subtractions"):
lst.extend(obj.Subtractions)
return lst
return []