ifc-commit/docs/merge.md
2026-03-24 16:11:15 +01:00

132 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# IFC Merging
How IFC files are merged in the ifc-commit pipeline.
---
## Recipe: `MergeProjects`
All merging in ifccommit uses the `ifcpatch` recipe `MergeProjects`. It combines
two or more `ifcopenshell.file` objects into a single model:
```python
import ifcopenshell
import ifcpatch
base = ifcopenshell.open("base.ifc")
part = ifcopenshell.open("part.ifc")
result = ifcpatch.execute({
"input": "base.ifc",
"file": base,
"recipe": "MergeProjects",
"arguments": [[part]], # list of ifcopenshell.file objects
})
ifcpatch.write(result, "merged.ifc")
```
**Important**: always pass `ifcopenshell.file` objects, not file paths.
Passing a path string causes a segfault in the current ifcopenshell version.
---
## What MergeProjects does
1. **Combines `IfcProject` elements** — the two projects are merged into one.
2. **Appends all entities** from the part model into the base model, remapping
entity ids to avoid collisions.
3. **Converts length units** — the part is automatically rescaled to match the
base model's unit before merging.
4. **Does not deduplicate** spatial hierarchies — if both files have a site,
building, and storey, the result will contain two of each. Use `replace`
instead of `insert` when updating an existing space.
---
## `insert` vs `replace`
| Command | Behaviour |
|-----------|-----------|
| `insert` | Appends part into base — no removal. May duplicate spatial elements. |
| `replace` | Removes the target space first, then merges part in. Clean result. |
### `insert`
```
base.ifc (295 products)
+ part.ifc (9 products)
= merged.ifc (304 products) ← space objects duplicated
```
Used when the part introduces new elements that do not already exist in the base.
### `replace`
```
base.ifc (295 products)
space A102 + contents (6 elements removed)
+ modified_space.ifc (9 products)
= merged.ifc (298 products) ← clean replacement
```
Used when updating a space that already exists in the base model.
---
## Internals of `replace`
`cmd_replace` in ifccommit.py performs the removal step manually before merging:
```python
# 1. Find and remove the space and its contained elements
contained = ifcopenshell.util.selector.filter_elements(
model, f'IfcElement, location = "{space_name}"'
)
for el in list(contained) + [space]:
ifcopenshell.api.run("root.remove_product", model, product=el)
# 2. Write stripped base to a temp file
model.write(tmp)
# 3. Merge the part in
stripped = ifcopenshell.open(tmp)
part_model = ifcopenshell.open(part_path)
result = ifcpatch.execute({
"input": tmp, "file": stripped,
"recipe": "MergeProjects",
"arguments": [[part_model]],
})
ifcpatch.write(result, output)
```
`root.remove_product` cleans up the element and its direct relationships
(placements, containment links). Geometry and shared assets are handled by the
model's internal garbage collection on write.
---
## Multi-type merging in `split`
The `location` filter in ifcopenshell's selector breaks when combined with the
`+` operator for multiple types (`IfcWall + IfcSlab, location = "Level 1"`
silently drops the filter). The workaround used in `cmd_split`:
1. Extract each type separately with its own location-filtered query.
2. Write each extraction to a temp file.
3. Merge all temp files with `MergeProjects`.
4. Clean up temp files.
This is encapsulated in the `extract_with_location()` helper in `ifccommit.py`.
---
## Known quirks
- **Segfault with path strings** — `MergeProjects` crashes if given a file path
instead of an `ifcopenshell.file` object. Always open the file first.
- **Duplicate spatial hierarchy** — `insert` does not merge sites, buildings, or
storeys; use `replace` for clean substitution.
- **Aggregated vs contained spaces** — the `location` filter only follows
`IfcRelContainedInSpatialStructure`, not `IfcRelAggregates`. Spaces linked via
aggregation appear empty to the filter (e.g. space B104 in duplex.ifc).