Straight-run Revit stair creation in Python problems

Hi, I have the following Grasshopper file with a python scripting component that generates a straight run stair. It takes 1) base and top levels, 2) input linecurve, 3) desired stair type and 4) target riser height. Following the SDK example, I can create a stair without any problems so long as it goes from level 0 to level 1. If I give an input curve and specify it going from level 1 to 2 for example, I will get an error:

Top elevation of the run should not be less that value of “Extend Below Base”.

In my code, I am specifying level 1 at 2000 as the base level. I set the ‘Relative Top Height’ parameter for stair-run object as 2000 because the stair must go from level 1 to level 2 (4000). I believe this value cannot be equal or less than the bottom level elevation and causes the error.

Does anyone know how to get around this? It seems like the script should not be so complicated for a creating a simple straight-run stair. I have another code that works fine for sketched stairs but am just puzzled why a straight run staircase isn’t straightforward to create. Should I just create all stairs with level 0 as the base level and then apply transformations to the stair later? This doesn’t seem very “revit-idiomatic”.

Here is a screenshot of Rhino and Grasshopper:

And the screenshot of the successfully created stair on level 0, but missing the stair that should start from level 1:

This is the snippet of the main function:

def create_straightrun_stairs(l_line, bot_level, top_level, tread_depth, num_risers):
    # creStart stair editing session
    with StairsEditScope(doc, 'New Stairs') as new_stairs_scope:
        new_stair_id = new_stairs_scope.Start(bot_level.Id, top_level.Id)
        
        #Set stair type
        new_stair = doc.GetElement(new_stair_id)
        new_stair.ChangeTypeId(stair_type.Id)

        # Start transaction to edit stairs
        with Transaction(doc, 'Add runs and landing') as stairs_trans:
            stairs_trans.Start()
            
            # Convert from Rhino to Revit geometry
            rl_line = Convert.Geometry.GeometryEncoder.ToCurve(l_line)
            new_run = Architecture.StairsRun.CreateStraightRun(doc, new_stair_id, rl_line, Architecture.StairsRunJustification.Center)
            new_run.EndsWithRiser = True
            
            # Edit stair run 'Relative Top Height' parameter
            top_elev_param = new_run.get_Parameter(BuiltInParameter.STAIRS_RUN_TOP_ELEVATION)
            if top_elev_param:
                _height = top_level.Elevation - bot_level.Elevation # already in internal units so no need to convert
                top_elev_param.Set(_height) 
            
            # Edit stair run properties
            new_stair.ActualTreadDepth = mm_to_internal(tread_depth)
            new_stair.DesiredRisersNumber = num_risers

            stairs_trans.Commit()
        new_stairs_scope.Commit(StairsFailurePreprocessor())
        return new_run

I am using Revit 2022 with the latest Rhino.Inside and am attaching the Grasshopper file here
Bug.gh (25.5 KB)

Hi @jasonlim

I have modified the code as per Rhino08 and RVT 2025. Its working for each level and I didn’t get such error. Try this one. I hope it works for you :slight_smile:

import clr, math
clr.AddReference("RhinoCommon")
clr.AddReference("RhinoInside.Revit")
clr.AddReference("RevitAPI")

from RhinoInside.Revit import Revit
from RhinoInside.Revit.Convert.Geometry import GeometryEncoder
from Autodesk.Revit import DB                       
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
import Rhino

doc = Revit.ActiveDBDocument


mm2ft = lambda mm: DB.UnitUtils.ConvertToInternalUnits(mm, DB.UnitTypeId.Millimeters)
ft2mm = lambda ft: DB.UnitUtils.ConvertFromInternalUnits(ft, DB.UnitTypeId.Millimeters)


class SilentPreprocessor(DB.IFailuresPreprocessor):
    def PreprocessFailures(self, accessor):
        return DB.FailureProcessingResult.Continue


def calc_tread_and_risers(rise_mm, path, lvl_bot, lvl_top, st_type):
    rise_total_mm = ft2mm(lvl_top.Elevation - lvl_bot.Elevation)
    risers = max(2, math.ceil(rise_total_mm / rise_mm))

    if isinstance(path, Rhino.Geometry.Line):
        run_len_mm = path.Length
    else:
        run_len_mm = path.GetLength()

    tread_mm = run_len_mm / (risers - 1)
    return tread_mm, risers


def build_stair(path, lvl_bot, lvl_top, st_type, tread_mm, risers):
    rv_curve = GeometryEncoder.ToCurve(path)
    dz = lvl_bot.Elevation - rv_curve.GetEndPoint(0).Z
    if abs(dz) > 1e-6:
        rv_curve = rv_curve.CreateTransformed(
            DB.Transform.CreateTranslation(DB.XYZ(0, 0, dz)))

    scope = DB.StairsEditScope(doc, "GH Straight Stair")
    stairs_id = scope.Start(lvl_bot.Id, lvl_top.Id)
    stair_el  = doc.GetElement(stairs_id)
    stair_el.ChangeTypeId(st_type.Id)

    tx = DB.Transaction(doc, "Add straight run")
    tx.Start()
    run = DB.Architecture.StairsRun.CreateStraightRun(
            doc, stairs_id, rv_curve,
            DB.Architecture.StairsRunJustification.Center)

    run.EndsWithRiser = True
    stair_el.DesiredTreadDepth   = mm2ft(tread_mm)
    stair_el.DesiredRisersNumber = risers
    tx.Commit()

    scope.Commit(SilentPreprocessor())
    return run

a = None
if trigger:
    tread_mm, risers = calc_tread_and_risers(
        target_riser_height, stair_path, level_bottom, level_top, stair_type)
    try:
        a = build_stair(stair_path, level_bottom, level_top,
                        stair_type, tread_mm, risers)
    except Exception as e:
        ghenv.Component.AddRuntimeMessage(
            RML.Error, "Stair creation failed:\n{}".format(e))
else:
    ghenv.Component.AddRuntimeMessage(
        RML.Remark, "Set ‘trigger’ to True to build the stair.")

Hi Muhammad, firstly thank you for your help.
I edited my code to match yours and also copied your code in its entirety. However, I am getting the error that DesiredTreadDepth is not a property of the Stair object. I checked the Revit API docs and this appears correct for both 2022 and 2025. I am still using Revit 2022 so am unsure if the docs are simply outdated for 2025.

In addition, while looking through the code, I realised that the main difference lies in setting this property and ignoring the ‘Relative Top Height’ parameter. Is this correct? I’m still puzzled because there seems to be no difference logically otherwise. Perhaps it is just a bug with 2022. I will try 2025 and see if it works there.

Image below shows your code, but I had to change DesiredTreadDepth to ActualTreadDepth to be able to run the script and afterwards, I get the same problem.

Hi @jasonlim

The warning appears because the baseline line is much longer than the stair really needs.
Revit must keep adding extra treads to fill that line; each extra tread adds a riser, so the total rise no longer matches the height between the two levels and the run overshoots the top level.

With a correctly sized baseline, Revit can place the right number of risers and the stair lands cleanly on the top level, so the warning disappears.

the following script now gives Revit a baseline of the correct length, it can take care of the rest:

Riser count & riser height
Tread depth
Relative Top Height (set automatically so the last riser lands exactly on the top level)

In short, you now only supply:

the bottom and top levels,
the stair type, and
a direction line.


CreateStairCaseInRevit_BaseLengthCorrection.gh (16.6 KB)