diff --git a/src/ifcopenshell-python/ifcopenshell/validate.py b/src/ifcopenshell-python/ifcopenshell/validate.py index 22e747f50d..19f14447c0 100644 --- a/src/ifcopenshell-python/ifcopenshell/validate.py +++ b/src/ifcopenshell-python/ifcopenshell/validate.py @@ -350,10 +350,28 @@ def validate(f: Union[ifcopenshell.file, str], logger: Logger, express_rules=Fal log_internal_cpp_errors(filename, logger) schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema_identifier) + used_guids: dict[str, ifcopenshell.entity_instance] = dict() + for inst in f: if hasattr(logger, "set_state"): logger.set_state("instance", inst) + if hasattr(inst, "GlobalId"): + guid = inst.GlobalId + if guid is not None and guid in used_guids: + rule = "Rule IfcRoot.UR1:\n The attribute GlobalId should be unique" + previous_element = used_guids[guid] + logger.error( + "On instance:\n %s\n %s\n%s\nViolated by:\n %s\n %s", + inst, + annotate_inst_attr_pos(inst, 0), + rule, + previous_element, + annotate_inst_attr_pos(previous_element, 0), + ) + else: + used_guids[guid] = inst + entity, attrs = get_entity_attributes(schema, inst.is_a()) if entity.is_abstract(): diff --git a/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc2x3.ifc b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc2x3.ifc new file mode 100644 index 0000000000..9a6eb58e05 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc2x3.ifc @@ -0,0 +1,20 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC2X3')); +ENDSEC; +DATA; +#1=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#2=IFCTELECOMADDRESS(.USERDEFINED.,$,'WEBPAGE',$,$,$,$,'https://ifcopenshell.org'); +#3=IFCORGANIZATION('IfcOpenShell','IfcOpenShell','IfcOpenShell is an open source software library that helps users and software developers to work with IFC data.',(#1),(#2)); +#4=IFCAPPLICATION(#3,'v0.7.0-e38eafd03','IfcOpenShell','IfcOpenShell'); +#5=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$); +#6=IFCORGANIZATION('APTR','Aperture Science',$,$,$); +#7=IFCPERSONANDORGANIZATION(#5,#6,$); +#8=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1712060210,#7,#4,1712060210); +#9=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',#8,$,$,$,$,$,$,$); +#10=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1712060210,#7,#4,1712060210); +#11=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',#10,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4.ifc b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4.ifc new file mode 100644 index 0000000000..eeca2cf84f --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4.ifc @@ -0,0 +1,11 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +#2=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4x3.ifc b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4x3.ifc new file mode 100644 index 0000000000..9bf812583a --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/fail-duplicated-guids-ifc4x3.ifc @@ -0,0 +1,11 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC4X3')); +ENDSEC; +DATA; +#1=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +#2=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/fixtures/validate/generate_00.py b/src/ifcopenshell-python/test/fixtures/validate/generate_00.py new file mode 100644 index 0000000000..e552a18970 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/generate_00.py @@ -0,0 +1,26 @@ +import ifcopenshell +import ifcopenshell.api +from pathlib import Path + + +guid1 = "3pdqyORIn8KBHZAhhtJ72T" +guid2 = "1sEzC8v31DshmvW5t5P631" + +for schema in ("ifc2x3", "ifc4", "ifc4x3"): + for status in ("fail", "pass"): + f = ifcopenshell.file(schema=schema) + + # setup IfcOwnerHistory + if schema == "ifc2x3": + ifcopenshell.api.run("owner.add_application", f) + person = ifcopenshell.api.run("owner.add_person", f) + organization = ifcopenshell.api.run("owner.add_organisation", f) + ifcopenshell.api.run("owner.add_person_and_organisation", f, person=person, organisation=organization) + + wall1 = ifcopenshell.api.run("root.create_entity", f) + wall2 = ifcopenshell.api.run("root.create_entity", f) + wall1.GlobalId = guid1 + wall2.GlobalId = guid1 if status == "fail" else guid2 + + filename = f"{status}-{'not-' if status == 'pass' else ''}duplicated-guids-{schema}.ifc" + f.write(Path(__file__).parent / filename) diff --git a/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc2x3.ifc b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc2x3.ifc new file mode 100644 index 0000000000..37d71d03e4 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc2x3.ifc @@ -0,0 +1,20 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC2X3')); +ENDSEC; +DATA; +#1=IFCACTORROLE(.USERDEFINED.,'CONTRIBUTOR',$); +#2=IFCTELECOMADDRESS(.USERDEFINED.,$,'WEBPAGE',$,$,$,$,'https://ifcopenshell.org'); +#3=IFCORGANIZATION('IfcOpenShell','IfcOpenShell','IfcOpenShell is an open source software library that helps users and software developers to work with IFC data.',(#1),(#2)); +#4=IFCAPPLICATION(#3,'v0.7.0-e38eafd03','IfcOpenShell','IfcOpenShell'); +#5=IFCPERSON('HSeldon','Seldon','Hari',$,$,$,$,$); +#6=IFCORGANIZATION('APTR','Aperture Science',$,$,$); +#7=IFCPERSONANDORGANIZATION(#5,#6,$); +#8=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1712060210,#7,#4,1712060210); +#9=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',#8,$,$,$,$,$,$,$); +#10=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1712060210,#7,#4,1712060210); +#11=IFCBUILDINGELEMENTPROXY('1sEzC8v31DshmvW5t5P631',#10,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4.ifc b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4.ifc new file mode 100644 index 0000000000..e839daaba6 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4.ifc @@ -0,0 +1,11 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +#2=IFCBUILDINGELEMENTPROXY('1sEzC8v31DshmvW5t5P631',$,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4x3.ifc b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4x3.ifc new file mode 100644 index 0000000000..d96dc23002 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/validate/pass-not-duplicated-guids-ifc4x3.ifc @@ -0,0 +1,11 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2024-04-02T17:16:50',(),(),'IfcOpenShell v0.7.0-e38eafd03','IfcOpenShell v0.7.0-e38eafd03',''); +FILE_SCHEMA(('IFC4X3')); +ENDSEC; +DATA; +#1=IFCBUILDINGELEMENTPROXY('3pdqyORIn8KBHZAhhtJ72T',$,$,$,$,$,$,$,$); +#2=IFCBUILDINGELEMENTPROXY('1sEzC8v31DshmvW5t5P631',$,$,$,$,$,$,$,$); +ENDSEC; +END-ISO-10303-21;