diff --git a/src/ifcopenshell-python/ifcopenshell/util/unit.py b/src/ifcopenshell-python/ifcopenshell/util/unit.py index 8b2ec3c167..5caff307a8 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/unit.py +++ b/src/ifcopenshell-python/ifcopenshell/util/unit.py @@ -683,8 +683,14 @@ def is_attr_type( return cur_decl if isinstance(cur_decl, ifcopenshell_wrapper.aggregation_type): - res = cur_decl.type_of_element() - cur_decl = res.declared_type() + # support aggregate of aggregates, as in IfcCartesianPointList3D.CoordList + def get_declared_type_from_aggregate(cur_decl): + cur_decl = cur_decl.type_of_element() + if not isinstance(cur_decl, ifcopenshell_wrapper.aggregation_type): + return cur_decl.declared_type() + return get_declared_type_from_aggregate(cur_decl) + + cur_decl = get_declared_type_from_aggregate(cur_decl) if hasattr(cur_decl, "name") and cur_decl.name() == ifc_unit_type_name: return cur_decl while hasattr(cur_decl, "declared_type") is True: @@ -733,14 +739,16 @@ def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str) -> old_length = [u for u in unit_assignment.Units if getattr(u, "UnitType", None) == "LENGTHUNIT"][0] new_length = ifcopenshell.api.run("unit.add_si_unit", file_patched, unit_type="LENGTHUNIT", prefix=prefix) + # support tuple of tuples, as in IfcCartesianPointList3D.CoordList + def convert_value(value): + if not isinstance(value, tuple): + return convert_unit(value, old_length, new_length) + return tuple(convert_value(v) for v in value) + # Traverse all elements and their nested attributes in the file and convert them for element, attr, val in iter_element_and_attributes_per_type(file_patched, "IfcLengthMeasure"): - if isinstance(val, tuple): - new_value = [convert_unit(v, old_length, new_length) for v in val] - setattr(element, attr.name(), tuple(new_value)) - else: - new_value = convert_unit(val, old_length, new_length) - setattr(element, attr.name(), new_value) + new_value = convert_value(val) + setattr(element, attr.name(), new_value) file_patched.remove(old_length) unit_assignment.Units = tuple([new_length, *unit_assignment.Units]) diff --git a/src/ifcopenshell-python/test/fixtures/units/polygonal-faces.ifc b/src/ifcopenshell-python/test/fixtures/units/polygonal-faces.ifc new file mode 100644 index 0000000000..acf56d68b8 --- /dev/null +++ b/src/ifcopenshell-python/test/fixtures/units/polygonal-faces.ifc @@ -0,0 +1,79 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); +FILE_NAME('mill.ifc','2024-04-23T16:52:20+05:00',(),(),'IfcOpenShell v0.7.0-f7c03db75','BlenderBIM 0.0.240422-998f9c6','Nobody'); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPROJECT('3Q4HLVId9C$OJq2NqKnvVO',$,'My Project',$,$,$,$,(#14,#26),#9); +#2=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.); +#3=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#4=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#5=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#6=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#7=IFCMEASUREWITHUNIT(IFCREAL(0.0174532925199433),#6); +#8=IFCCONVERSIONBASEDUNIT(#5,.PLANEANGLEUNIT.,'degree',#7); +#9=IFCUNITASSIGNMENT((#4,#2,#8,#3)); +#10=IFCCARTESIANPOINT((0.,0.,0.)); +#11=IFCDIRECTION((0.,0.,1.)); +#12=IFCDIRECTION((1.,0.,0.)); +#13=IFCAXIS2PLACEMENT3D(#10,#11,#12); +#14=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#13,$); +#15=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); +#16=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#14,$,.GRAPH_VIEW.,$); +#17=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Box','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); +#18=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#14,$,.SECTION_VIEW.,$); +#19=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#14,$,.ELEVATION_VIEW.,$); +#20=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#14,$,.MODEL_VIEW.,$); +#21=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Model',*,*,*,*,#14,$,.PLAN_VIEW.,$); +#22=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Profile','Model',*,*,*,*,#14,$,.ELEVATION_VIEW.,$); +#23=IFCCARTESIANPOINT((0.,0.)); +#24=IFCDIRECTION((1.,0.)); +#25=IFCAXIS2PLACEMENT2D(#23,#24); +#26=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Plan',2,1.E-05,#25,$); +#27=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Plan',*,*,*,*,#26,$,.GRAPH_VIEW.,$); +#28=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Plan',*,*,*,*,#26,$,.PLAN_VIEW.,$); +#29=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Annotation','Plan',*,*,*,*,#26,$,.PLAN_VIEW.,$); +#30=IFCSITE('1T8u08wvPCnhfVRFeG_Je7',$,'My Site',$,$,#53,$,$,$,$,$,$,$,$); +#36=IFCBUILDING('0AGAIai5z9HOTud5I6CjMh',$,'My Building',$,$,#59,$,$,$,$,$,$); +#42=IFCBUILDINGSTOREY('2lMUoFnMv5KOSnzZ9_MPZv',$,'My Storey',$,$,#65,$,$,$,$); +#48=IFCRELAGGREGATES('0cjacslobAjw8I_uTLtTuK',$,$,$,#1,(#30)); +#49=IFCCARTESIANPOINT((0.,0.,0.)); +#50=IFCDIRECTION((0.,0.,1.)); +#51=IFCDIRECTION((1.,0.,0.)); +#52=IFCAXIS2PLACEMENT3D(#49,#50,#51); +#53=IFCLOCALPLACEMENT($,#52); +#54=IFCRELAGGREGATES('126VmVXPD0pAP2uJz6QYat',$,$,$,#30,(#36)); +#55=IFCCARTESIANPOINT((0.,0.,0.)); +#56=IFCDIRECTION((0.,0.,1.)); +#57=IFCDIRECTION((1.,0.,0.)); +#58=IFCAXIS2PLACEMENT3D(#55,#56,#57); +#59=IFCLOCALPLACEMENT(#53,#58); +#60=IFCRELAGGREGATES('1KLZsOP9zAivCjkiTn31bt',$,$,$,#36,(#42)); +#61=IFCCARTESIANPOINT((0.,0.,0.)); +#62=IFCDIRECTION((0.,0.,1.)); +#63=IFCDIRECTION((1.,0.,0.)); +#64=IFCAXIS2PLACEMENT3D(#61,#62,#63); +#65=IFCLOCALPLACEMENT(#59,#64); +#66=IFCACTUATOR('3RT$GBDjj6VhZQ2OYV3P13',$,'Cube',$,$,#95,#84,$,.ELECTRICACTUATOR.); +#72=IFCINDEXEDPOLYGONALFACE((1,2,4,3)); +#73=IFCINDEXEDPOLYGONALFACE((3,4,8,7)); +#74=IFCINDEXEDPOLYGONALFACE((7,8,6,5)); +#75=IFCINDEXEDPOLYGONALFACE((5,6,2,1)); +#76=IFCINDEXEDPOLYGONALFACE((3,7,5,1)); +#77=IFCINDEXEDPOLYGONALFACE((8,4,2,6)); +#78=IFCCARTESIANPOINTLIST3D(((-999.999938964844,-999.999938964844,-999.999938964844),(-999.999938964844,-999.999938964844,999.999938964844),(-999.999938964844,999.999938964844,-999.999938964844),(-999.999938964844,999.999938964844,999.999938964844),(999.999938964844,-999.999938964844,-999.999938964844),(999.999938964844,-999.999938964844,999.999938964844),(999.999938964844,999.999938964844,-999.999938964844),(999.999938964844,999.999938964844,999.999938964844))); +#79=IFCPOLYGONALFACESET(#78,.T.,(#72,#73,#74,#75,#76,#77),$); +#80=IFCSHAPEREPRESENTATION(#15,'Body','Tessellation',(#79)); +#81=IFCCARTESIANPOINT((-1000.,-1000.,-1000.)); +#82=IFCBOUNDINGBOX(#81,2000.,2000.,2000.); +#83=IFCSHAPEREPRESENTATION(#17,'Box','BoundingBox',(#82)); +#84=IFCPRODUCTDEFINITIONSHAPE($,$,(#83,#80)); +#85=IFCRELCONTAINEDINSPATIALSTRUCTURE('107zrsI95BsfGJXI83megf',$,$,$,(#66),#42); +#91=IFCCARTESIANPOINT((0.,5000.,0.)); +#92=IFCDIRECTION((0.,0.,1.)); +#93=IFCDIRECTION((1.,0.,0.)); +#94=IFCAXIS2PLACEMENT3D(#91,#92,#93); +#95=IFCLOCALPLACEMENT(#65,#94); +ENDSEC; +END-ISO-10303-21; diff --git a/src/ifcopenshell-python/test/util/test_unit_conversion.py b/src/ifcopenshell-python/test/util/test_unit_conversion.py index af84677647..22e750c507 100644 --- a/src/ifcopenshell-python/test/util/test_unit_conversion.py +++ b/src/ifcopenshell-python/test/util/test_unit_conversion.py @@ -41,9 +41,10 @@ def test_file_units_length_convert(ifc_file): elem_id = element.id() original_element = f.by_id(elem_id) original_val = getattr(original_element, attr.name()) - if isinstance(original_val, tuple): - # assert element is equal to original element times scale - assert val == tuple([v * scale for v in original_val]) - else: - # assert element is equal to original element times scale - assert val == original_val * scale + def convert_value(value): + if not isinstance(value, tuple): + return value * scale + return tuple(convert_value(v) for v in value) + + # assert element is equal to original element times scale + assert val == convert_value(original_val)