Files
create/src/Mod/BIM/ArchStairs.py
Roy-043 8a13cbba67 BIM+Draft: change object related version info to log messages
To inform the user warnings are shown if an object has new or modified properties in the current version. These warnings can however be confusing, especially if there are many. With this PR they are turned into log messages. They are also moved out of translation, and instead of the object Label the object Name is displayed.

Additionally:
Zero path length warnings for path arrays are now only displayed if the Align property is True.

See: #21180.
2025-09-08 10:58:28 +02:00

1718 lines
87 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2013 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 Stairs"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecad.org"
## @package ArchStairs
# \ingroup ARCH
# \brief The Stairs object and tools
#
# This module provides tools to build Stairs objects.
import math
import FreeCAD
import ArchComponent
import ArchPipe
import Draft
import DraftVecUtils
import DraftGeomUtils
import Part
from FreeCAD import Vector
from draftutils import params
if FreeCAD.GuiUp:
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui
else:
# \cond
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
zeroMM = FreeCAD.Units.Quantity('0mm')
class _Stairs(ArchComponent.Component):
"A stairs object"
def __init__(self,obj):
ArchComponent.Component.__init__(self,obj)
self.Type = "Stairs"
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"), locked=True)
if not "Width" in pl:
obj.addProperty("App::PropertyLength","Width","Stairs",QT_TRANSLATE_NOOP("App::Property","The width of these stairs"), locked=True)
if not "Height" in pl:
obj.addProperty("App::PropertyLength","Height","Stairs",QT_TRANSLATE_NOOP("App::Property","The total height of these stairs"), locked=True)
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"), locked=True)
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)"), locked=True)
# 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"), locked=True)
if not "TreadDepth" in pl:
obj.addProperty("App::PropertyLength","TreadDepth","Steps",QT_TRANSLATE_NOOP("App::Property","The depth of the treads of these stairs"), locked=True)
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"), locked=True)
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"), locked=True)
if not "TreadThickness" in pl:
obj.addProperty("App::PropertyLength","TreadThickness","Steps",QT_TRANSLATE_NOOP("App::Property","The thickness of the treads"), locked=True)
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"), locked=True)
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"), locked=True)
if not hasattr(obj,"LandingDepth"):
obj.addProperty("App::PropertyLength","LandingDepth","Steps",QT_TRANSLATE_NOOP("App::Property","The depth of the landing of these stairs"), locked=True)
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"), locked=True)
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"), locked=True)
if not hasattr(obj,"Flight"):
obj.addProperty("App::PropertyEnumeration","Flight","Structure",QT_TRANSLATE_NOOP("App::Property","The direction of flight after landing"), locked=True)
obj.Flight = ["Straight","HalfTurnLeft","HalfTurnRight"]
# Segment and Parts properties
if not hasattr(obj,"LastSegment"):
obj.addProperty("App::PropertyLink","LastSegment","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","Last Segment (Flight or Landing) of Arch Stairs connecting to This Segment"), locked=True)
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"), locked=True)
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"), locked=True) # 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"), locked=True)
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"), locked=True)
#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"), locked=True)
#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",QT_TRANSLATE_NOOP("App::Property","Name of Railing object (left) created"), locked=True)
if not hasattr(obj,"RailingRight"):
obj.addProperty("App::PropertyLinkHidden","RailingRight","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","Name of Railing object (right) created"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
#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"), locked=True)
#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",QT_TRANSLATE_NOOP("App::Property","Height of Railing on Left hand side from Stairs or Landing"), locked=True)
if not hasattr(obj,"RailingHeightRight"):
obj.addProperty("App::PropertyLength","RailingHeightRight","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","Height of Railing on Right hand side from Stairs or Landing"), locked=True)
if not hasattr(obj,"RailingOffsetLeft"):
obj.addProperty("App::PropertyLength","RailingOffsetLeft","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","Offset of Railing on Left hand side from stairs or landing Edge"), locked=True)
if not hasattr(obj,"RailingOffsetRight"):
obj.addProperty("App::PropertyLength","RailingOffsetRight","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","Offset of Railing on Right hand side from stairs or landing Edge"), locked=True)
# 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"), locked=True)
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"), locked=True)
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"), locked=True)
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"), locked=True)
if not "StringerWidth" in pl:
obj.addProperty("App::PropertyLength","StringerWidth","Structure",QT_TRANSLATE_NOOP("App::Property","The width of the stringers"), locked=True)
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"), locked=True)
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"), locked=True)
if not "DownSlabThickness" in pl:
obj.addProperty("App::PropertyLength","DownSlabThickness","Structure",QT_TRANSLATE_NOOP("App::Property","The thickness of the lower floor slab"), locked=True)
if not "UpSlabThickness" in pl:
obj.addProperty("App::PropertyLength","UpSlabThickness","Structure",QT_TRANSLATE_NOOP("App::Property","The thickness of the upper floor slab"), locked=True)
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"), locked=True)
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"), locked=True)
obj.ConnectionEndStairsUp = ["toFlightThickness","toSlabThickness"]
# additional stairs properties
if not "ArchSketchData" in pl:
obj.addProperty("App::PropertyBool","ArchSketchData","Stairs",QT_TRANSLATE_NOOP("App::Property","Use Base ArchSketch (if used) data (e.g. selected edge, widths, aligns) instead of Stairs' properties"), locked=True)
obj.ArchSketchData = True
# TODO Consider other properties for flight, landing etc.
if not "ArchSketchEdges" in pl:
obj.addProperty("App::PropertyStringList","ArchSketchEdges","Stairs",QT_TRANSLATE_NOOP("App::Property","Selected edges of the base Sketch/ArchSketch, to use in creating the shape (flight) of this Arch Stairs (instead of using all the Base ArchSketch's edges by default). Input are index numbers of edges. Disabled and ignored if Base object (ArchSketch) provides selected edges (as Flight Axis) information, with getStairsBaseShapeEdgesInfo() method. [ENHANCEMENT by ArchSketch] GUI 'Edit Stairs' Tool is provided in external SketchArch Add-on to let users to (de)select the edges interactively. 'Toponaming-Tolerant' if ArchSketch is used in Base (and SketchArch Add-on is installed). Warning : Not 'Toponaming-Tolerant' if just Sketch is used."), locked=True)
if not hasattr(obj,"ArchSketchPropertySet"):
obj.addProperty("App::PropertyEnumeration","ArchSketchPropertySet","Stairs",QT_TRANSLATE_NOOP("App::Property","Select User Defined PropertySet to use in creating variant shape, with same ArchSketch "), locked=True)
obj.ArchSketchPropertySet = ['Default']
if not hasattr(self,"ArchSkPropSetPickedUuid"):
self.ArchSkPropSetPickedUuid = ''
if not hasattr(self,"ArchSkPropSetListPrev"):
self.ArchSkPropSetListPrev = []
def dumps(self): # Supercede Arch.Component.dumps()
dump = super().dumps()
if not isinstance(dump, tuple):
dump = (dump,) #Python Tuple With One Item
dump = dump + (self.ArchSkPropSetPickedUuid, self.ArchSkPropSetListPrev)
return dump
def loads(self,state):
self.Type = "Stairs"
if state == None:
return
elif state[0] == 'S': # state[1] == 't', behaviour before 2024.11.28
return
elif state[0] == 'Stairs':
self.ArchSkPropSetPickedUuid = state[1]
self.ArchSkPropSetListPrev = state[2]
elif state[0] != 'Stairs': # model before merging super.dumps/loads()
self.ArchSkPropSetPickedUuid = state[0]
self.ArchSkPropSetListPrev = state[1]
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)
if (hasattr(obj,"ArchSketchData") and obj.ArchSketchData and
Draft.getType(obj.Base) == "ArchSketch"):
if hasattr(obj,"ArchSketchEdges"):
obj.setEditorMode("ArchSketchEdges", ["ReadOnly"])
if hasattr(obj,"ArchSketchPropertySet"):
obj.setEditorMode("ArchSketchPropertySet", 0)
else:
if hasattr(obj,"ArchSketchEdges"):
obj.setEditorMode("ArchSketchEdges", 0)
if hasattr(obj,"ArchSketchPropertySet"):
obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"])
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 _log
_log(
"v0.20.3, "
+ obj.Name
+ ", "
+ "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 _log
_log(
"v0.20.3, "
+ obj.Name
+ ", "
+ "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
# Stairs can do without Base. Base validity is tested in code below.
# Remarked out ensureBase() below
#if not self.ensureBase(obj):
# return
self.steps = []
self.risers = []
self.pseudosteps = []
self.pseudorisers = []
self.structures = []
pl = obj.Placement
landings = 0 # TODO Any use? 2018.7.15
base = None
baseProxy = None
# PropertySet support
propSetPickedUuidPrev = self.ArchSkPropSetPickedUuid
propSetListPrev = self.ArchSkPropSetListPrev
propSetSelectedNamePrev = obj.ArchSketchPropertySet
propSetSelectedNameCur = None
propSetListCur = None
if Draft.getType(obj.Base) == "ArchSketch":
baseProxy = obj.Base.Proxy
if hasattr(baseProxy,"getPropertySet"):
# get full list of PropertySet
propSetListCur = baseProxy.getPropertySet(obj.Base)
# get updated name (if any) of the selected PropertySet
propSetSelectedNameCur = baseProxy.getPropertySet(obj.Base,
propSetUuid=propSetPickedUuidPrev)
if propSetSelectedNameCur: # True if selection is not deleted
if propSetListPrev != propSetListCur:
obj.ArchSketchPropertySet = propSetListCur
obj.ArchSketchPropertySet = propSetSelectedNameCur
self.ArchSkPropSetListPrev = propSetListCur
#elif propSetListPrev == propSetListCur:
#pass #nothing to do in this case
# but if below, though (propSetListPrev == propSetListCur)
elif propSetSelectedNamePrev != propSetSelectedNameCur:
obj.ArchSketchPropertySet = propSetSelectedNameCur
else: # True if selection is deleted
if propSetListCur:
if propSetListPrev != propSetListCur:
obj.ArchSketchPropertySet = propSetListCur
obj.ArchSketchPropertySet = 'Default'
#else: # Seems no need ...
#obj.PropertySet = 'Default'
# Get ArchSketchData if enabled
# e.g. flightAxis, floorHeight, landingAxis
flightAxis = landingAxis = strucInfo = [] # None
# baseProxy if obj.base is ArchSketch above
if baseProxy and obj.ArchSketchData and \
hasattr(baseProxy, 'getStairsBaseShapeEdgesInfo'):
propSetUuid = self.ArchSkPropSetPickedUuid
info = baseProxy.getStairsBaseShapeEdgesInfo(obj.Base,
propSetUuid=propSetUuid)
if info:
flightAxis = info.get('flightAxis')
strucInfo = info.get('strucInfo, None')
#floorHeight = info.get('floorHeight') TODO To support floorHeight
# Still fallback to check if Base has Solids e.g. is another Stairs,
# do not clear shape.
#if not strucInfo and not flightAxis:
#obj.Shape = Part.Shape()
#return #if ArchSketchData but no baseShapeEdge, no fallback
#pass
# Creation of base Stairs shape
# TODO Fix: Why below is repeated in next if routine? Seems this is
# just part of which further down below. To add missing codes here?
# If ArchSketch not provides info, but there is obj.Base e.g. a Stairs
if (not flightAxis and not strucInfo) and obj.Base:
if hasattr(obj.Base,"Shape"):
if obj.Base.Shape:
if obj.Base.Shape.Solids:
base = Part.Shape(obj.Base.Shape)
# special case NumberOfSteps = 1 : multi-edges landing
# TODO to Support individual width, height etc. for each flight/ landing
if (not base) and obj.Width.Value and obj.Height.Value and \
(obj.NumberOfSteps > 0):
# Check if there is obj.Base and its validity to proceed
if self.ensureBase(obj):
if not hasattr(obj.Base,'Shape'):
return
# TODO See todo remarks above, below to be further reviewed.
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 baseProxy and obj.ArchSketchData and not flightAxis :
obj.Shape = Part.Shape()
# TODO Clear Railings also?
return
elif flightAxis:
edgeL = flightAxis #[flightAxis]
# If Base is Sketch and has input in ArchSketchEdges
elif obj.Base.isDerivedFrom("Sketcher::SketchObject") and obj.ArchSketchEdges:
edgeL = []
baseGeom = obj.Base.Geometry
lg =len(baseGeom)
for e in obj.ArchSketchEdges:
ie = int(e)
if lg >= (ie+1) :
if isinstance(baseGeom[ie], Part.LineSegment):
edgeL.append(baseGeom[ie].toShape())
#if not ArchSketchData and obj.ArchSketchEdges (whether stock Sketch or ArchSketch)
elif len(obj.Base.Shape.Edges) == 1: #elif not obj.ArchSketchData and len(obj.Base.Shape.Edges) == 1:
edgeL = [obj.Base.Shape.Edges[0]]
elif len(obj.Base.Shape.Edges) > 1: #elif not obj.ArchSketchData and len(obj.Base.Shape.Edges) > 1: # >= 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'
edgeL = [Part.sortEdges(obj.Base.Shape.Edges)[0]]
else: # Should not happen?
edgeL = []
#lenAxis = len(flightAxis) + len(landingAxis)
# Build Stairs if there is no obj.Base or even obj.Base is not valid
else:
if not obj.Length.Value:
obj.Shape = Part.Shape()
# TODO clear Railings also
return
edgeL = [Part.LineSegment(Vector(0,0,0),Vector(obj.Length.Value,0,0)).toShape()]
for edge in edgeL:
if not isinstance(edge, list): # i.e. a single edges
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()
elif obj.NumberOfSteps == 1:
# TODO - All use self.makeMultiEdgesLanding(obj,edges) ?
self.makeStraightLanding(obj,edge)
elif obj.NumberOfSteps == 0:
pass # TODO Should delete the whole shape
else: # TODO Not implemented yet
if obj.Landings == "At center":
landings = 1
self.makeCurvedStairsWithLanding(obj,edge)
else:
self.makeCurvedStairs(obj,edge)
#elif len(edge.Edges) >= 1:
else: # i.e. isinstance(edge, list), or elif len(edge) >= 1:
#edges = Part.sortEdges(obj.Base.Shape.Edges)[0]
#self.makeMultiEdgesLanding(obj,edges)
self.makeMultiEdgesLanding(obj,edge)
steps = risers = None
if self.steps:
steps = self.steps
elif self.pseudosteps:
steps = self.pseudosteps
if self.risers:
risers = self.risers
elif self.pseudorisers:
risers = self.pseudorisers
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
# TODO No Railing etc?
elif base: # i.e. obj.Base has Shape e.g. it is another Stairs
pass
else:
#base = None
obj.Shape = Part.Shape()
# TODO Clear Railings also
# 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")
if FreeCAD.GuiUp:
railingLeftWireObject.ViewObject.hide()
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")
if FreeCAD.GuiUp:
railingRightWireObject.ViewObject.hide()
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 Fix this - BlondelRatio is not computed
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
def onChanged(self,obj,prop):
ArchComponent.Component.onChanged(self,obj,prop)
if (prop == "ArchSketchPropertySet"
and Draft.getType(obj.Base) == "ArchSketch"):
baseProxy = obj.Base.Proxy
if hasattr(baseProxy,"getPropertySet"):
uuid = baseProxy.getPropertySet(obj,
propSetName=obj.ArchSketchPropertySet)
self.ArchSkPropSetPickedUuid = uuid
if (hasattr(obj,"ArchSketchData") and obj.ArchSketchData
and Draft.getType(obj.Base) == "ArchSketch"):
if hasattr(obj,"ArchSketchEdges"):
obj.setEditorMode("ArchSketchEdges", ["ReadOnly"])
if hasattr(obj,"ArchSketchPropertySet"):
obj.setEditorMode("ArchSketchPropertySet", 0)
else:
if hasattr(obj,"ArchSketchEdges"):
obj.setEditorMode("ArchSketchEdges", 0)
if hasattr(obj,"ArchSketchPropertySet"):
obj.setEditorMode("ArchSketchPropertySet", ["ReadOnly"])
@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
# TODO
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] # TODO
stepWire, stepFace = _Stairs.returnOutlineWireFace(outline, pArc, mode = "faceAlso") #(outlinePoints, pArc, mode="wire or faceAlso")
if obj.TreadThickness.Value: # TODO
step = stepFace.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
self.steps.append(step)
else:
self.pseudosteps.append(stepFace)
if obj.StructureThickness.Value: # TODO
landingFace = stepFace
struct = landingFace.extrude(Vector(0,0,-abs(obj.StructureThickness.Value)))
if struct:
self.structures.append(struct)
self.makeRailingOutline(obj,edges)
# TODO
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,numOfSteps=None,
downstartstairs=None,endstairsup=None,hgt=None,
vWidth=None,align=None,vLength=None,vHeight=None,
vNose=None,vRiserThickness=None,
vTreadThickness=None,structure=None,
structureThickness=None,downSlabThickness=None,
upSlabThickness=None,structureOffset=None,
stringerWidth=None,stringerOverlap=None):
"builds a simple, straight staircase from a straight edge"
'''
edge : Edge defining the flight/landing like stairs' direction, run, rise/height etc. (mandatory)
Below parameters, if provided, would overrides information derived from the edge and/or Stairs' built-in properties -
s1 : (to be clarified)
s2 : (to be clarified)
numOfSteps : Numbers of Steps
downstartstairs : (to be clarified)
endstairsup : "toFlightThickness", "toSlabThickness"
hgt : Height of flight
vWidth : Vector - Width of flight/tread
align : Align of flight/tread
vLength : Vector - Depth/Length of tread
vHeight : Vector - Height of riser
vNose : Vector - Length of nosing
vRiserThickness : Vector - Thickness of riser
vTreadThickness : Vector - Thickness of tread
structure : "Massive", "One stringer", "Two stringers"
structureThickness : (to be clarified)
structureOffset : Value - (to be clarified)
downSlabThickness : Value - (to be clarified)
upSlabThickness : Value - (to be clarified)
stringerWidth : Value - (to be clarified)
stringerOverlap : (to be clarified)
(TODO : To support custom input of tread, riser, structure, stringer etc.
and output of these parts individually)
'''
# 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"), locked=True)
v = DraftGeomUtils.vec(edge)
v_proj = Vector(v.x, v.y, 0) # Projected on XY plane. May not be
# setup numOfSteps
if not numOfSteps:
numOfSteps = obj.NumberOfSteps
# if not numOfSteps - not call by makeStraightStairsWithLanding()
# if not 're-base' there (StraightStair is part of StraightStairsWithLanding 'flight'), then 're-base' here (StraightStair is individual 'flight')
# TODO 2025.3.23 Add attribute to identify the 'caller' instead of
callByMakeStraightStairsWithLanding = False
else:
callByMakeStraightStairsWithLanding = True
# general data
if not downstartstairs:
downstartstairs = obj.ConnectionDownStartStairs
if not endstairsup:
endstairsup = obj.ConnectionEndStairsUp
# setup hgt (flight height)
if not hgt:
if round(v.z,Draft.precision()) != 0:
hgt = v.z
else:
hgt = obj.Height.Value
# setup vWidth (flight/tread width)
if not vWidth:
vWidth = DraftVecUtils.scaleTo(v_proj.cross(Vector(0,0,1)),obj.Width.Value)
# setup align
if not align:
align = obj.Align
# setup vLength (tread length(depth) )
if not vLength:
vLength = DraftVecUtils.scaleTo(v_proj,float(v_proj.Length)/(numOfSteps-1)) # TODO need this float?
# setup vHeight (tread height)
if not vHeight:
vHeight = Vector(0,0,float(hgt)/numOfSteps)
# setup vBase: Based on edge provided
vBase = edge.Vertexes[0].Point
# TODO Review
if not callByMakeStraightStairsWithLanding:
if obj.LastSegment: # TODO
lastSegmentAbsTop = obj.LastSegment.AbsTop # TODO
vBase = Vector(vBase.x, vBase.y,lastSegmentAbsTop.z) # TODO # use Last Segment top's z-coordinate
obj.AbsTop = vBase.add(Vector(0,0,hgt)) # TODO
# setup vNose (nosing length)
if not vNose:
vNose = DraftVecUtils.scaleTo(v_proj,-abs(obj.Nosing.Value))
# setup ang
ang = math.atan(vHeight.Length/vLength.Length)
# setup vBasedAligned
vBasedAligned = self.align(vBase,align,vWidth)
if not vRiserThickness:
vRiserThickness = DraftVecUtils.scaleTo(v_proj,obj.RiserThickness.Value)
if not vTreadThickness:
vTreadThickness = DraftVecUtils.scaleTo(vHeight,obj.TreadThickness.Value)
# steps and risers
for i in range(numOfSteps-1):
p1 = vBasedAligned.add((Vector(vLength).multiply(i)).add(Vector(vHeight).multiply(i+1))) # TODO need Vector()?
p1 = p1.add(-vTreadThickness) # TODO 2025.4.20 Tested: Seems DraftVecUtils.neg(x) == -x
r1 = p1
p1 = p1.add(vNose)
p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength) # TODO 2025.4.20 Tested: Seems DraftVecUtils.neg(x) == -x
p3 = p2.add(vWidth)
p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose) # TODO 2025.4.20
step = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
if vTreadThickness.Length:
step = step.extrude(vTreadThickness) # TODO need abs()?
# TODO Add here; or add in self.execute() routine?
self.steps.append(step)
else:
self.pseudosteps.append(step)
# TODO 2025.4.20 To Review
''' risers - add to steps or pseudosteps in the meantime before adding self.risers / self.pseudorisers '''
r2 = r1.add(-vHeight) #vResHeight # TODO 2025.4.20 Tested: Seems DraftVecUtils.neg(x) == -x
if i == 0:
r2 = r2.add(vTreadThickness)
r3 = r2.add(vWidth)
r4 = r3.add(vHeight) #vResHeight
if i == 0:
r4 = r4.add(-vTreadThickness)
riser = Part.Face(Part.makePolygon([r1,r2,r3,r4,r1]))
if vRiserThickness.Length:
riser = riser.extrude(vRiserThickness) #Vector(0,100,0))
self.steps.append(riser)
# Separate risers from steps, keep above in the meantime
self.risers.append(riser)
else:
self.pseudosteps.append(riser)
# Separate risers from steps, keep above in the meantime
self.pseudorisers.append(riser)
# structure
lProfile = []
struct = None
# setup structure, structureThickness, structureOffset
if not structure:
structure = obj.Structure
if not structureThickness:
structureThickness = obj.StructureThickness.Value
if not structureOffset:
structureOffset = obj.StructureOffset.Value
if structure == "Massive":
if structureThickness:
# TODO Why 'reuse' vBase?
# '# Massive Structure to respect 'align' attribute'
vBase = vBasedAligned.add(vRiserThickness)
for i in range(numOfSteps-1):
if not lProfile:
lProfile.append(vBase)
last = lProfile[-1]
if len(lProfile) == 1:
last = last.add(-vTreadThickness)
lProfile.append(last.add(vHeight))
lProfile.append(lProfile[-1].add(vLength))
lProfile[-1] = lProfile[-1].add(-vRiserThickness)
resHeight1 = structureThickness/math.cos(ang)
dh = s2 - float(hgt)/numOfSteps
resHeight2 = ((numOfSteps-1)*vHeight.Length) - dh
if endstairsup == "toFlightThickness":
lProfile.append(lProfile[-1].add(Vector(0,0,-resHeight1)))
resHeight2 = ((numOfSteps-1)*vHeight.Length)-(resHeight1+vTreadThickness.z)
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 + vTreadThickness.z) - dh
resLength2 = th / math.tan(ang)
lProfile.append(lProfile[-1].add(Vector(0,0,vTreadThickness.z - dh)))
lProfile.append(lProfile[-1].add(DraftVecUtils.scaleTo(vLength,resLength2)))
if s1 > resHeight1:
downstartstairs = "VerticalCut"
if downstartstairs == "VerticalCut":
if not downSlabThickness:
downSlabThickness = obj.DownSlabThickness.Value
dh = downSlabThickness - resHeight1 - vTreadThickness.z
resHeight2 = resHeight2 + downSlabThickness - 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
# setup upSlabThickness
if not upSlabThickness:
upSlabThickness = obj.UpSlabThickness.Value
if upSlabThickness > resHeight1:
s1 = temp_s1
resHeight2 = resHeight2 + s1
resLength = (vLength.Length/vHeight.Length) * resHeight2
th = (resHeight1 - s1) + vTreadThickness.z
resLength2 = th / math.tan(ang)
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 structureOffset:
mvec = DraftVecUtils.scaleTo(vWidth,structureOffset)
struct.translate(mvec)
evec = DraftVecUtils.scaleTo(evec,evec.Length-(2*mvec.Length))
struct = struct.extrude(evec)
elif structure in ["One stringer","Two stringers"]:
# setup stringerWidth
if not stringerWidth:
stringerWidth = obj.StringerWidth.Value
if stringerWidth and structureThickness:
hyp = math.sqrt(vHeight.Length**2 + vLength.Length**2)
l1 = Vector(vLength).multiply(numOfSteps-1)
# setup stringerOverlap
if not stringerOverlap:
stringerOverlap = obj.StringerOverlap.Value
h1 = Vector(vHeight).multiply(numOfSteps-1).add(Vector(0,0,-vTreadThickness.Length+stringerOverlap))
p1 = vBase.add(l1).add(h1)
p1 = self.align(p1,align,vWidth)
if stringerOverlap <= float(hgt)/numOfSteps:
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 = (structureThickness/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 = (structureThickness/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,stringerWidth)
if obj.Structure == "One stringer":
if structureOffset:
mvec = DraftVecUtils.scaleTo(vWidth,structureOffset)
else:
mvec = DraftVecUtils.scaleTo(vWidth,(vWidth.Length/2)-stringerWidth/2)
pol.translate(mvec)
struct = pol.extrude(evec)
elif obj.Structure == "Two stringers":
pol2 = pol.copy()
if structureOffset:
mvec = DraftVecUtils.scaleTo(vWidth,structureOffset)
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, numOfSteps=None, landings=None, landingDepth=None, height=None, width=None, align=None, treadDepthEnforce=None, riserHeightEnforce=None, flight=None):
"builds a straight staircase with/without a landing in the middle"
'''
edge : Edge defining the flight/landing like stairs' direction, run, rise/height etc. (mandatory)
Below parameters are optional, and if provided, would overrides information derived from the edge and/or Stairs' built-in properties -
numOfSteps : None (default), or parameters from ArchSketch - int (default)
landings : None (default), or parameters from ArchSketch - 'None' (default), or 'At center' (not supported at the moment)
landingDepth : None (default), or parameters from ArchSketch - 'Auto' (default), or float
height : None (default), or parameters from ArchSketch - float (default)
width : None (default), or parameters from ArchSketch - float (default)
align : None (default), or parameters from ArchSketch - 'Left' (default), or 'Right'
treadDepthEnforce : None (default), or parameters from ArchSketch - 'Auto' (default), or float
riserHeightEnforce : None (default), or parameters from ArchSketch - 'Auto' (default), or float
flight : None (default), or parameters from ArchSketch - 'Straight' (default), or ['HalfTurnLeft', 'HalfTurnRight']
(TODO : To support custom input of tread, riser, structure, stringer etc.
and output of these parts individually)
'''
v = DraftGeomUtils.vec(edge)
v_proj = Vector(v.x, v.y, 0) # Projected on XY plane.
landingStep = 0
# setup numOfSteps
if numOfSteps is not None:
if numOfSteps < 2:
print("Fewer than 2 steps, unable to create/update stairs")
return
#else: print(" OK, numOfSteps equal or > 2 steps, proceed creation")
elif obj.NumberOfSteps < 2: #elif numOfSteps is None and obj.NumberOfSteps < 2: # Would not be called by execute()
print("Fewer than 2 steps, unable to create/update stairs")
return
else: #elif obj.NumberOfSteps >= 2: #elif numOfSteps is None and obj.NumberOfSteps >= 2:
numOfSteps = obj.NumberOfSteps
print(" numOfSteps = obj.NumberOfSteps - ", numOfSteps)
# setup landingStep - step number of landing, if present
if (landings == "At center" or obj.Landings == "At center"):
wantLanding = True
else:
wantLanding = False
if wantLanding and numOfSteps > 3:
#landing = int(obj.NumberOfSteps/2)
landingStep = int(numOfSteps/2) # so, landing >= 1
hasLanding = True
else:
#landing = obj.NumberOfSteps
landingStep = numOfSteps
hasLanding = False
if wantLanding: # but not numOfSteps > 3, so no hasLanding
print("Fewer than 4 steps, unable to create landing")
# setup height
if height is None: #if not height:
height = obj.Height.Value
# check width
if width is None: #if not width:
width = obj.Width.Value
# check align
if align is None:
align = obj.Align
# setup vLength (tread length(depth) ) : check treadDepthEnforce
if treadDepthEnforce and treadDepthEnforce != 'Auto':
vLength = DraftVecUtils.scaleTo(v_proj,treadDepthEnforce)
elif treadDepthEnforce == 'Auto' or obj.TreadDepthEnforce == 0: #elif treadDepth is None and ...
# check landings
if hasLanding:
# check landingDepth
if landingDepth and landingDepth != 'Auto': # i.e. landingDepth == float
reslength = v_proj.Length - landingDepth
elif (landingDepth is None) and obj.LandingDepth:
reslength = v_proj.Length - obj.LandingDepth.Value
landingDepth = obj.LandingDepth.Value
else: # e.g. landingDepth == 'Auto', obj.LandingDepth == 0
reslength = v_proj.Length - width
landingDepth = width
treadDepth = reslength/(numOfSteps-2)
else:
reslength = v_proj.Length
treadDepth = reslength/(numOfSteps-1)
if treadDepthEnforce != 'Auto':
obj.TreadDepth = treadDepth
vLength = DraftVecUtils.scaleTo(v_proj,treadDepth)
else: # if obj.TreadDepthEnforce
obj.TreadDepth = obj.TreadDepthEnforce
vLength = DraftVecUtils.scaleTo(v_proj,obj.TreadDepthEnforce.Value)
# setup vWidth (tread width)
vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),width)
# setup h, hstep (flight height, riser height) : check riserHeightEnforce
if riserHeightEnforce and riserHeightEnforce != 'Auto':
h = riserHeightEnforce * numOfSteps
hstep = riserHeight
#if obj.RiserHeightEnforce == 0:
elif riserHeightEnforce == 'Auto' or obj.RiserHeightEnforce == 0: # elif riserHeightEnforce is None and ...
if round(v.z,Draft.precision()) != 0:
h = v.z
else:
h = height
hstep = h/numOfSteps
if riserHeightEnforce != 'Auto':
obj.RiserHeight = hstep
else: # if obj.RiserHeightEnforce
#h = obj.RiserHeightEnforce.Value * (obj.NumberOfSteps)
h = obj.RiserHeightEnforce.Value * numOfSteps
hstep = obj.RiserHeightEnforce.Value
obj.RiserHeight = hstep
vHeight = Vector(0,0,hstep)
p1 = edge.Vertexes[0].Point
# TODO To add support for multi-segments stairs
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))
p2h = landingStep*hstep
p2 = p1.add(DraftVecUtils.scale(vLength,landingStep-1).add(Vector(0,0,p2h)))
edgeP1p2 = Part.LineSegment(p1,p2).toShape()
if hasLanding:
p3 = p2.add(DraftVecUtils.scaleTo(vLength,landingDepth))
halfTurn = ['HalfTurnLeft', 'HalfTurnRight']
#if obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]:
if ( (flight in halfTurn) or
(flight is None and (obj.Flight in halfTurn)) ):
if (flight == 'HalfTurnLeft') or (obj.Flight in 'HalfTurnLeft') :
#(flight is None and (obj.Flight in 'HalfTurnLeft')) ):
halfTurnLR = 'HalfTurnLeft'
else:
halfTurnLR = 'HalfTurnRight'
if (align == "Left" and halfTurnLR == "HalfTurnLeft") or (align == "Right" and halfTurnLR == "HalfTurnRight"):
p3r = p2
elif (align == "Left" and halfTurnLR == "HalfTurnRight"):
p3r = self.align(p2,"Right",-2*vWidth) # -ve / opposite direction of "Right" - no "Left" in _Stairs.Align()
elif (align == "Right" and halfTurnLR == "HalfTurnLeft"):
p3r = self.align(p2,"Right",2*vWidth)
elif (align == "Center" and halfTurnLR == "HalfTurnLeft"):
p3r = self.align(p2,"Right",vWidth)
elif (align == "Center" and halfTurnLR == "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,numOfSteps-(landingStep+1)).add(Vector(0,0,(numOfSteps-landingStep)*hstep)))
# TODO To support DownSlabThickness, UpSlabThickness per flight
self.makeStraightStairs(obj,Part.LineSegment(p3r,p4r).toShape(),
hstep,obj.UpSlabThickness.Value,
numOfSteps-landingStep,
"HorizontalVerticalCut",None,hgt=h,
vWidth=-vWidth,align=align,
vLength=-vLength,vHeight=vHeight)
else:
#p4 = p3.add(DraftVecUtils.scale(vLength,numOfSteps-(landingStep+1)).add(Vector(0,0,(numOfSteps-landingStep)*hstep)))
p4 = p3.add(DraftVecUtils.scale(vLength,numOfSteps-(
landingStep+1)).add(Vector(0,0,(numOfSteps-
landingStep)*hstep)))
# TODO To support DownSlabThickness, UpSlabThickness per flight
self.makeStraightStairs(obj,Part.LineSegment(p3,p4).toShape(),
hstep,obj.UpSlabThickness.Value,
numOfSteps-landingStep,
"HorizontalVerticalCut",None,hgt=h,
vWidth=vWidth,align=align,
vLength=vLength,vHeight=vHeight)
self.makeStraightLanding(obj,Part.LineSegment(p2,p3).toShape(),
None,True) # TODO numOfSteps=None, callByMakeStraightStairsWithLanding=True
# TODO To support DownSlabThickness, UpSlabThickness per flight
self.makeStraightStairs(obj,edgeP1p2,obj.DownSlabThickness.Value,
hstep,landingStep,None,'toSlabThickness',
vWidth=vWidth,align=align,
vLength=vLength,vHeight=vHeight)
# Remark: work without h=h, review
else:
# TODO To support DownSlabThickness, UpSlabThickness per flight
self.makeStraightStairs(obj,edgeP1p2,obj.DownSlabThickness.Value,
obj.UpSlabThickness.Value,landingStep,None,
None,hgt=h,vWidth=vWidth,align=align,
vLength=vLength,vHeight=vHeight)
# TODO To review
outlineNotUsed, outlineRailL, outlineRailR, vBase2, outlineP1P2ClosedNU, outlineP3P4ClosedNU, NU, pArc, pArc1, pArc2 = self.returnOutlines(obj, edgeP1p2, obj.Align, None, obj.Width,
obj.WidthOfLanding,
obj.TreadThickness, obj.RiserHeight, obj.RailingOffsetLeft,
obj.RailingOffsetRight, obj.RailingHeightLeft,
obj.RailingHeightRight,True)
# TODO To review
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 []