132 lines
3.9 KiB
Markdown
132 lines
3.9 KiB
Markdown
# 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).
|