Final Results

The results still need to be supplemented. Currently only the stairs part is completed and still needs:

  1. Linked dimensioning for outer walls
  2. Considering the case of forward split specialty modeling, linked models need to be processed
  3. User custom selection of vertical dimension position
  4. Dimensions text avoiding stair runs
  5. Selection of level and dimension styles
![Image Description](https://cdn.bimath.com/blog/pg/Snipaste_2026-01-04_17-15-08.png)

Align Vertical Dimension Position

Revit Secondary Development Automatically Generate Section Stair Dimensions

Stair Architecture

Stair development first needs to distinguish these names for convenient calling later

Stairs
StairsRun
StairsRiser
Tread

Stair Dimension Problem

Dimensioning needs to obtain Reference and horizontal faces and vertical faces of stair landings or stair runs. Stairs are divided into the above parts, each has a separate Solid that can be picked. My problem also happened here. The Type() returned by picking Geometry alone is ElementType, not a class related to Stairs, so the Reference obtained by picking alone is wrong. Guessing that the Solid.Reference obtained alone is the classification in editing stairs, so there is a phenomenon that there is no error with ID but cannot be displayed. If anyone needs to do automatic stair dimensioning, I will write down my ideas here for everyone’s reference.

Stair Dimension Steps

  1. Get Stair Geometry
  2. Get GeometryInstance
  3. Get Host Element through Instance category and take out Solid under instance to get target face
  4. Use Paramater of Host Element combined with Face to generate dimensions

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

//Clicking on the stair face can identify the stair riser type as Stairs, try to extract from Stairs Geometry
var stairsGeo = stairs.get_Geometry(option);
foreach (GeometryObject o in stairsGeo)
{
if(o is GeometryInstance instance)
{
//At most two Solids. If there is one, it represents only structure or only architecture, indicating this is a single specialty file
if (instance.Symbol.GetType() == typeof(StairsLanding))
{
Solid fakeSolid = null;
foreach (GeometryObject geometryObject in instance.GetInstanceGeometry())
{
if (geometryObject is Solid solid)
{
if (fakeSolid == null)
{
fakeSolid = solid;
}
else
{
//Large volume is structural part, small volume is finish part, can be taken at will here
if (fakeSolid.Volume > solid.Volume)
{
fakeSolid = solid;
}
}
}
}

PlanarFace planarFace = null;
foreach (var face in fakeSolid.Faces)
{
if (face is PlanarFace pf)
{
var faceNormal = pf.FaceNormal;
//Tread vector must be 0, 0, ±1
if (Math.Abs(faceNormal.X - 0) < 0.01 && Math.Abs(faceNormal.Y - 0) < 0.01 && Math.Abs(faceNormal.Z - 1) < 0.01)
{
planarFace = pf;

}

}
}

map.Add((doc.GetElement(planarFace.Reference) as StairsLanding).BaseElevation, planarFace);
}
else if (instance.Symbol.GetType() == typeof(StairsRun))
{
Solid fakeSolid = null;
foreach (GeometryObject geometryObject in instance.GetInstanceGeometry())
{
if (geometryObject is Solid solid)
{
if (fakeSolid == null)
{
fakeSolid = solid;
}
else
{
if (fakeSolid.Volume > solid.Volume)
{
fakeSolid = solid;
}
}
}
}

var run = doc.GetElement(fakeSolid.Faces.get_Item(0).Reference) as StairsRun;
var runTopElevation = run.TopElevation;
var runPathLoop = run.GetStairsPath();
//Run path is generally a line segment, vector is the riser direction
Line runPathDir = null;
foreach (var curve in runPathLoop)
{
runPathDir = curve as Line;
}
var runGeo = run.get_Geometry(option);


var faces = fakeSolid.Faces;
//Get All Faces
//Screen out all horizontal and vertical faces here, but horizontal faces are not needed, keep for now
//If tread quantity of stair run cannot be accurately extracted, tread quantity and height within a single run can be determined by level
PlanarFace referFace1 = null, referFace2 = null, referFace3 = null, referFace4 = null;
SelectTargetFaces(faces, runPathDir, out referFace1, out referFace2);
SelectTargetFaces(faces, runPathDir, out referFace3, out referFace4, 0);


CreateDimension(doc, view, run, referFace1, referFace2);
//CreateVerticalDimension(doc,view,run,referFace3,referFace4);
//var relativeTopElevation = run.get_Parameter(BuiltInParameter.STAIRS_RUN_TOP_ELEVATION);
//var relativeTopValue = relativeTopElevation.AsValueString()/* / 304.8*/;
//var relativeEndElevation = run.get_Parameter(BuiltInParameter.STAIRS_RUN_BOTTOM_ELEVATION);
//var relativeEndValue = relativeEndElevation.AsValueString()/* / 304.8*/;
referFace3 = map.FirstOrDefault(x => Math.Abs(x.Key - run.TopElevation) < 0.01).Value;
referFace4 = map.FirstOrDefault(x => Math.Abs(x.Key - run.BaseElevation) < 0.01).Value;
if (referFace3 == null || referFace4 == null)
{

continue;
}

CreateVerticalDimension(doc, view, run, referFace3, referFace4);

}

}
}

Level Dimension

Automatic dimensioning cannot be without level dimensions. The four XYZ parameters of the API represent origin, leader bend point, leader bend point midpoint, and reference origin respectively. Generally, reference origin and origin can be the same point. As long as the point is on the dimensioned Reference, there is nothing special to pay attention to. Just paste the code directly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
foreach (KeyValuePair<double, PlanarFace> face in map)
{
//Create level
var elevationFace = face.Value;
var elevationFaceRefer = elevationFace.Reference;
var boundingBoxUV = elevationFace.GetBoundingBox();
var point = elevationFace.Evaluate(new UV((boundingBoxUV.Max.U + boundingBoxUV.Min.U) / 2,
(boundingBoxUV.Max.V + boundingBoxUV.Min.V) / 2));
var bendPoint = point.Add(new XYZ(0, 1, 1));//Leader Bend Point
var endPoint = point.Add(new XYZ(0, 2, 1));//Leader End Point

var spotElevation = doc.Create.NewSpotElevation(view, elevationFaceRefer, point,
bendPoint, endPoint, point, true);
}