Files
willtools-demo/docs/revit-api-cheatsheet.md
T
will 8e6ee31596
ci / test (push) Successful in 6m18s
WillTools 2026-09-09 -- clean snapshot of the development repo for review
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.
2026-09-09 08:32:15 -05:00

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)`.