Additionally 2 Arch_Window bugs were fixed: * If the W1 value was changed the box tracker was not repositioned relative to the cursor. * The WindowColor was not applied because of a typo in the code. De current default color is quite dark BTW. Note that all dimensional values that were not really defaults, but just the last entered values, have been removed from preferences-archdefaults.ui. As a result the layout looks a bit strange. That will be improved in a next PR.
1526 lines
73 KiB
Python
1526 lines
73 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')
|
|
|
|
|
|
def makeStairs(baseobj=None,length=None,width=None,height=None,steps=None,name=None):
|
|
|
|
"""makeStairs([baseobj],[length],[width],[height],[steps],[name]): creates a Stairs
|
|
objects with given attributes."""
|
|
|
|
if not FreeCAD.ActiveDocument:
|
|
FreeCAD.Console.PrintError("No active document. Aborting\n")
|
|
return
|
|
|
|
stairs = []
|
|
additions = []
|
|
label = name if name else translate("Arch","Stairs")
|
|
|
|
def setProperty(obj,length,width,height,steps):
|
|
if length:
|
|
obj.Length = length
|
|
else:
|
|
obj.Length = params.get_param_arch("StairsLength")
|
|
if width:
|
|
obj.Width = width
|
|
else:
|
|
obj.Width = params.get_param_arch("StairsWidth")
|
|
if height:
|
|
obj.Height = height
|
|
else:
|
|
obj.Height = params.get_param_arch("StairsHeight")
|
|
if steps:
|
|
obj.NumberOfSteps = steps
|
|
obj.Structure = "Massive"
|
|
obj.StructureThickness = 150
|
|
obj.DownSlabThickness = 150
|
|
obj.UpSlabThickness = 150
|
|
|
|
obj.RailingOffsetLeft = 60
|
|
obj.RailingOffsetRight = 60
|
|
obj.RailingHeightLeft = 900
|
|
obj.RailingHeightRight = 900
|
|
|
|
if baseobj:
|
|
if not isinstance(baseobj,list):
|
|
baseobj = [baseobj]
|
|
lenSelection = len(baseobj)
|
|
if lenSelection > 1:
|
|
stair = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Stairs")
|
|
stair.Label = label
|
|
_Stairs(stair)
|
|
stairs.append(stair)
|
|
stairs[0].Label = label
|
|
i = 1
|
|
else:
|
|
i = 0
|
|
for baseobjI in baseobj:
|
|
stair = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Stairs")
|
|
stair.Label = label
|
|
_Stairs(stair)
|
|
stairs.append(stair)
|
|
stairs[i].Label = label
|
|
stairs[i].Base = baseobjI
|
|
|
|
if len(baseobjI.Shape.Edges) > 1:
|
|
stepsI = 1 #'landing' if 'multi-edges' currently
|
|
elif steps:
|
|
stepsI = steps
|
|
else:
|
|
stepsI = 20
|
|
setProperty(stairs[i],None,width,height,stepsI)
|
|
|
|
if i > 1:
|
|
additions.append(stairs[i])
|
|
stairs[i].LastSegment = stairs[i-1]
|
|
else:
|
|
if len(stairs) > 1: # i.e. length >1, have a 'master' staircase created
|
|
stairs[0].Base = stairs[1]
|
|
i += 1
|
|
if lenSelection > 1:
|
|
stairs[0].Additions = additions
|
|
|
|
else:
|
|
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Stairs")
|
|
obj.Label = label
|
|
_Stairs(obj)
|
|
setProperty(obj,length,width,height,steps)
|
|
stairs.append(obj)
|
|
|
|
if FreeCAD.GuiUp:
|
|
if baseobj:
|
|
for stair in stairs:
|
|
_ViewProviderStairs(stair.ViewObject)
|
|
else:
|
|
_ViewProviderStairs(obj.ViewObject)
|
|
|
|
if stairs:
|
|
for stair in stairs:
|
|
stair.recompute()
|
|
makeRailing(stairs)
|
|
# return stairs - all other functions expect one object as return value
|
|
return stairs[0]
|
|
else:
|
|
obj.recompute()
|
|
|
|
return obj
|
|
|
|
|
|
def makeRailing(stairs):
|
|
"simple make Railing function testing"
|
|
|
|
def makeRailingLorR(stairs,side="L"):
|
|
for stair in reversed(stairs):
|
|
if side == "L":
|
|
outlineLR = stair.OutlineLeft
|
|
outlineLRAll = stair.OutlineLeftAll
|
|
stairRailingLR = "RailingLeft"
|
|
elif side == "R":
|
|
outlineLR = stair.OutlineRight
|
|
outlineLRAll = stair.OutlineRightAll
|
|
stairRailingLR = "RailingRight"
|
|
if outlineLR or outlineLRAll:
|
|
lrRail = ArchPipe.makePipe(baseobj=None,diameter=0,length=0,placement=None,name=translate("Arch","Railing"))
|
|
if outlineLRAll:
|
|
setattr(stair, stairRailingLR, lrRail)
|
|
break
|
|
elif outlineLR:
|
|
setattr(stair, stairRailingLR, lrRail)
|
|
|
|
if stairs is None:
|
|
sel = FreeCADGui.Selection.getSelection()
|
|
sel0 = sel[0]
|
|
stairs = []
|
|
# TODO currently consider 1st selected object, then would tackle multiple objects?
|
|
if Draft.getType(sel[0]) == "Stairs":
|
|
stairs.append(sel0)
|
|
if Draft.getType(sel0.Base) == "Stairs":
|
|
stairs.append(sel0.Base)
|
|
additions = sel0.Additions
|
|
for additionsI in additions:
|
|
if Draft.getType(additionsI) == "Stairs":
|
|
stairs.append(additionsI)
|
|
else:
|
|
stairs.append(sel[0])
|
|
else:
|
|
print("No Stairs object selected")
|
|
return
|
|
|
|
makeRailingLorR(stairs,"L")
|
|
makeRailingLorR(stairs,"R")
|
|
|
|
|
|
class _CommandStairs:
|
|
|
|
"the Arch Stairs command definition"
|
|
|
|
def GetResources(self):
|
|
|
|
return {'Pixmap' : 'Arch_Stairs',
|
|
'MenuText': QT_TRANSLATE_NOOP("Arch_Stairs","Stairs"),
|
|
'Accel': "S, R",
|
|
'ToolTip': QT_TRANSLATE_NOOP("Arch_Stairs","Creates a flight of stairs")}
|
|
|
|
def IsActive(self):
|
|
|
|
return not FreeCAD.ActiveDocument is None
|
|
|
|
def Activated(self):
|
|
|
|
FreeCAD.ActiveDocument.openTransaction(translate("Arch","Create Stairs"))
|
|
FreeCADGui.addModule("Arch")
|
|
|
|
# a list of 'segment' / 'flight' of stairs
|
|
stairs = []
|
|
additions = []
|
|
|
|
if len(FreeCADGui.Selection.getSelection()) > 0:
|
|
n = []
|
|
nStr = ""
|
|
for obj in FreeCADGui.Selection.getSelection():
|
|
n.append(obj.Name) # would no longer use
|
|
if nStr != "":
|
|
nStr = nStr + ","
|
|
nStr = nStr + "FreeCAD.ActiveDocument." + obj.Name
|
|
FreeCADGui.doCommand("obj = Arch.makeStairs(baseobj=["+nStr+"])")
|
|
|
|
else:
|
|
FreeCADGui.doCommand("obj = Arch.makeStairs(steps="+str(params.get_param_arch("StairsSteps"))+")")
|
|
|
|
FreeCADGui.addModule("Draft")
|
|
for obj in stairs:
|
|
Draft.autogroup(obj) # seems not working?
|
|
|
|
FreeCAD.ActiveDocument.commitTransaction()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
|
|
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:
|
|
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 []
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand('Arch_Stairs',_CommandStairs())
|