An IDS file doesn't start as XML. It starts as a sentence, the same kind of sentence covered in the first lesson of this module: "every reinforcing bar must have a diameter." This lesson is the path from that sentence to a real, running check, watching one pass and one deliberately fail, not a syntax reference to memorise.
An IDS file can be hand-written directly against the schema, or generated through a tool. Both are legitimate. Hand-writing gives full control but is easy to get wrong, a missing namespace, a misplaced attribute, and the file simply won't validate. Generating it through a library removes most of that risk, since the tool only ever produces schema-valid output.
This lesson uses the generated path, specifically ifctester, an open-source library built on top of IfcOpenShell. It's not the only correct choice, it's the one with an actual working example to walk through end to end.
Before writing anything, a requirement breaks down into the same shape every time:
Requirement: every reinforcing bar must have a diameter.
Applicability facet: Entity, IfcReinforcingBar, this decides which elements the check even looks at.
Requirement facet: Attribute, NominalDiameter, this decides what must be true of them.
Cardinality: required.
That breakdown is the actual thinking IDS asks for. The Python that follows is just one way of writing it down.
Step 1, file metadata:
from ifctester import ids
specs = ids.Ids(
title="Rebar Requirements",
author="engineer@example.com",
date="2026-07-08"
)
Step 2, the specification and its applicability:
spec = ids.Specification(
name="Rebar must have NominalDiameter",
ifcVersion=["IFC2X3"]
)
spec.applicability.append(ids.Entity(name="IFCREINFORCINGBAR"))
Step 3, the requirement:
spec.requirements.append(
ids.Attribute(name="NominalDiameter", cardinality="required")
)
specs.specifications.append(spec)
Step 4, save:
specs.to_xml("rebar_check.ids")
Worth being precise about what just happened: this Python isn't a different way of expressing IDS, it's generating the actual XML representation of the same concepts from the breakdown above. Line by line:
spec.applicability.append(ids.Entity(name="IFCREINFORCINGBAR"))
produces
<ids:applicability>
<ids:entity><ids:name><ids:simpleValue>IFCREINFORCINGBAR</ids:simpleValue></ids:name></ids:entity>
</ids:applicability>
and
ids.Attribute(name="NominalDiameter", cardinality="required")
produces
<ids:attribute cardinality="required">
<ids:name><ids:simpleValue>NominalDiameter</ids:simpleValue></ids:name>
</ids:attribute>
Same concepts, same structure, two different ways of writing them.
python -m ifctester rebar_check.ids project.ifc -r Html -o report.html
Run against a real Revit 2021 IFC2X3 project with 1,252 reinforcing bars, this specification returns 1,252 of 1,252 passing. Every bar in the file carries a diameter.
Change one thing. Instead of checking NominalDiameter as an Attribute, check BarLength as a Property:
spec.requirements.append(
ids.Property(
propertySet="Pset_ReinforcingBarCommon",
baseName="BarLength",
cardinality="required"
)
)
Run it against the same file. Result: 0 of 1,252 passed.
Nothing about the file changed between the two runs. What changed was where the specification looked. BarLength in this IFC2X3 export sits as a direct Attribute on IfcReinforcingBar, not inside Pset_ReinforcingBarCommon. The Property facet is syntactically correct, the specification is well-formed, and it still fails completely, because it's asking a question about a location the data was never written to. This is the exact failure shape covered in the previous lesson, seen this time from the writing side rather than the reading side: a specification can be perfectly built and still return zero passes if it's pointed at the wrong place.
One easy way to end up with an invalid file, worth knowing before it happens rather than after: cardinality, minOccurs and maxOccurs, belongs on applicability, not on the specification itself. Requirement-level cardinality is separate, expressed through cardinality="required" on the individual facet, exactly as it appears above. This isn't arbitrary, buildingSMART moved this attribute during the schema's finalisation specifically because the earlier placement could produce invalid configurations. The author field has its own trap too: it must be a real email address, not a name, author="engineer@example.com" is correct, a plain name will fail schema validation.
Both runs above produce reports in exactly the same shape covered in the previous lesson: a specification name, a pass or fail count, and, for anything short of a full pass, which elements failed. The report doesn't know or care whether the specification that produced it was hand-written or generated, or whether it passed on the first attempt or the second. Debugging a failed specification means going back to the same three-part breakdown this lesson started with, applicability, requirement, cardinality, and checking each one against where the data actually lives in the file, not assuming the file is wrong.
ids.Entity(name=...) and <ids:entity> are the same applicability facet, written two ways.Comments use a free GitHub account — takes under a minute to create, and keeps discussions spam-free and permanently archived.