Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ifcdiff

Two IFC model versions in, a human-readable changelog out.

Which walls moved and by how much. Which properties changed. Quantity deltas by class — and, crucially, what caused them. What was added and deleted. As markdown for a pull request, HTML for a CI artifact, JSON for a script, or BCF 2.1 for a coordination tool.

Pure standard library. No geometry kernel, no server, no wheels to install.

ifcdiff old.ifc new.ifc
## Headline

- IfcSlab NetVolume is up +6.2% (+1.50 m³): +1.50 from 1 edited element.
- IfcWall NetVolume is up +1.4% (+0.24 m³): +2.40 from new elements,
  -2.40 from removed elements, +0.24 from 1 edited element.

## Moved (2)

| Element      | Class   | Location                  | Change                                  |
| ------------ | ------- | ------------------------- | --------------------------------------- |
| Wall — core C| IfcWall | Site / Building / Level 1 | moved 3,500 mm (Z−3,500 mm) rotated 90° |
| Wall — east  | IfcWall | Site / Building / Level 1 | moved 250 mm (X+250 mm)                 |

Read this before you use it: there is another ifcdiff

IfcOpenShell ships a tool with the same name. pip install ifcdiff gets you that one — a mature, GlobalId-based comparator with a real geometry kernel behind it. If you need boundary-representation equivalence ("is this extrusion the same solid as that mesh?"), use IfcOpenShell's, not this one.

This project exists because that tool answers a different question. It emits diff.json: a machine-readable list of which GlobalIds changed. What it does not do is tell a project engineer why the concrete volume went up 8% this issue — and that is the question someone asks on every project.

IfcOpenShell ifcdiff this ifcdiff
Install ifcopenshell (~100 MB, native geometry kernel) stdlib only, zero dependencies
Geometry comparison resolves breps; tolerance-aware representation fingerprint only (see limits)
Output JSON markdown, HTML, JSON, BCF 2.1
Quantity roll-up by class / material / storey / type, decomposed by cause
Movement magnitude changed / unchanged distance in mm, per-axis, rotation in degrees
GUID instability assumes GlobalIds are stable falls back to name/tag/position matching
CI gate --fail-on 'qty:NetVolume>5%', exit code 1

They are complements. Use IfcOpenShell's when geometry is the question; use this one when the changelog is.

The PyPI distribution here is ifcdiff-cli to avoid the collision. The command is still ifcdiff.


Install

pip install ifcdiff-cli

Or just vendor it — it is stdlib-only, so git clone and python -m ifcdiff works with no install at all.

Usage

# markdown changelog to stdout
ifcdiff old.ifc new.ifc

# several reports at once; the format comes from the extension
ifcdiff old.ifc new.ifc -o report.md -o report.html -o report.json -o issues.bcfzip

# why did the concrete move? group the roll-up by material
ifcdiff old.ifc new.ifc --by material

# fail the build when structural quantities drift or elements vanish
ifcdiff old.ifc new.ifc -q --fail-on 'qty:NetVolume>5%' --fail-on 'deleted>0'

Exit codes: 0 clean, 1 a gate tripped, 2 bad input or usage.

What it detects

Kind Meaning
added / deleted present in only one model
moved world placement changed — distance in mm, per-axis delta, rotation in degrees
geometry the representation subgraph changed shape (placement excluded)
quantity an IfcElementQuantity value changed, with percentage
property any IfcPropertySet value added, removed or changed
material material or layer thickness changed
type a different IfcTypeObject is assigned
container re-hosted to another storey or spatial element
attribute Name, Description, ObjectType, Tag or PredefinedType
classification IfcClassificationReference changed
reclassified the IFC entity itself changed, e.g. IfcWallIfcSlab

The quantity roll-up

The headline is the point of the tool:

IfcWall NetVolume is up +1.4% (+0.24 m³): +2.40 from new elements,
-2.40 from removed elements, +0.24 from 1 edited element.

The three causes always sum exactly to the delta, so the number is auditable — you can see whether a jump came from new elements, deleted ones, or the same elements growing. Group with --by class|material|storey|type.

All values are normalised to SI (m, m², m³) from each file's own units, so a millimetre export and a metre export of the same building compare as identical.

When GlobalIds are not stable

Some authoring tools regenerate GUIDs on export, which naively reads as "every element was deleted and re-added". --match controls the fallback:

  • guid — GlobalId only, like IfcOpenShell's.
  • strict (default) — then class + name + tag + storey.
  • loose — then world position, then shape fingerprint.

Fallback pairings only apply when they are unambiguously 1:1, and every change found that way is flagged with ⚠︎ in the report so you can distrust it.

BCF output

ifcdiff old.ifc new.ifc -o issues.bcfzip                      # one topic per element
ifcdiff old.ifc new.ifc -o issues.bcfzip --bcf-group kind     # "42 walls moved"

BCF 2.1 with a viewpoint per topic: the element pre-selected and an orthogonal camera aimed at its world position. Topic GUIDs are UUIDv5-derived and the timestamp defaults to the new file's mtime (SOURCE_DATE_EPOCH honoured), so the archive is byte-for-byte reproducible — re-running in CI does not produce a new set of topics.

CI

- run: |
    pip install ifcdiff-cli
    ifcdiff baseline.ifc "$MODEL" -o report.md -o issues.bcfzip \
      --fail-on 'deleted>0' --fail-on 'qty:NetVolume>5%'

Gate metrics: any change kind, plus changes, modified, unchanged, moved_mm (largest single movement) and qty:<Name> (absolute, or percent with a trailing %).

Python API

from ifcdiff import load, compare, Options, render_markdown, write_bcf

result = compare(load("old.ifc"), load("new.ifc"), Options(match="loose"))

print(result.counts)                        # {'added': 1, 'moved': 2, ...}
for change in result.by_kind("moved"):
    print(change.name, change.detail["distance_mm"])

volume = result.rollup_for("NetVolume")
print(volume.pct, volume.from_added, volume.from_changed)

print(render_markdown(result))
write_bcf(result, "issues.bcfzip")

Limits — read these

This is an honest list, not a roadmap.

  • No boundary-representation resolution. Geometry comparison is a fingerprint of the representation subgraph. If one file describes a wall as an extrusion and the other as a mesh, that is reported as a shape change even when the two resolve to the same solid. IfcOpenShell's ifcdiff handles this; this one deliberately does not.
  • No implicit geometry evaluation. Openings, clipping and boolean results are not subtracted. Quantities come from IfcElementQuantity as authored — if the exporter did not write base quantities, there is nothing to roll up.
  • IfcGridPlacement is not resolved (it needs the grid), so such elements are treated as world-origin and only their shape fingerprint speaks.
  • Speed. Roughly 2 MB/s of IFC on one core; an 8 MB pair diffs in ~3.5 s. Large brep-heavy models are dominated by the shape fingerprint — pass --no-geometry to skip it when you only care about data changes.
  • Type-borne property sets are folded into each occurrence by default and marked (type); disable with --no-inherited-psets.

Development

make test        # 157 tests, stdlib only
make demo        # diff the bundled example models
make examples    # regenerate examples/*.ifc and the sample reports

tests/ifcgen.py is a small IFC4 writer used to build fixtures. The tests run against genuinely valid IFC-SPF — units, placement chains, property sets, base quantities and material layer sets — rather than mocks, and examples/old.ifc and examples/new.ifc come out of it and open in any viewer.

Prior art

  • IfcOpenShell ifcdiff — the established CLI; geometry kernel, JSON output.
  • Speckle — version diffing in a hosted viewer; excellent if your workflow already pushes to a Speckle server.
  • ThatOpen / web-ifc and viewers built on it — in-browser visual comparison, colour-coded in 3D.
  • Solibri Model Comparison — commercial, ruleset-driven, GUI.

None of them is a zero-dependency CLI that prints a changelog and sets an exit code. That is the gap this fills.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages