3.9 KiB
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:
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
- Combines
IfcProjectelements — the two projects are merged into one. - Appends all entities from the part model into the base model, remapping entity ids to avoid collisions.
- Converts length units — the part is automatically rescaled to match the base model's unit before merging.
- Does not deduplicate spatial hierarchies — if both files have a site,
building, and storey, the result will contain two of each. Use
replaceinstead ofinsertwhen 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:
# 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:
- Extract each type separately with its own location-filtered query.
- Write each extraction to a temp file.
- Merge all temp files with
MergeProjects. - Clean up temp files.
This is encapsulated in the extract_with_location() helper in ifccommit.py.
Known quirks
- Segfault with path strings —
MergeProjectscrashes if given a file path instead of anifcopenshell.fileobject. Always open the file first. - Duplicate spatial hierarchy —
insertdoes not merge sites, buildings, or storeys; usereplacefor clean substitution. - Aggregated vs contained spaces — the
locationfilter only followsIfcRelContainedInSpatialStructure, notIfcRelAggregates. Spaces linked via aggregation appear empty to the filter (e.g. space B104 in duplex.ifc).