Create Switchback Stairs and Landing From Lines With Python Script

Fresh off my success at creating a sketched stair run from lines imported from CAD ( Create Stairs From CAD Link/List of Lines with Python Script?),
I tried to adapt it to a switchback stair. The documentation only shows a sketched stair and straight run, with the landing magically finding it’s location.

Why not do it that way? My goal is to translate exactly what the CAD geometry shows and not let Revit decide. This geometry is taken from a real building, and I want to make sure it isn’t “fudged” just to make Revit happy.

I am getting close (after many Revit crashes, what’s up with that?), but am not able to connect the second stair-run to the landing, as you can see here:

MutantStairs6Elev

I tried a “midLevel” variable approximating the elevation where newRun2 and the landing could meet, but no dice (see hashed out line 53 in code). Also, the requirements for the Landing baseElevation are a little confusing to me:

baseElevation

Type: System Double
Base elevation of the new stairs run. The elevation has following restriction:

  • The base elevation is relative to the base elevation of the stairs. (Relative? Meaning the same height? How is that calculated?)
  • The base elevation will be rounded automatically to a multiple of the riser height. (Are they just saying it will connect automatically to top of the stair-run?)
  • The base elevation should be equal to or greater than half of the riser height. (Are they saying it should be > than 1/2 a single riser, or…?)

At any rate, here is my code:

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Architecture import StairsRun
from Autodesk.Revit.DB.Architecture import StairsLanding
from Autodesk.Revit.DB import CurveLoop
doc = DocumentManager.Instance.CurrentDBDocument	
class StairsFailurePreprocessor( IFailuresPreprocessor ):
	def PreprocessFailures(self, failuresAccessor):
		return FailureProcessingResult.Continue

baseLevel = UnwrapElement(IN[0])
nextLevel = UnwrapElement(IN[1])
b1Curves = IN[2]
r1Curves = IN[3]
p1Curves = IN[4]
b2Curves = IN[5]
r2Curves = IN[6]
p2Curves = IN[7]
elCurves = IN[8]

TransactionManager.Instance.ForceCloseTransaction()

newStairsScope = StairsEditScope(doc, 'New Stairs')
newStairsId = newStairsScope.Start(baseLevel.Id, nextLevel.Id)

trans = Transaction(doc, 'Add Runs and Landings to Stairs')
trans.Start()

bdryCurves1 = list(b1Curves)
riserCurves1 = list(r1Curves)
pathCurves1 = list(p1Curves)

bdryCurves2 = list(b2Curves)
riserCurves2 = list(r2Curves)
pathCurves2 = list(p2Curves)

landingLoop = CurveLoop.Create(elCurves)

r1Count = len(r1Curves)
r2Count = len(r2Curves)

#midLevel = nextLevel.Elevation - baseLevel.Elevation / 2 * r1Count / r2Count

newRun1 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, baseLevel.Elevation, bdryCurves1, riserCurves1, pathCurves1)

newRun2 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, baseLevel.Elevation, bdryCurves2, riserCurves2, pathCurves2)

newLanding = Autodesk.Revit.DB.Architecture.StairsLanding.CreateSketchedLanding(doc, newStairsId, landingLoop, newRun1.TopElevation)

trans.Commit()
newStairsScope.Commit(StairsFailurePreprocessor())

OUT = newStairsId

…and the graph. The smaller python nodes convert Dynamo Lines using .ToRevitType (my thanks to @wouter.hilhorst).

I am not committed to this solution, but it seems like it should work somehow.

Best,

-L

1 Like

Hello @LoRue
you need to use the BaseElevation property of the last instance of StairsLanding to create the next instance of StairsRun (newRun2)
https://www.revitapidocs.com/2015/43d0fed8-4a1f-2cd3-0c01-33b13fec59c5.htm

an example

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Architecture import StairsRun
from Autodesk.Revit.DB.Architecture import StairsLanding

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)
	
class StairsFailurePreprocessor( IFailuresPreprocessor ):
	def PreprocessFailures(self, failuresAccessor):
		return FailureProcessingResult.Continue

baseLevel = UnwrapElement(IN[0])
nextLevel = UnwrapElement(IN[1])
geoDwgList = IN[2] #list with sublits [[x,y], [x,y]] x = geometry protoType / y = DwgLayer
b1Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "boundaryCurvesRun1"]
r1Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "riserCurvesRun1"]
p1Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "stairsPathRun1"]
b2Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "boundaryCurvesRun2"]
r2Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "riserCurvesRun2"]
p2Curves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "stairsPathRun2"]
elCurves = [x[0].ToRevitType() for x in geoDwgList if x[1] == "landing"]

TransactionManager.Instance.ForceCloseTransaction()

newStairsScope = StairsEditScope(doc, 'New Stairs')
newStairsId = newStairsScope.Start(baseLevel.Id, nextLevel.Id)

trans = Transaction(doc, 'Add Runs and Landings to Stairs')
trans.Start()

bdryCurves1 = list(b1Curves)
riserCurves1 = list(r1Curves)
pathCurves1 = list(p1Curves)

bdryCurves2 = list(b2Curves)
riserCurves2 = list(r2Curves)
pathCurves2 = list(p2Curves)

landingLoop = Autodesk.Revit.DB.CurveLoop.Create(elCurves)

newRun1 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, baseLevel.Elevation, bdryCurves1, riserCurves1, pathCurves1)
newLanding = Autodesk.Revit.DB.Architecture.StairsLanding.CreateSketchedLanding(doc, newStairsId, landingLoop, newRun1.TopElevation)
newRun2 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, newLanding.BaseElevation, bdryCurves2, riserCurves2, pathCurves2)

trans.Commit()
newStairsScope.Commit(StairsFailurePreprocessor())

OUT = newStairsId
1 Like

@c.poupin Ah, that makes perfect sense. Brilliant! Didn’t realize TopElevation was an enumeration (still learning). Also, I like how you dealt with .ToRevitType() instead of a separate node. Very slick.

The Python Script | Extract Dwg node seems to make a list derived from the SelectModelElement node input, which is from a CAD file or…lines created in Revit? That’s a great way to test something instead of the hassle of a link.

Here is my updated code. Added the Revit Nodes reference just in case:

import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Architecture import StairsRun
from Autodesk.Revit.DB.Architecture import StairsLanding
from Autodesk.Revit.DB import CurveLoop

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

doc = DocumentManager.Instance.CurrentDBDocument	
class StairsFailurePreprocessor( IFailuresPreprocessor ):
	def PreprocessFailures(self, failuresAccessor):
		return FailureProcessingResult.Continue

baseLevel = UnwrapElement(IN[0])
nextLevel = UnwrapElement(IN[1])
b1Curves = IN[2]
r1Curves = IN[3]
p1Curves = IN[4]
b2Curves = IN[5]
r2Curves = IN[6]
p2Curves = IN[7]
elCurves = IN[8]

TransactionManager.Instance.ForceCloseTransaction()

newStairsScope = StairsEditScope(doc, 'New Stairs')
newStairsId = newStairsScope.Start(baseLevel.Id, nextLevel.Id)

trans = Transaction(doc, 'Add Runs and Landings to Stairs')
trans.Start()

bdryCurves1 = list(b1Curves)
riserCurves1 = list(r1Curves)
pathCurves1 = list(p1Curves)

bdryCurves2 = list(b2Curves)
riserCurves2 = list(r2Curves)
pathCurves2 = list(p2Curves)

landingLoop = CurveLoop.Create(elCurves)

r1Count = len(r1Curves)
r2Count = len(r2Curves)

#midLevel = nextLevel.Elevation - baseLevel.Elevation / 2 * r1Count / r2Count

newRun1 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, baseLevel.Elevation, bdryCurves1, riserCurves1, pathCurves1)
newLanding = Autodesk.Revit.DB.Architecture.StairsLanding.CreateSketchedLanding(doc, newStairsId, landingLoop, newRun1.TopElevation)
newRun2 = Autodesk.Revit.DB.Architecture.StairsRun.CreateSketchedRun(doc, newStairsId, newLanding.BaseElevation, bdryCurves2, riserCurves2, pathCurves2)
trans.Commit()
newStairsScope.Commit(StairsFailurePreprocessor())

OUT = newStairsId

Well done, Sir!

-L

1 Like