Count lighting fixtures and check electrical circuits in Revit with Python (pyRevit)
Three short pyRevit scripts for electrical modellers: count fixtures per type, find fixtures without a circuit and spot circuits above their breaker rating. Plus the trap everyone falls into: an ElementSet is not a list.
Short answer: get the lighting fixtures with a FilteredElementCollector on BuiltInCategory.OST_LightingFixtures. The circuits of a fixture come from fixture.MEPModel.GetElectricalSystems(); if that is empty, the fixture is not on any circuit. Circuits themselves are ElectricalSystem elements: compare ApparentCurrent with Rating and you see which circuit is above its breaker rating. Watch out: circuit.Elements is an ElementSet, not a list; count it with .Size.
Where electrical lives in the Revit API
For electrical modellers almost everything revolves around four kinds of elements:
- Lighting fixtures: category
OST_LightingFixtures. Receptacles and other connection points are inOST_ElectricalFixtures. - Panels: category
OST_ElectricalEquipment, with the name inRBS_ELEC_PANEL_NAME. - Circuits: the class
ElectricalSystemfromAutodesk.Revit.DB.Electrical. A circuit knows its panel (PanelName), its number (CircuitNumber), its description (LoadName), the current (ApparentCurrent) and the breaker rating (Rating). - Cable trays and conduit:
CableTrayandConduit, also fromAutodesk.Revit.DB.Electrical.
Like lengths, power and voltage are stored in internal units in Revit. So always read them with UnitUtils.ConvertFromInternalUnits and UnitTypeId.VoltAmperes or UnitTypeId.Volts, never as a bare number. The examples below run on a simulated greenhouse complex with three sections of grow lighting, 270 fixtures and four panels. The model uses Dutch names: "Belichting" is lighting, "kring" is circuit and "OV" is a sub-panel.
Count fixtures per type
The first question is nearly always: how many fixtures of which type, and how much load is that? This script counts them and adds up the apparent load per type:
from Autodesk.Revit.DB import *
doc = __revit__.ActiveUIDocument.Document
fixtures = (FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_LightingFixtures)
.WhereElementIsNotElementType()
.ToElements())
per_type = {}
for fixture in fixtures:
type_name = fixture.get_Parameter(
BuiltInParameter.ELEM_TYPE_PARAM).AsValueString()
va = fixture.get_Parameter(
BuiltInParameter.RBS_ELEC_APPARENT_LOAD).AsDouble()
va = UnitUtils.ConvertFromInternalUnits(va, UnitTypeId.VoltAmperes)
count, total = per_type.get(type_name, (0, 0.0))
per_type[type_name] = (count + 1, total + va)
for type_name in sorted(per_type):
count, total = per_type[type_name]
print("{}: {} fixtures, {:.1f} kVA".format(
type_name, count, total / 1000))
print("Total: {} fixtures".format(len(fixtures)))LED 520W 230V: 89 fixtures, 47.2 kVA
LED 645W 400V: 91 fixtures, 59.7 kVA
SON-T 1000W 400V: 90 fixtures, 96.5 kVA
Total: 270 fixtures- Read the type name with
ELEM_TYPE_PARAMandAsValueString(). That works in any language version of Revit, unlike looking a parameter up by its visible name. - A dictionary with
.get(key, default)is the shortest way to add up per type. - Notice: 91 fixtures "LED 645W 400V", while each section has 90. In the practice model one of them sits in the wrong section. A count finds this kind of deviation sooner than your eye does.
Find fixtures without a circuit
A fixture that is not on any circuit does not count towards the panel load and is missing from the circuit list. GetElectricalSystems() on the fixture's MEPModel returns its circuits; if that collection is empty, you have found one.
from Autodesk.Revit.DB import *
doc = __revit__.ActiveUIDocument.Document
fixtures = (FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_LightingFixtures)
.WhereElementIsNotElementType()
.ToElements())
without_circuit = []
for fixture in fixtures:
mep = fixture.MEPModel
circuits = list(mep.GetElectricalSystems()) if mep else []
if not circuits:
without_circuit.append(fixture)
for fixture in without_circuit:
mark = fixture.get_Parameter(
BuiltInParameter.ALL_MODEL_MARK).AsString()
type_name = fixture.get_Parameter(
BuiltInParameter.ELEM_TYPE_PARAM).AsValueString()
print("Without circuit: {} ({})".format(mark, type_name))
print("{} of {} fixtures without a circuit".format(
len(without_circuit), len(fixtures)))Without circuit: A1-R2-08 (SON-T 1000W 400V)
1 of 270 fixtures without a circuitGetElectricalSystems()returns a .NET collection. Wrap it inlist(...)and you can use it like any Python list:if not circuits,len(circuits),circuits[0].- Not every element has an
MEPModel; hence theif mepcheck first. - To check receptacles as well, use the same loop with
OST_ElectricalFixtures.
Circuits above their breaker rating
Every circuit knows its current and its breaker. ApparentCurrent is the current in amps: apparent load divided by the voltage, and for a 3-pole circuit divided by √3 times the voltage. Rating is the breaker rating in amps. If the current is higher than the rating, something is wrong.
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Electrical import ElectricalSystem
from Autodesk.Revit.DB.Electrical import ElectricalSystemType
doc = __revit__.ActiveUIDocument.Document
overloaded = []
for circuit in FilteredElementCollector(doc).OfClass(ElectricalSystem):
if circuit.SystemType != ElectricalSystemType.PowerCircuit:
continue
if circuit.ApparentCurrent > circuit.Rating:
overloaded.append("{} circuit {}: {:.2f} A on {:.0f} A".format(
circuit.PanelName, circuit.CircuitNumber,
circuit.ApparentCurrent, circuit.Rating))
for line in sorted(overloaded):
print(line)OV-A1 circuit 14: 18.76 A on 16 A
OV-A2 circuit 4: 8.20 A on 6 ARatingonly exists on power circuits. On a data or fire alarm circuit Revit raises anInvalidOperationException, which is why the script skips everything that is not aPowerCircuit.- Many offices use a stricter rule of their own, such as "at most 80 % of the breaker rating" or "at most five fixtures per circuit". Put that limit in a variable at the top of the script, so everyone can see which rule the button checks.
- This is a check against your own rules, not a code-compliant design calculation. Do the final cable sizing, voltage drop and selectivity in your electrical design software.
ElementSet or list: the IronPython trap
The fixtures on a circuit are in circuit.Elements. It looks like a list, but it is an ElementSet: a collection from the Revit API. You can loop over it, but len() and [0] do not work in pyRevit (IronPython). Count with .Size, or turn it into a list first:
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Electrical import ElectricalSystem
doc = __revit__.ActiveUIDocument.Document
circuit = (FilteredElementCollector(doc)
.OfClass(ElectricalSystem)
.FirstElement())
members = circuit.Elements # an ElementSet, not a Python list
print(members.Size) # count with Size
first = list(members)[0] # make a list first, then [0]
print(first.get_Parameter(BuiltInParameter.ALL_MODEL_MARK).AsString())5
A1-R1-01Write len(circuit.Elements) anyway, and the script stops with an error like this:
TypeError: object of type 'ElementSet' has no len()The same holds the other way round: methods that change a circuit want an ElementSet, not a Python list. This script puts the fixture without a circuit on the circuit where it belongs:
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Electrical import ElectricalSystem
doc = __revit__.ActiveUIDocument.Document
circuits = {}
for circuit in FilteredElementCollector(doc).OfClass(ElectricalSystem):
circuits[circuit.LoadName] = circuit
target = circuits["Belichting A1 R2 K2"]
to_add = ElementSet()
for fixture in (FilteredElementCollector(doc)
.OfCategory(BuiltInCategory.OST_LightingFixtures)
.WhereElementIsNotElementType()):
if not list(fixture.MEPModel.GetElectricalSystems()):
to_add.Insert(fixture)
t = Transaction(doc, "Connect fixtures")
t.Start()
ok = target.AddToCircuit(to_add) # an ElementSet, not a Python list
t.Commit()
print(ok)
print("{}: {} points, {:.2f} A".format(
target.LoadName, target.Elements.Size, target.ApparentCurrent))True
Belichting A1 R2 K2: 5 points, 13.40 AAddToCircuitandRemoveFromCircuitwant anElementSet. With a Python list you get aTypeError.AddToCircuitreturnsFalseif a fixture does not fit the circuit, for example because of a different voltage or number of poles.- Creating a new circuit with
ElectricalSystem.Createtakes aList[ElementId]fromSystem.Collections.Genericinstead. Two methods, two kinds of collection: when in doubt, check the documentation at revitapidocs.com. - Changes are only allowed inside a transaction. Why, and what happens if you forget it, is in "Attempt to modify the model outside of transaction".
From script to button
Each script above becomes a button on the ribbon when you save it as script.py in a Name.pushbutton folder in your pyRevit extension; how that works is in pyRevit tutorial: your first button. Three habits make such a button useful for the whole office:
- A settings block at the top with type names, parameter names and limits. Whoever uses the button on another project only edits that block.
- No f-strings. pyRevit uses IronPython 2.7 by default; write
"{}".format(value). - Schedules to Excel as a CSV file. In countries that use a decimal comma, a semicolon as separator makes Excel open it straight into columns.
This is work that belongs next to your electrical package: it counts, checks and reports from the Revit model, following your own rules. Calculating to the standard stays in your electrical design software.