athar-ifc 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- athar_ifc-0.1.0/PKG-INFO +281 -0
- athar_ifc-0.1.0/README.md +260 -0
- athar_ifc-0.1.0/athar/__init__.py +1 -0
- athar_ifc-0.1.0/athar/__main__.py +193 -0
- athar_ifc-0.1.0/athar/_native/Cargo.lock +347 -0
- athar_ifc-0.1.0/athar/_native/Cargo.toml +40 -0
- athar_ifc-0.1.0/athar/_native/src/canon.rs +825 -0
- athar_ifc-0.1.0/athar/_native/src/descriptor.rs +103 -0
- athar_ifc-0.1.0/athar/_native/src/edges.rs +309 -0
- athar_ifc-0.1.0/athar/_native/src/lib.rs +912 -0
- athar_ifc-0.1.0/athar/_native/src/spatial.rs +335 -0
- athar_ifc-0.1.0/athar/_native/src/step.rs +500 -0
- athar_ifc-0.1.0/athar/bottom/__init__.py +1 -0
- athar_ifc-0.1.0/athar/bottom/_native.py +23 -0
- athar_ifc-0.1.0/athar/bottom/constants.py +6 -0
- athar_ifc-0.1.0/athar/bottom/native_schema.py +129 -0
- athar_ifc-0.1.0/athar/bottom/parser.py +169 -0
- athar_ifc-0.1.0/athar/bottom/properties.py +139 -0
- athar_ifc-0.1.0/athar/bottom/schema_dict.py +10 -0
- athar_ifc-0.1.0/athar/bottom/signatures.py +95 -0
- athar_ifc-0.1.0/athar/bottom/types.py +57 -0
- athar_ifc-0.1.0/athar/check.py +408 -0
- athar_ifc-0.1.0/athar/delta/__init__.py +1 -0
- athar_ifc-0.1.0/athar/delta/report.py +248 -0
- athar_ifc-0.1.0/athar/engine.py +235 -0
- athar_ifc-0.1.0/athar/matcher/__init__.py +1 -0
- athar_ifc-0.1.0/athar/matcher/core.py +117 -0
- athar_ifc-0.1.0/athar/matcher/types.py +13 -0
- athar_ifc-0.1.0/athar/policies/data-integrity.json +5 -0
- athar_ifc-0.1.0/athar/policies/georeferencing.json +9 -0
- athar_ifc-0.1.0/athar/policies/no-deletions.json +5 -0
- athar_ifc-0.1.0/athar/policies/safety-critical.json +9 -0
- athar_ifc-0.1.0/athar_bench/__init__.py +1 -0
- athar_ifc-0.1.0/athar_bench/ui.py +737 -0
- athar_ifc-0.1.0/athar_dev/__init__.py +1 -0
- athar_ifc-0.1.0/athar_dev/changeset_scoring.py +91 -0
- athar_ifc-0.1.0/athar_dev/ifc_mutations.py +423 -0
- athar_ifc-0.1.0/athar_git/__init__.py +7 -0
- athar_ifc-0.1.0/athar_git/cache.py +130 -0
- athar_ifc-0.1.0/athar_git/cli.py +155 -0
- athar_ifc-0.1.0/athar_git/pr_bot.py +302 -0
- athar_ifc-0.1.0/athar_git/render.py +227 -0
- athar_ifc-0.1.0/athar_qa/__init__.py +10 -0
- athar_ifc-0.1.0/athar_qa/render.py +131 -0
- athar_ifc-0.1.0/athar_store/__init__.py +21 -0
- athar_ifc-0.1.0/athar_store/cli.py +145 -0
- athar_ifc-0.1.0/athar_store/store.py +395 -0
- athar_ifc-0.1.0/athar_view/__init__.py +3 -0
- athar_ifc-0.1.0/athar_view/cli.py +187 -0
- athar_ifc-0.1.0/athar_view/server.py +326 -0
- athar_ifc-0.1.0/athar_view/static/assets/event-C6c_xMBB.js +1 -0
- athar_ifc-0.1.0/athar_view/static/assets/geometry.worker-BdUwmL7i.js +1 -0
- athar_ifc-0.1.0/athar_view/static/assets/ifc-lite_bg-D7jvTNoO.wasm +0 -0
- athar_ifc-0.1.0/athar_view/static/assets/index-CEeaJSse.css +1 -0
- athar_ifc-0.1.0/athar_view/static/assets/index-tCTOrRZ9.js +4108 -0
- athar_ifc-0.1.0/athar_view/static/assets/native-bridge-DJR5bS96.js +1 -0
- athar_ifc-0.1.0/athar_view/static/favicon.svg +1 -0
- athar_ifc-0.1.0/athar_view/static/index.html +15 -0
- athar_ifc-0.1.0/pyproject.toml +64 -0
athar_ifc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: athar-ifc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Rust
|
|
7
|
+
Requires-Dist: ifcopenshell
|
|
8
|
+
Requires-Dist: ifcdiff ; extra == 'benchmark'
|
|
9
|
+
Requires-Dist: ifcfast ; extra == 'benchmark'
|
|
10
|
+
Requires-Dist: pytest ; extra == 'benchmark'
|
|
11
|
+
Requires-Dist: specklepy ; extra == 'benchmark'
|
|
12
|
+
Requires-Dist: fastapi ; extra == 'benchmark-ui'
|
|
13
|
+
Requires-Dist: uvicorn ; extra == 'benchmark-ui'
|
|
14
|
+
Provides-Extra: benchmark
|
|
15
|
+
Provides-Extra: benchmark-ui
|
|
16
|
+
Summary: Semantic IFC diff engine
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
19
|
+
Project-URL: Repository, https://github.com/Amixx/Athar
|
|
20
|
+
|
|
21
|
+
# Athar
|
|
22
|
+
|
|
23
|
+
Semantic diff for IFC files. Compares BIM models at the entity/property level — not line-by-line text.
|
|
24
|
+
|
|
25
|
+
*Athar (Arabic: أثار) — a trace left behind.*
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
For each IFC file, Athar builds a **signature vector** per product/spatial entity:
|
|
30
|
+
|
|
31
|
+
- `vh_geometry` — sha256 Merkle hash over the entity's geometry-domain subgraph, **excluding placement**, so identical components share it across locations.
|
|
32
|
+
- `vh_data` — Merkle hash over the data-domain subgraph (properties, materials, typing). GlobalId and OwnerHistory never enter any hash.
|
|
33
|
+
- `vh_topology` — WL-style gossip hash over context and spatial neighborhoods.
|
|
34
|
+
- World-space placement matrix (quantized), centroid, and AABB from the resolved `ObjectPlacement` chain.
|
|
35
|
+
|
|
36
|
+
Old/new entities are then matched by **tiered pool reduction**: each tier only
|
|
37
|
+
looks at still-unmatched entities and emits disjoint 1:1 pairs, so there is no
|
|
38
|
+
candidate explosion and no ambiguous fan-out by construction.
|
|
39
|
+
|
|
40
|
+
| Tier | Evidence | Score |
|
|
41
|
+
|------|----------|-------|
|
|
42
|
+
| `guid` | GlobalId unique on both sides + same class | 1.0 (identical vector) / 0.9 |
|
|
43
|
+
| `geometry_hash` | same class + full signature-vector equality (interchangeable entities, zipped 1:1) | 0.8 |
|
|
44
|
+
|
|
45
|
+
Anything weaker is reported as added+deleted rather than guessed: a corpus
|
|
46
|
+
survey over real revision, discipline, and unrelated model pairs
|
|
47
|
+
(`docs/corpus/2026-06-10-corpus-survey.md`) showed that topology-only and
|
|
48
|
+
spatial-proximity fallback matching never fired on a genuine revision pair and
|
|
49
|
+
only manufactured cross-model matches, so those tiers were removed. Duplicated
|
|
50
|
+
GlobalIds are never trusted as identity — those entities fall through to the
|
|
51
|
+
vector tier. Matching is deterministic and entirely algorithmic, with no
|
|
52
|
+
tuning knobs.
|
|
53
|
+
|
|
54
|
+
The delta report classifies every entity as `added`, `deleted`, `modified`, or
|
|
55
|
+
`unchanged`, with per-aspect detail (`geometry/data/topology/placement`,
|
|
56
|
+
`placement_delta_mm`), `change_scope` (`intrinsic` = the entity itself changed,
|
|
57
|
+
`transitive` = only its neighborhood changed, `mixed`), and matcher
|
|
58
|
+
diagnostics (per-tier match counts, duplicate-GUID counts). Reports also carry
|
|
59
|
+
an `audit` block with producer/canon metadata plus old/new input provenance:
|
|
60
|
+
path, schema, byte size, and sha256. That makes the raw JSON suitable for
|
|
61
|
+
archiving as milestone evidence even before a visual viewer or PDF/HTML export
|
|
62
|
+
exists. Default output stays byte-deterministic; pass `--generated-at now` or
|
|
63
|
+
an explicit timestamp when an archived copy should embed a generation time.
|
|
64
|
+
|
|
65
|
+
Schema support: **IFC4 and IFC2X3**, same-schema comparisons only (no
|
|
66
|
+
IFC2X3↔IFC4 translation).
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pipx install athar-ifc
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This installs the `athar` command. The PyPI distribution is `athar-ifc`; the
|
|
75
|
+
Python packages import as `athar`, `athar_git`, and so on. Wheels ship the Rust
|
|
76
|
+
signature pipeline (`athar_native`) and the viewer bundle for Linux (x86_64,
|
|
77
|
+
aarch64), macOS (arm64, x86_64), and Windows (x86_64); other platforms build
|
|
78
|
+
from the sdist, which needs a Rust toolchain. Requires Python 3.10+ and
|
|
79
|
+
[ifcopenshell](https://ifcopenshell.org/).
|
|
80
|
+
|
|
81
|
+
Local development (needs a Rust toolchain):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m venv .venv
|
|
85
|
+
source .venv/bin/activate
|
|
86
|
+
make dev-setup
|
|
87
|
+
make native-build # builds athar_native and installs Athar editable
|
|
88
|
+
make test
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Two-file diff (JSON output)
|
|
95
|
+
python -m athar old.ifc new.ifc
|
|
96
|
+
|
|
97
|
+
# Include an audit generation timestamp for an archived report
|
|
98
|
+
python -m athar old.ifc new.ifc --generated-at now
|
|
99
|
+
|
|
100
|
+
# CI policy gate over a fresh diff; exits 2 on policy violations
|
|
101
|
+
python -m athar check old.ifc new.ifc --policy athar-policy.json
|
|
102
|
+
|
|
103
|
+
# CI policy gate over an existing JSON report
|
|
104
|
+
python -m athar check --report report.json --policy athar-policy.json
|
|
105
|
+
|
|
106
|
+
# Stream output as NDJSON records (header, one record per entity, end with stats)
|
|
107
|
+
python -m athar old.ifc new.ifc --stream ndjson
|
|
108
|
+
|
|
109
|
+
# Stream output as chunked JSON
|
|
110
|
+
python -m athar old.ifc new.ifc --stream chunked_json --chunk-size 1000
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### CI Policy Gates
|
|
114
|
+
|
|
115
|
+
`athar check` evaluates an Athar JSON report against a small JSON policy and
|
|
116
|
+
prints structured JSON: `{ "ok": bool, "violations": [...], "summary": ... }`.
|
|
117
|
+
It exits `0` when the policy passes, `2` when the diff is valid but violates
|
|
118
|
+
policy, and `1` for execution/configuration errors.
|
|
119
|
+
|
|
120
|
+
Example policy:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"forbid_schema_change": true,
|
|
125
|
+
"max_deleted": 0,
|
|
126
|
+
"max_deleted_by_class": {
|
|
127
|
+
"IfcWall": 0
|
|
128
|
+
},
|
|
129
|
+
"max_modified_change_scope": {
|
|
130
|
+
"intrinsic": 10,
|
|
131
|
+
"mixed": 0
|
|
132
|
+
},
|
|
133
|
+
"forbid_site_placement_change": true,
|
|
134
|
+
"max_placement_delta_mm": 50,
|
|
135
|
+
"max_placement_delta_mm_by_class": {
|
|
136
|
+
"IfcSite": 0
|
|
137
|
+
},
|
|
138
|
+
"forbid_aspect_changes": {
|
|
139
|
+
"data": ["IfcWall"],
|
|
140
|
+
"placement": "*"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The gate is report-driven and CI-platform-agnostic. Current policies can only
|
|
146
|
+
inspect signals present in the report: schema equality, section counts,
|
|
147
|
+
class-specific counts, modified change scopes, changed aspects, and placement
|
|
148
|
+
translation deltas. Fine-grained checks such as “property `FireRating` was
|
|
149
|
+
removed” require future report detail beyond the current data hash.
|
|
150
|
+
|
|
151
|
+
### Git IFC Diff Driver
|
|
152
|
+
|
|
153
|
+
Athar can be installed as a Git diff driver for `.ifc` files. The driver uses
|
|
154
|
+
the same semantic engine, renders a deterministic terminal summary, and caches
|
|
155
|
+
signature bundles on disk by Git blob id (falling back to a content hash for
|
|
156
|
+
working-tree files).
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Configure the current repository and add .gitattributes guidance
|
|
160
|
+
athar git install
|
|
161
|
+
|
|
162
|
+
# Manual terminal rendering without Git plumbing
|
|
163
|
+
athar git diff old.ifc new.ifc
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`athar git install` sets `diff.athar.command` to
|
|
167
|
+
`athar git diff --external` and appends:
|
|
168
|
+
|
|
169
|
+
```gitattributes
|
|
170
|
+
*.ifc diff=athar -merge
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The `-merge` marker is intentional. IFC is STEP text, but Git's default text
|
|
174
|
+
merge can silently splice two exported models into a corrupt file with
|
|
175
|
+
duplicate or dangling STEP ids. Athar provides semantic diffing; it does not
|
|
176
|
+
attempt semantic merge.
|
|
177
|
+
|
|
178
|
+
The persistent signature cache is versioned and deterministic, but v1 does not
|
|
179
|
+
evict old entries. Set `ATHAR_CACHE_DIR` to isolate or periodically clear a
|
|
180
|
+
large repository's cache.
|
|
181
|
+
|
|
182
|
+
### GitHub PR Comments
|
|
183
|
+
|
|
184
|
+
Athar also includes a thin GitHub PR bot command for CI. It discovers `.ifc`
|
|
185
|
+
files changed between two Git refs, runs the same cached semantic diff for
|
|
186
|
+
modified/renamed pairs, and creates or updates one stable PR comment marked by
|
|
187
|
+
an HTML sentinel.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
athar git pr-comment \
|
|
191
|
+
--base "$BASE_SHA" \
|
|
192
|
+
--head "$HEAD_SHA" \
|
|
193
|
+
--repo owner/repo \
|
|
194
|
+
--pr 123
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use `--dry-run` to print the Markdown comment body without calling GitHub.
|
|
198
|
+
When `--policy-result athar-check.json` is supplied, the comment includes a
|
|
199
|
+
pass/fail summary from an `athar check` result, but policy enforcement remains
|
|
200
|
+
separate. The checked-in `.github/workflows/athar-pr-diff.yml` workflow shows
|
|
201
|
+
the default pull-request setup using `GITHUB_TOKEN`.
|
|
202
|
+
|
|
203
|
+
### Visual 3D Diff Viewer
|
|
204
|
+
|
|
205
|
+
`athar view` overlays both models in one 3D scene in the browser, colored by
|
|
206
|
+
the semantic report: green added, red deleted, blue modified, gray ghosted
|
|
207
|
+
unchanged, amber displacement lines for placement moves. An old↔new slider
|
|
208
|
+
crossfades between revisions; clicking an entity shows its class, GUID,
|
|
209
|
+
changed aspects, placement delta, and match evidence.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
athar view old.ifc new.ifc # build report, serve, open browser
|
|
213
|
+
athar view old.ifc new.ifc --no-open --port 4799 # print the URL instead
|
|
214
|
+
athar git install --difftool # then: git difftool -t athar -- model.ifc
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The browser does all tessellation (`@ifc-lite/geometry` WASM); rendering is
|
|
218
|
+
three.js. `--viewer-url` (or `ATHAR_VIEWER_URL`) hands off to a hosted viewer
|
|
219
|
+
Perfetto-style: the page fetches the models back from the local server via
|
|
220
|
+
`?src=`, so nothing is uploaded. Offline mode serves a local build from
|
|
221
|
+
`ATHAR_VIEWER_DIST`, a release-packaged `athar_view/static`, or a freshly built
|
|
222
|
+
`viewer/dist`. The same SPA also accepts plain drag-drop of `old.ifc`,
|
|
223
|
+
`new.ifc`, and a report JSON — no launcher needed.
|
|
224
|
+
|
|
225
|
+
Viewer development requires [bun](https://bun.sh) and lives entirely under
|
|
226
|
+
`viewer/` (see [viewer/README.md](viewer/README.md)). Build the local bundle
|
|
227
|
+
with `make viewer-build`; `make viewer-package-static` copies it into
|
|
228
|
+
`athar_view/static`, which the release workflow does before building wheels.
|
|
229
|
+
|
|
230
|
+
## Testing
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
make test # full default suite (~20 seconds)
|
|
234
|
+
make test-large-acceptance # opt-in large IFC acceptance checks
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The default suite runs corpus-wide invariant tests (same-file zero diff,
|
|
238
|
+
stats accounting, no cross-class matches, duplicate-GUID and dangling-ref
|
|
239
|
+
behavior, schema policy) over every small real IFC in the corpus — the
|
|
240
|
+
Building-Landscaping v0→v3 revision chain, the IFC2X3 Duplex architecture
|
|
241
|
+
model, two GNI BIM Fundamentals samples, and small external samples — plus
|
|
242
|
+
metamorphic
|
|
243
|
+
GUID-scramble tests that prove identity recovery without GlobalId evidence.
|
|
244
|
+
It also generates known-edit semantic scenarios from real seeds (GUID
|
|
245
|
+
scramble, moving one product by a known vector, deleting a leaf product,
|
|
246
|
+
editing a pset value, editing a type-level pset value inherited by its
|
|
247
|
+
occurrences, renaming, duplicating a GUID) in temp dirs and asserts
|
|
248
|
+
the report against expectations derived from the constructed edit itself,
|
|
249
|
+
never from blessing engine output. Small files from the optional external
|
|
250
|
+
corpus (default `../vscode-ifc/test-files`, override via
|
|
251
|
+
`ATHAR_EXTERNAL_CORPUS_DIR`) skip when absent.
|
|
252
|
+
|
|
253
|
+
Large acceptance checks are opt-in so day-to-day runs stay fast:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
ATHAR_RUN_LARGE_ACCEPTANCE=1 python -m pytest tests/test_acceptance_large_ifc.py -q
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The acceptance tier covers the medium/large corpus (8MB–182MB; same-file
|
|
260
|
+
invariants, a real 44MB revision pair, discipline pairs, unrelated pairs, and
|
|
261
|
+
a GUID-scramble at scale). Files that are missing or unfetched LFS pointers
|
|
262
|
+
skip individually. Bound wall-clock per test via `ATHAR_ACCEPTANCE_TIMEOUT_S`.
|
|
263
|
+
|
|
264
|
+
At every tier, tests assert structural invariants and expectations derived
|
|
265
|
+
from the inputs themselves — never exact whole-report JSON goldens. Corpus
|
|
266
|
+
files too big for the repo will be described by a checked-in manifest rather
|
|
267
|
+
than stored (design: `docs/corpus/REMOTE_CORPUS_MANIFEST.md`; not yet
|
|
268
|
+
implemented).
|
|
269
|
+
|
|
270
|
+
## Documentation
|
|
271
|
+
|
|
272
|
+
Agent-facing architecture and conventions are split by ownership: start at
|
|
273
|
+
[AGENTS.md](AGENTS.md), then follow the nearest subdirectory `AGENTS.md` for
|
|
274
|
+
core engine, Git integration, tests, docs, and scripts. Dated performance
|
|
275
|
+
findings live under `docs/perf/`, with `docs/perf/STATUS.md` stating what is
|
|
276
|
+
still current. Corpus survey findings live under `docs/corpus/`.
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT
|
|
281
|
+
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Athar
|
|
2
|
+
|
|
3
|
+
Semantic diff for IFC files. Compares BIM models at the entity/property level — not line-by-line text.
|
|
4
|
+
|
|
5
|
+
*Athar (Arabic: أثار) — a trace left behind.*
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
For each IFC file, Athar builds a **signature vector** per product/spatial entity:
|
|
10
|
+
|
|
11
|
+
- `vh_geometry` — sha256 Merkle hash over the entity's geometry-domain subgraph, **excluding placement**, so identical components share it across locations.
|
|
12
|
+
- `vh_data` — Merkle hash over the data-domain subgraph (properties, materials, typing). GlobalId and OwnerHistory never enter any hash.
|
|
13
|
+
- `vh_topology` — WL-style gossip hash over context and spatial neighborhoods.
|
|
14
|
+
- World-space placement matrix (quantized), centroid, and AABB from the resolved `ObjectPlacement` chain.
|
|
15
|
+
|
|
16
|
+
Old/new entities are then matched by **tiered pool reduction**: each tier only
|
|
17
|
+
looks at still-unmatched entities and emits disjoint 1:1 pairs, so there is no
|
|
18
|
+
candidate explosion and no ambiguous fan-out by construction.
|
|
19
|
+
|
|
20
|
+
| Tier | Evidence | Score |
|
|
21
|
+
|------|----------|-------|
|
|
22
|
+
| `guid` | GlobalId unique on both sides + same class | 1.0 (identical vector) / 0.9 |
|
|
23
|
+
| `geometry_hash` | same class + full signature-vector equality (interchangeable entities, zipped 1:1) | 0.8 |
|
|
24
|
+
|
|
25
|
+
Anything weaker is reported as added+deleted rather than guessed: a corpus
|
|
26
|
+
survey over real revision, discipline, and unrelated model pairs
|
|
27
|
+
(`docs/corpus/2026-06-10-corpus-survey.md`) showed that topology-only and
|
|
28
|
+
spatial-proximity fallback matching never fired on a genuine revision pair and
|
|
29
|
+
only manufactured cross-model matches, so those tiers were removed. Duplicated
|
|
30
|
+
GlobalIds are never trusted as identity — those entities fall through to the
|
|
31
|
+
vector tier. Matching is deterministic and entirely algorithmic, with no
|
|
32
|
+
tuning knobs.
|
|
33
|
+
|
|
34
|
+
The delta report classifies every entity as `added`, `deleted`, `modified`, or
|
|
35
|
+
`unchanged`, with per-aspect detail (`geometry/data/topology/placement`,
|
|
36
|
+
`placement_delta_mm`), `change_scope` (`intrinsic` = the entity itself changed,
|
|
37
|
+
`transitive` = only its neighborhood changed, `mixed`), and matcher
|
|
38
|
+
diagnostics (per-tier match counts, duplicate-GUID counts). Reports also carry
|
|
39
|
+
an `audit` block with producer/canon metadata plus old/new input provenance:
|
|
40
|
+
path, schema, byte size, and sha256. That makes the raw JSON suitable for
|
|
41
|
+
archiving as milestone evidence even before a visual viewer or PDF/HTML export
|
|
42
|
+
exists. Default output stays byte-deterministic; pass `--generated-at now` or
|
|
43
|
+
an explicit timestamp when an archived copy should embed a generation time.
|
|
44
|
+
|
|
45
|
+
Schema support: **IFC4 and IFC2X3**, same-schema comparisons only (no
|
|
46
|
+
IFC2X3↔IFC4 translation).
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install athar-ifc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This installs the `athar` command. The PyPI distribution is `athar-ifc`; the
|
|
55
|
+
Python packages import as `athar`, `athar_git`, and so on. Wheels ship the Rust
|
|
56
|
+
signature pipeline (`athar_native`) and the viewer bundle for Linux (x86_64,
|
|
57
|
+
aarch64), macOS (arm64, x86_64), and Windows (x86_64); other platforms build
|
|
58
|
+
from the sdist, which needs a Rust toolchain. Requires Python 3.10+ and
|
|
59
|
+
[ifcopenshell](https://ifcopenshell.org/).
|
|
60
|
+
|
|
61
|
+
Local development (needs a Rust toolchain):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python -m venv .venv
|
|
65
|
+
source .venv/bin/activate
|
|
66
|
+
make dev-setup
|
|
67
|
+
make native-build # builds athar_native and installs Athar editable
|
|
68
|
+
make test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Two-file diff (JSON output)
|
|
75
|
+
python -m athar old.ifc new.ifc
|
|
76
|
+
|
|
77
|
+
# Include an audit generation timestamp for an archived report
|
|
78
|
+
python -m athar old.ifc new.ifc --generated-at now
|
|
79
|
+
|
|
80
|
+
# CI policy gate over a fresh diff; exits 2 on policy violations
|
|
81
|
+
python -m athar check old.ifc new.ifc --policy athar-policy.json
|
|
82
|
+
|
|
83
|
+
# CI policy gate over an existing JSON report
|
|
84
|
+
python -m athar check --report report.json --policy athar-policy.json
|
|
85
|
+
|
|
86
|
+
# Stream output as NDJSON records (header, one record per entity, end with stats)
|
|
87
|
+
python -m athar old.ifc new.ifc --stream ndjson
|
|
88
|
+
|
|
89
|
+
# Stream output as chunked JSON
|
|
90
|
+
python -m athar old.ifc new.ifc --stream chunked_json --chunk-size 1000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### CI Policy Gates
|
|
94
|
+
|
|
95
|
+
`athar check` evaluates an Athar JSON report against a small JSON policy and
|
|
96
|
+
prints structured JSON: `{ "ok": bool, "violations": [...], "summary": ... }`.
|
|
97
|
+
It exits `0` when the policy passes, `2` when the diff is valid but violates
|
|
98
|
+
policy, and `1` for execution/configuration errors.
|
|
99
|
+
|
|
100
|
+
Example policy:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"forbid_schema_change": true,
|
|
105
|
+
"max_deleted": 0,
|
|
106
|
+
"max_deleted_by_class": {
|
|
107
|
+
"IfcWall": 0
|
|
108
|
+
},
|
|
109
|
+
"max_modified_change_scope": {
|
|
110
|
+
"intrinsic": 10,
|
|
111
|
+
"mixed": 0
|
|
112
|
+
},
|
|
113
|
+
"forbid_site_placement_change": true,
|
|
114
|
+
"max_placement_delta_mm": 50,
|
|
115
|
+
"max_placement_delta_mm_by_class": {
|
|
116
|
+
"IfcSite": 0
|
|
117
|
+
},
|
|
118
|
+
"forbid_aspect_changes": {
|
|
119
|
+
"data": ["IfcWall"],
|
|
120
|
+
"placement": "*"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The gate is report-driven and CI-platform-agnostic. Current policies can only
|
|
126
|
+
inspect signals present in the report: schema equality, section counts,
|
|
127
|
+
class-specific counts, modified change scopes, changed aspects, and placement
|
|
128
|
+
translation deltas. Fine-grained checks such as “property `FireRating` was
|
|
129
|
+
removed” require future report detail beyond the current data hash.
|
|
130
|
+
|
|
131
|
+
### Git IFC Diff Driver
|
|
132
|
+
|
|
133
|
+
Athar can be installed as a Git diff driver for `.ifc` files. The driver uses
|
|
134
|
+
the same semantic engine, renders a deterministic terminal summary, and caches
|
|
135
|
+
signature bundles on disk by Git blob id (falling back to a content hash for
|
|
136
|
+
working-tree files).
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Configure the current repository and add .gitattributes guidance
|
|
140
|
+
athar git install
|
|
141
|
+
|
|
142
|
+
# Manual terminal rendering without Git plumbing
|
|
143
|
+
athar git diff old.ifc new.ifc
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`athar git install` sets `diff.athar.command` to
|
|
147
|
+
`athar git diff --external` and appends:
|
|
148
|
+
|
|
149
|
+
```gitattributes
|
|
150
|
+
*.ifc diff=athar -merge
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The `-merge` marker is intentional. IFC is STEP text, but Git's default text
|
|
154
|
+
merge can silently splice two exported models into a corrupt file with
|
|
155
|
+
duplicate or dangling STEP ids. Athar provides semantic diffing; it does not
|
|
156
|
+
attempt semantic merge.
|
|
157
|
+
|
|
158
|
+
The persistent signature cache is versioned and deterministic, but v1 does not
|
|
159
|
+
evict old entries. Set `ATHAR_CACHE_DIR` to isolate or periodically clear a
|
|
160
|
+
large repository's cache.
|
|
161
|
+
|
|
162
|
+
### GitHub PR Comments
|
|
163
|
+
|
|
164
|
+
Athar also includes a thin GitHub PR bot command for CI. It discovers `.ifc`
|
|
165
|
+
files changed between two Git refs, runs the same cached semantic diff for
|
|
166
|
+
modified/renamed pairs, and creates or updates one stable PR comment marked by
|
|
167
|
+
an HTML sentinel.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
athar git pr-comment \
|
|
171
|
+
--base "$BASE_SHA" \
|
|
172
|
+
--head "$HEAD_SHA" \
|
|
173
|
+
--repo owner/repo \
|
|
174
|
+
--pr 123
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use `--dry-run` to print the Markdown comment body without calling GitHub.
|
|
178
|
+
When `--policy-result athar-check.json` is supplied, the comment includes a
|
|
179
|
+
pass/fail summary from an `athar check` result, but policy enforcement remains
|
|
180
|
+
separate. The checked-in `.github/workflows/athar-pr-diff.yml` workflow shows
|
|
181
|
+
the default pull-request setup using `GITHUB_TOKEN`.
|
|
182
|
+
|
|
183
|
+
### Visual 3D Diff Viewer
|
|
184
|
+
|
|
185
|
+
`athar view` overlays both models in one 3D scene in the browser, colored by
|
|
186
|
+
the semantic report: green added, red deleted, blue modified, gray ghosted
|
|
187
|
+
unchanged, amber displacement lines for placement moves. An old↔new slider
|
|
188
|
+
crossfades between revisions; clicking an entity shows its class, GUID,
|
|
189
|
+
changed aspects, placement delta, and match evidence.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
athar view old.ifc new.ifc # build report, serve, open browser
|
|
193
|
+
athar view old.ifc new.ifc --no-open --port 4799 # print the URL instead
|
|
194
|
+
athar git install --difftool # then: git difftool -t athar -- model.ifc
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The browser does all tessellation (`@ifc-lite/geometry` WASM); rendering is
|
|
198
|
+
three.js. `--viewer-url` (or `ATHAR_VIEWER_URL`) hands off to a hosted viewer
|
|
199
|
+
Perfetto-style: the page fetches the models back from the local server via
|
|
200
|
+
`?src=`, so nothing is uploaded. Offline mode serves a local build from
|
|
201
|
+
`ATHAR_VIEWER_DIST`, a release-packaged `athar_view/static`, or a freshly built
|
|
202
|
+
`viewer/dist`. The same SPA also accepts plain drag-drop of `old.ifc`,
|
|
203
|
+
`new.ifc`, and a report JSON — no launcher needed.
|
|
204
|
+
|
|
205
|
+
Viewer development requires [bun](https://bun.sh) and lives entirely under
|
|
206
|
+
`viewer/` (see [viewer/README.md](viewer/README.md)). Build the local bundle
|
|
207
|
+
with `make viewer-build`; `make viewer-package-static` copies it into
|
|
208
|
+
`athar_view/static`, which the release workflow does before building wheels.
|
|
209
|
+
|
|
210
|
+
## Testing
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
make test # full default suite (~20 seconds)
|
|
214
|
+
make test-large-acceptance # opt-in large IFC acceptance checks
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The default suite runs corpus-wide invariant tests (same-file zero diff,
|
|
218
|
+
stats accounting, no cross-class matches, duplicate-GUID and dangling-ref
|
|
219
|
+
behavior, schema policy) over every small real IFC in the corpus — the
|
|
220
|
+
Building-Landscaping v0→v3 revision chain, the IFC2X3 Duplex architecture
|
|
221
|
+
model, two GNI BIM Fundamentals samples, and small external samples — plus
|
|
222
|
+
metamorphic
|
|
223
|
+
GUID-scramble tests that prove identity recovery without GlobalId evidence.
|
|
224
|
+
It also generates known-edit semantic scenarios from real seeds (GUID
|
|
225
|
+
scramble, moving one product by a known vector, deleting a leaf product,
|
|
226
|
+
editing a pset value, editing a type-level pset value inherited by its
|
|
227
|
+
occurrences, renaming, duplicating a GUID) in temp dirs and asserts
|
|
228
|
+
the report against expectations derived from the constructed edit itself,
|
|
229
|
+
never from blessing engine output. Small files from the optional external
|
|
230
|
+
corpus (default `../vscode-ifc/test-files`, override via
|
|
231
|
+
`ATHAR_EXTERNAL_CORPUS_DIR`) skip when absent.
|
|
232
|
+
|
|
233
|
+
Large acceptance checks are opt-in so day-to-day runs stay fast:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
ATHAR_RUN_LARGE_ACCEPTANCE=1 python -m pytest tests/test_acceptance_large_ifc.py -q
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The acceptance tier covers the medium/large corpus (8MB–182MB; same-file
|
|
240
|
+
invariants, a real 44MB revision pair, discipline pairs, unrelated pairs, and
|
|
241
|
+
a GUID-scramble at scale). Files that are missing or unfetched LFS pointers
|
|
242
|
+
skip individually. Bound wall-clock per test via `ATHAR_ACCEPTANCE_TIMEOUT_S`.
|
|
243
|
+
|
|
244
|
+
At every tier, tests assert structural invariants and expectations derived
|
|
245
|
+
from the inputs themselves — never exact whole-report JSON goldens. Corpus
|
|
246
|
+
files too big for the repo will be described by a checked-in manifest rather
|
|
247
|
+
than stored (design: `docs/corpus/REMOTE_CORPUS_MANIFEST.md`; not yet
|
|
248
|
+
implemented).
|
|
249
|
+
|
|
250
|
+
## Documentation
|
|
251
|
+
|
|
252
|
+
Agent-facing architecture and conventions are split by ownership: start at
|
|
253
|
+
[AGENTS.md](AGENTS.md), then follow the nearest subdirectory `AGENTS.md` for
|
|
254
|
+
core engine, Git integration, tests, docs, and scripts. Dated performance
|
|
255
|
+
findings live under `docs/perf/`, with `docs/perf/STATUS.md` stating what is
|
|
256
|
+
still current. Corpus survey findings live under `docs/corpus/`.
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|