ExtractElements: handle IfcProject without RepresentationContexts #8199

The georeferencing fix (e6dc582) iterates IfcProject.RepresentationContexts
unconditionally, but the attribute is OPTIONAL and None on projects without
contexts, crashing every extraction on such files with
TypeError: 'NoneType' object is not iterable.

Also extend the #8199 regression test to assert element placements are
copied verbatim, so extraction can never bake map coordinates into local
placements.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-05 19:48:05 +03:00
committed by Thomas Krijnen
parent bade0647e8
commit 49de7dbcb1
2 changed files with 12 additions and 2 deletions
@@ -110,7 +110,7 @@ class Patcher(ifcpatch.BasePatcher):
pass pass
if element.is_a("IfcProject"): if element.is_a("IfcProject"):
proj = self.new.add(element) proj = self.new.add(element)
for ctx in element.RepresentationContexts: for ctx in element.RepresentationContexts or ():
for coop in getattr(ctx, 'HasCoordinateOperation', ()): for coop in getattr(ctx, 'HasCoordinateOperation', ()):
self.new.add(coop) self.new.add(coop)
return proj return proj
+11 -1
View File
@@ -21,10 +21,12 @@ import os
import ifcopenshell import ifcopenshell
import ifcopenshell.api.aggregate import ifcopenshell.api.aggregate
import ifcopenshell.api.context import ifcopenshell.api.context
import ifcopenshell.api.geometry
import ifcopenshell.api.georeference import ifcopenshell.api.georeference
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.api.spatial import ifcopenshell.api.spatial
import ifcopenshell.util.element import ifcopenshell.util.element
import numpy
import pytest import pytest
import ifcpatch import ifcpatch
@@ -96,7 +98,10 @@ class TestExtractElements(test.bootstrap.IFC4):
self.file, self.file,
coordinate_operation={"Eastings": 100000.0, "Northings": 200000.0}, coordinate_operation={"Eastings": 100000.0, "Northings": 200000.0},
) )
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") wall = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
matrix = numpy.eye(4)
matrix[:3, 3] = [5.0, 10.0, 2.0]
ifcopenshell.api.geometry.edit_object_placement(self.file, product=wall, matrix=matrix)
output = ifcpatch.execute({"file": self.file, "recipe": "ExtractElements", "arguments": ["IfcWall"]}) output = ifcpatch.execute({"file": self.file, "recipe": "ExtractElements", "arguments": ["IfcWall"]})
@@ -105,6 +110,11 @@ class TestExtractElements(test.bootstrap.IFC4):
conversion = output.by_type("IfcMapConversion")[0] conversion = output.by_type("IfcMapConversion")[0]
assert conversion.Eastings == 100000.0 assert conversion.Eastings == 100000.0
assert conversion.Northings == 200000.0 assert conversion.Northings == 200000.0
# Placements must be copied verbatim: extraction must not bake map
# coordinates (or any other georeferencing transform) into the local
# placements of the extracted elements.
wall_new = output.by_type("IfcWall")[0]
assert wall_new.ObjectPlacement.RelativePlacement.Location.Coordinates == (5.0, 10.0, 2.0)
@pytest.mark.skipif( @pytest.mark.skipif(
"IFC4X3" not in ifcopenshell.ifcopenshell_wrapper.schema_names(), "IFC4X3" not in ifcopenshell.ifcopenshell_wrapper.schema_names(),