Public demo snapshot of an in-house pyRevit extension for precast concrete shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel handling solver (PCI Chapter 8). - No client, job, plant or product data: knowledge layers ship as structural templates only. - Ticket QC's model-read escalation targets a LAN model server endpoint (configurable), not a cloud provider. - Built from the private development repo via tools/make_dist.py.
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
# 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)`.
|
||||
Reference in New Issue
Block a user