Skip to content
PyPath

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.

8 min readUpdated October 11, 2026Checked against the Revit API 2025 documentation

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 in OST_ElectricalFixtures.
  • Panels: category OST_ElectricalEquipment, with the name in RBS_ELEC_PANEL_NAME.
  • Circuits: the class ElectricalSystem from Autodesk.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: CableTray and Conduit, also from Autodesk.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:

FixturesPerType.pushbutton/script.py
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)))
Output on the simulated greenhouse complex
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_PARAM and AsValueString(). 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.

WithoutCircuit.pushbutton/script.py
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)))
Output on the simulated greenhouse complex
Without circuit: A1-R2-08 (SON-T 1000W 400V)
1 of 270 fixtures without a circuit
  • GetElectricalSystems() returns a .NET collection. Wrap it in list(...) and you can use it like any Python list: if not circuits, len(circuits), circuits[0].
  • Not every element has an MEPModel; hence the if mep check 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.

AboveBreaker.pushbutton/script.py
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)
Output on the simulated greenhouse complex
OV-A1 circuit 14: 18.76 A on 16 A
OV-A2 circuit 4: 8.20 A on 6 A
  • Rating only exists on power circuits. On a data or fire alarm circuit Revit raises an InvalidOperationException, which is why the script skips everything that is not a PowerCircuit.
  • 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:

script.py
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())
Output on the simulated greenhouse complex
5
A1-R1-01

Write len(circuit.Elements) anyway, and the script stops with an error like this:

Output
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:

Connect.pushbutton/script.py
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))
Output on the simulated greenhouse complex
True
Belichting A1 R2 K2: 5 points, 13.40 A
  • AddToCircuit and RemoveFromCircuit want an ElementSet. With a Python list you get a TypeError.
  • AddToCircuit returns False if a fixture does not fit the circuit, for example because of a different voltage or number of poles.
  • Creating a new circuit with ElectricalSystem.Create takes a List[ElementId] from System.Collections.Generic instead. 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.