← Back to Knowledge Hub Module 7 → 7.3

7.3 Writing Your First IDS File, Step by Step


Introduction

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.

Two Ways to Get There

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.

From Sentence to Structure, Before Any Code

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.

Building the Specification

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.

Running It: A Pass

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.

Running It Again: A Deliberate Failure

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.

Common Trap: Where Cardinality Actually Goes

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.

Reading the Result

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.

Key Takeaways

  • Every requirement breaks down the same way before any code gets written: an applicability facet deciding which elements, a requirement facet deciding what must be true of them, and cardinality. The Python is one way of writing that structure down, not a separate language for it.
  • Generated Python and its resulting XML express the identical concept in two forms, ids.Entity(name=...) and <ids:entity> are the same applicability facet, written two ways.
  • A specification can be well-formed and still return zero passes, checking a value at the wrong schema location produces a real failure with a structural cause, not a data problem.
  • Cardinality belongs on applicability, not the specification itself, and the author field must be a real email address. Both are easy first-attempt mistakes worth knowing about in advance.

Discuss this lesson

Comments use a free GitHub account — takes under a minute to create, and keeps discussions spam-free and permanently archived.