8e6ee31596
ci / test (push) Successful in 6m18s
History-free snapshot of the private development repository at cbe45c5 (2026-09-09). Code, docs and public catalogue only; no plant or job knowledge layers, no client or job names (tools/scrub.py clean). The intro deck is left out of this snapshot because its slide images could not be checked for job drawings.
262 lines
7.5 KiB
Markdown
262 lines
7.5 KiB
Markdown
# Revit 2025 API cheatsheet (IronPython / pyRevit)
|
|
|
|
Copy-paste patterns that are known to work on Revit 2025. Everything assumes:
|
|
|
|
```python
|
|
from pyrevit import revit, forms, script
|
|
from Autodesk.Revit import DB, UI
|
|
from Autodesk.Revit.DB import *
|
|
doc, uidoc = revit.doc, revit.uidoc
|
|
```
|
|
|
|
---
|
|
|
|
## Collecting
|
|
|
|
```python
|
|
# all instances of a category
|
|
walls = FilteredElementCollector(doc)\
|
|
.OfCategory(BuiltInCategory.OST_Walls)\
|
|
.WhereElementIsNotElementType()\
|
|
.ToElements()
|
|
|
|
# by .NET class
|
|
levels = FilteredElementCollector(doc).OfClass(Level).ToElements()
|
|
|
|
# types, not instances
|
|
wall_types = FilteredElementCollector(doc)\
|
|
.OfCategory(BuiltInCategory.OST_Walls)\
|
|
.WhereElementIsElementType().ToElements()
|
|
|
|
# only what is visible in a view
|
|
in_view = FilteredElementCollector(doc, doc.ActiveView.Id)\
|
|
.OfCategory(BuiltInCategory.OST_Doors)\
|
|
.WhereElementIsNotElementType().ToElements()
|
|
|
|
# elements on a level
|
|
lvl_filter = ElementLevelFilter(level.Id)
|
|
on_level = FilteredElementCollector(doc).WherePasses(lvl_filter).ToElements()
|
|
|
|
# by parameter value (fast, server-side)
|
|
rule = ParameterFilterRuleFactory.CreateEqualsRule(
|
|
ElementId(BuiltInParameter.ALL_MODEL_MARK), 'A-01')
|
|
by_mark = FilteredElementCollector(doc)\
|
|
.WherePasses(ElementParameterFilter(rule)).ToElements()
|
|
```
|
|
|
|
`.ToElements()` materialises; `.ToElementIds()` is cheaper if ids are enough.
|
|
Collectors are lazy — iterate once.
|
|
|
|
---
|
|
|
|
## Parameters
|
|
|
|
```python
|
|
prm = el.LookupParameter('Comments') # by name (locale!)
|
|
prm = el.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS) # robust
|
|
|
|
# read, by storage type
|
|
{StorageType.String: prm.AsString,
|
|
StorageType.Integer: prm.AsInteger,
|
|
StorageType.Double: prm.AsDouble,
|
|
StorageType.ElementId: prm.AsElementId}[prm.StorageType]()
|
|
|
|
prm.AsValueString() # what the UI shows, units included
|
|
|
|
# write (inside a transaction)
|
|
prm.Set('new value')
|
|
```
|
|
|
|
Prefer `BuiltInParameter` over `LookupParameter` — parameter *names* are
|
|
localised and change between templates. `vibe.p()` / `vibe.set_p()` accept
|
|
either.
|
|
|
|
Type vs instance: `doc.GetElement(el.GetTypeId())` gets the type; type
|
|
parameters live there, not on the instance.
|
|
|
|
---
|
|
|
|
## Transactions
|
|
|
|
```python
|
|
t = Transaction(doc, 'Do the thing')
|
|
t.Start()
|
|
try:
|
|
...
|
|
t.Commit()
|
|
except Exception:
|
|
t.RollBack()
|
|
raise
|
|
|
|
# pyRevit equivalent
|
|
with revit.Transaction('Do the thing'):
|
|
...
|
|
|
|
# local helper
|
|
from vibe import tx
|
|
with tx('Do the thing'):
|
|
...
|
|
|
|
# several transactions, one undo step
|
|
with TransactionGroup(doc, 'Batch') as g: # or vibe.txg('Batch')
|
|
...
|
|
```
|
|
|
|
Rules: no nested `Transaction` (use `SubTransaction`); a transaction cannot span
|
|
a modal dialog; anything that changes the model — including creating a view,
|
|
setting a parameter, deleting — needs one.
|
|
|
|
---
|
|
|
|
## Units — internal is always decimal feet
|
|
|
|
```python
|
|
UnitUtils.ConvertToInternalUnits(3000, UnitTypeId.Millimeters) # mm -> feet
|
|
UnitUtils.ConvertFromInternalUnits(length, UnitTypeId.Meters) # feet -> m
|
|
UnitUtils.ConvertFromInternalUnits(area, UnitTypeId.SquareMeters)
|
|
|
|
# what the document is set to display
|
|
doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId()
|
|
```
|
|
|
|
`DisplayUnitType` / `UnitType` were removed in Revit 2022. Use `ForgeTypeId`
|
|
(`UnitTypeId.*`, `SpecTypeId.*`).
|
|
|
|
---
|
|
|
|
## Creating elements
|
|
|
|
```python
|
|
# level
|
|
lvl = Level.Create(doc, ft(4000))
|
|
|
|
# wall
|
|
line = Line.CreateBound(XYZ(0, 0, 0), XYZ(ft(5000), 0, 0))
|
|
wall = Wall.Create(doc, line, wall_type.Id, level.Id, ft(3000), 0.0, False, True)
|
|
|
|
# family instance
|
|
symbol = FilteredElementCollector(doc)\
|
|
.OfCategory(BuiltInCategory.OST_Doors)\
|
|
.WhereElementIsElementType().FirstElement()
|
|
if not symbol.IsActive:
|
|
symbol.Activate(); doc.Regenerate() # required before first placement
|
|
inst = doc.Create.NewFamilyInstance(
|
|
XYZ(0, 0, 0), symbol, level, Structure.StructuralType.NonStructural)
|
|
|
|
# hosted on a wall
|
|
inst = doc.Create.NewFamilyInstance(
|
|
point, symbol, wall, level, Structure.StructuralType.NonStructural)
|
|
|
|
# text note
|
|
TextNote.Create(doc, view.Id, XYZ(0, 0, 0), 'hello', text_type.Id)
|
|
|
|
# delete
|
|
doc.Delete(el.Id) # returns the ids actually deleted
|
|
```
|
|
|
|
`symbol.Activate()` + `doc.Regenerate()` before the first placement of an
|
|
inactive symbol, or `NewFamilyInstance` throws.
|
|
|
|
---
|
|
|
|
## Views, sheets, schedules
|
|
|
|
```python
|
|
views = FilteredElementCollector(doc).OfClass(View)\
|
|
.WhereElementIsNotElementType()
|
|
plans = [v for v in views if v.ViewType == ViewType.FloorPlan and not v.IsTemplate]
|
|
|
|
sheets = FilteredElementCollector(doc).OfClass(ViewSheet)\
|
|
.WhereElementIsNotElementType().ToElements()
|
|
|
|
sheet = ViewSheet.Create(doc, titleblock_type_id)
|
|
sheet.SheetNumber, sheet.Name = 'A-101', 'Ground Floor'
|
|
Viewport.Create(doc, sheet.Id, view.Id, XYZ(1, 1, 0))
|
|
|
|
uidoc.ActiveView = some_view # switch the active view
|
|
uidoc.Selection.SetElementIds(List[ElementId]([el.Id])) # needs System.Collections.Generic
|
|
```
|
|
|
|
---
|
|
|
|
## Rooms, geometry, location
|
|
|
|
```python
|
|
rooms = FilteredElementCollector(doc)\
|
|
.OfCategory(BuiltInCategory.OST_Rooms)\
|
|
.WhereElementIsNotElementType().ToElements()
|
|
rooms = [r for r in rooms if r.Area > 0] # unplaced rooms have Area 0
|
|
|
|
loc = el.Location
|
|
if isinstance(loc, LocationPoint):
|
|
pt = loc.Point
|
|
elif isinstance(loc, LocationCurve):
|
|
crv = loc.Curve; start, end = crv.GetEndPoint(0), crv.GetEndPoint(1)
|
|
|
|
bb = el.get_BoundingBox(None) # None = model coords; a view = view crop
|
|
center = (bb.Min + bb.Max) / 2
|
|
|
|
opts = Options()
|
|
opts.ComputeReferences, opts.DetailLevel = True, ViewDetailLevel.Fine
|
|
geo = el.get_Geometry(opts)
|
|
solids = [g for g in geo if isinstance(g, Solid) and g.Volume > 0]
|
|
```
|
|
|
|
---
|
|
|
|
## .NET interop from IronPython
|
|
|
|
```python
|
|
from System.Collections.Generic import List
|
|
ids = List[ElementId]([e.Id for e in els]) # APIs that want ICollection<ElementId>
|
|
|
|
import clr
|
|
clr.AddReference('RevitAPI')
|
|
clr.AddReference('System.Windows.Forms')
|
|
|
|
# out parameters come back as a tuple
|
|
ok, value = some_api.TryGetValue(key)
|
|
```
|
|
|
|
Revit APIs typed `ICollection<ElementId>` will not accept a Python list —
|
|
wrap it in `List[ElementId]`.
|
|
|
|
---
|
|
|
|
## pyRevit UI
|
|
|
|
```python
|
|
forms.alert('Done', title='Tool', warn_icon=False)
|
|
forms.alert('Nothing selected', exitscript=True)
|
|
choice = forms.SelectFromList.show(items, multiselect=True, button_name='OK')
|
|
text = forms.ask_for_string(default='A-', prompt='Sheet prefix')
|
|
path = forms.pick_file(file_ext='xlsx')
|
|
level = forms.select_levels()
|
|
sheets = forms.select_sheets()
|
|
|
|
output = script.get_output()
|
|
output.print_md('### Heading')
|
|
output.print_table(table_data=rows, columns=['A', 'B'])
|
|
output.linkify(el.Id) # clickable, selects the element in Revit
|
|
with forms.ProgressBar(title='{value}/{max_value}') as pb:
|
|
for i, el in enumerate(els):
|
|
pb.update_progress(i + 1, len(els))
|
|
```
|
|
|
|
---
|
|
|
|
## Gotchas that cost hours
|
|
|
|
- Parameter names are **localised**; `BuiltInParameter` is not.
|
|
- `el.Name` throws on some element types — wrap in try/except.
|
|
- Elements from a **linked** model are read-only; get them via
|
|
`RevitLinkInstance.GetLinkDocument()`, and transform coordinates with
|
|
`link_instance.GetTotalTransform()`.
|
|
- Deleting while iterating a collector corrupts the iteration — materialise ids
|
|
first, then delete.
|
|
- `doc.Regenerate()` after geometry changes if you need to read the result in the
|
|
same transaction.
|
|
- Worksharing: check `el.Pinned` and element ownership before batch edits.
|
|
- A `FilteredElementCollector` over a view id also returns hidden-in-view
|
|
elements unless you check `el.IsHidden(view)`.
|