fix(ifcdiff): check attributes by default so PredefinedType changes are caught (#8214)

IfcDiff defaulted to relationships=["geometry"], so a plain diff only ever
compared geometry. Attribute-only edits on an element that kept its GlobalId
(a modified or removed PredefinedType, a renamed element, etc.) were silently
missed. The CLI made this worse: --relationships did not list "attributes" or
"geometry" as valid values, so there was no documented way to enable it.

The default is now ["attributes", "geometry"], so a plain `ifcdiff old new`
reports attribute changes alongside geometry changes. The CLI help and the
IfcDiff docstring now document all valid relationship values.

Added a regression test covering a PredefinedType change detected with the
default configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 0a8ae14789)
This commit is contained in:
Petru Conduraru
2026-07-09 13:24:31 +03:00
committed by Dion Moult
parent ff798bc989
commit b3dc0b4478
2 changed files with 27 additions and 4 deletions
+10 -4
View File
@@ -51,8 +51,10 @@ class IfcDiff:
:param old: IFC file object for the old model :param old: IFC file object for the old model
:param new: IFC file object for the new model :param new: IFC file object for the new model
:param relationships: List of relationships to check. None means that only :param relationships: List of relationships to check. None means that
geometry is compared. See RELATIONSHIP_TYPE for available relationships. attributes and geometry are compared, so changes such as a modified or
removed PredefinedType are reported. See RELATIONSHIP_TYPE for available
relationships.
:param is_shallow: True if you want only the first difference to be listed. :param is_shallow: True if you want only the first difference to be listed.
False if you want all differences to be checked. Choosing False means False if you want all differences to be checked. Choosing False means
that comparisons will take longer. that comparisons will take longer.
@@ -86,7 +88,7 @@ class IfcDiff:
self.new = new self.new = new
self.change_register = {} self.change_register = {}
self.representation_ids = {} self.representation_ids = {}
self.relationships = relationships or ["geometry"] self.relationships = relationships or ["attributes", "geometry"]
self.precision = 1e-4 self.precision = 1e-4
self.is_shallow = is_shallow self.is_shallow = is_shallow
self.filter_elements = filter_elements self.filter_elements = filter_elements
@@ -435,7 +437,11 @@ if __name__ == "__main__":
"-r", "-r",
"--relationships", "--relationships",
type=str, type=str,
help='A list of space-separated relationships, chosen from "type", "property", "container", "aggregate", "classification"', help=(
'A list of space-separated relationships, chosen from "attributes", "geometry", '
'"type", "property", "container", "aggregate", "classification". '
'Defaults to "attributes geometry" when omitted.'
),
default="", default="",
) )
args = parser.parse_args() args = parser.parse_args()
+17
View File
@@ -77,6 +77,23 @@ class TestIfcDiff:
assert ifc_diff.deleted_elements == set() assert ifc_diff.deleted_elements == set()
assert ifc_diff.change_register == {wall.GlobalId: {"attributes_changed": True}} assert ifc_diff.change_register == {wall.GlobalId: {"attributes_changed": True}}
def test_changed_predefined_type_is_caught_by_default(self):
# Regression test for #8214: a plain diff (no relationships specified)
# must report a modified or removed PredefinedType. Previously the
# default only compared geometry, so attribute-only edits were missed.
ifc_file = setup_project()
wall = ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcWall", name="Foo")
wall.PredefinedType = "SOLIDWALL"
new_file = ifc_file.from_string(ifc_file.to_string())
new_file.by_id(wall.id()).PredefinedType = "NOTDEFINED"
ifc_diff = ifcdiff.IfcDiff(ifc_file, new_file)
ifc_diff.diff()
assert ifc_diff.added_elements == set()
assert ifc_diff.deleted_elements == set()
assert ifc_diff.change_register == {wall.GlobalId: {"attributes_changed": True}}
def test_changed_geometry(self): def test_changed_geometry(self):
ifc_file = setup_project() ifc_file = setup_project()
wall = ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcWall", name="Foo") wall = ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcWall", name="Foo")