diff --git a/features/rules/GRF/GRF003_CRS-presence-with-spatial-entities.feature b/features/rules/GRF/GRF003_CRS-presence-with-spatial-entities.feature index 4958ce20b..8fdf27a2c 100644 --- a/features/rules/GRF/GRF003_CRS-presence-with-spatial-entities.feature +++ b/features/rules/GRF/GRF003_CRS-presence-with-spatial-entities.feature @@ -13,11 +13,11 @@ Models containing IfcFacility must contain a IfcProjectedCRS or IfcGeographicCRS Given a model with Schema 'IFC4' Given an .IfcBuilding. - Then There must be at least 1 instance(s) of IfcProjectedCRS + Then There must be at least 1 instance(s) of .IfcProjectedCRS. Scenario: CRS required when IfcFacility is present Given a model with Schema 'IFC4.3' Given an .IfcFacility. - Then There must be at least 1 instance(s) of IfcCoordinateReferenceSystem + Then There must be at least 1 instance(s) of .IfcCoordinateReferenceSystem. diff --git a/features/rules/GRF/GRF005_CRS-unit-type-differences.feature b/features/rules/GRF/GRF005_CRS-unit-type-differences.feature new file mode 100644 index 000000000..ca529e577 --- /dev/null +++ b/features/rules/GRF/GRF005_CRS-unit-type-differences.feature @@ -0,0 +1,26 @@ +@industry-practice +@GRF +@version1 +@E00100 +Feature: GRF005 - CRS unit type differences +The rule verifies that the Scale attribute of IfcMapConversion is used when the units of the CRS are not identical to the units of the engineering coordinate system. +If omitted, the value of 1.0 is assumed. +If the units of the referenced source location engineering coordinate system are different from the units of the referenced target coordinate system, +then this attribute must be included and must have the value of the scale from the source to the target units + + + Scenario Outline: When the length unit of the Local CRS (from IfcProject) is not equal to the length unit of the Projected CRS, then the IfcMapCOnversion.Scale must be provided and cannot be 1.0 + + Given A model with Schema 'IFC4.3' + Given An .IfcMapConversion. + Given There must be at least 1 instance(s) of .. + Given The unit of the project ^is not^ equal to the unit(s) of the .. + + Then .Scale. ^is not^ empty + Then .Scale. ^is not^ 1.0 + + Examples: + | unit | IfcCoordinateReferenceSystem | + | length | IfcProjectedCRS | + | angle | IfcGeographicCRS | + diff --git a/features/rules/IFC/IFC102_Absence-of-deprecated-entities.feature b/features/rules/IFC/IFC102_Absence-of-deprecated-entities.feature index a2e44a7e0..6bc38f08e 100644 --- a/features/rules/IFC/IFC102_Absence-of-deprecated-entities.feature +++ b/features/rules/IFC/IFC102_Absence-of-deprecated-entities.feature @@ -17,7 +17,7 @@ IFC4: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ Given A model with Schema 'IFC4.3' Given An IFC model - Then There must be less than 1 instance(s) of ^excluding subtypes^ + Then There must be less than 1 instance(s) of .. ^excluding subtypes^ Examples: | Entity | @@ -51,7 +51,7 @@ IFC4: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ Given An IFC model Given A model with Schema 'IFC4' - Then There must be less than 1 instance(s) of ^excluding subtypes^ + Then There must be less than 1 instance(s) of .. ^excluding subtypes^ Examples: | Entity | @@ -91,7 +91,7 @@ IFC4: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/ Given An IFC model Given A model with Schema 'IFC2X3' - Then There must be less than 1 instance(s) of ^excluding subtypes^ + Then There must be less than 1 instance(s) of .. ^excluding subtypes^ Examples: | Entity | diff --git a/features/rules/PJS/PJS101_Project-presence.feature b/features/rules/PJS/PJS101_Project-presence.feature index 7816d3957..71f107c1e 100644 --- a/features/rules/PJS/PJS101_Project-presence.feature +++ b/features/rules/PJS/PJS101_Project-presence.feature @@ -10,4 +10,4 @@ For example, project libraries, including type libraries or property definition Scenario: Check project existence Given an IFC Model - Then There must be exactly 1 instance(s) of IfcProject \ No newline at end of file + Then There must be exactly 1 instance(s) of .IfcProject. \ No newline at end of file diff --git a/features/rules/SPS/SPS001_Basic-spatial-structure-for-buildings.feature b/features/rules/SPS/SPS001_Basic-spatial-structure-for-buildings.feature index 15403d456..7fab97f67 100644 --- a/features/rules/SPS/SPS001_Basic-spatial-structure-for-buildings.feature +++ b/features/rules/SPS/SPS001_Basic-spatial-structure-for-buildings.feature @@ -12,17 +12,17 @@ The rule verifies that there's maximum one instance of IfcSite and at least one Scenario: Agreement141 - Agreement on having maximum of one instance of IfcSite - Then There must be at most 1 instance(s) of IfcSite + Then There must be at most 1 instance(s) of .IfcSite. Scenario: Agreement142(1) - Agreement on having at least one instance of IfcBuilding as part of the spatial structure - Then There must be at least 1 instance(s) of IfcBuilding + Then There must be at least 1 instance(s) of .IfcBuilding. Scenario: Agreement142(2) - Agreement on having at least one instance of IfcBuilding as part of the spatial structure Given An .IfcSite. Given an .IfcBuilding. - Then It must be assigned to the IfcSite + Then It must be assigned to the .IfcSite. Scenario: Agreement142(3) - Agreement on having at least one instance of IfcBuilding as part of the spatial structure @@ -30,4 +30,4 @@ The rule verifies that there's maximum one instance of IfcSite and at least one Given no .IfcSite. Given An .IfcBuilding. - Then It must be assigned to the IfcProject + Then It must be assigned to the .IfcProject. diff --git a/features/steps/givens/values.py b/features/steps/givens/values.py index 722214a21..a7709293a 100644 --- a/features/steps/givens/values.py +++ b/features/steps/givens/values.py @@ -3,7 +3,6 @@ from validation_handling import gherkin_ifc from . import ValidationOutcome, OutcomeSeverity - @gherkin_ifc.step("Its values") @gherkin_ifc.step("Its values excluding {excluding}") def step_impl(context, inst, excluding=None): @@ -32,4 +31,4 @@ def step_impl(context, inst): shp = ifcopenshell.ifcopenshell_wrapper.map_shape(ifcopenshell.geom.settings(), inst.wrapped_data) d = np.linalg.det(np.array(shp.components)) - yield ValidationOutcome(instance_id=d, severity=OutcomeSeverity.PASSED) + yield ValidationOutcome(instance_id=d, severity=OutcomeSeverity.PASSED) \ No newline at end of file diff --git a/features/steps/registered_type_definitions.json b/features/steps/registered_type_definitions.json index 9efb3e5d5..a1c3f6b21 100644 --- a/features/steps/registered_type_definitions.json +++ b/features/steps/registered_type_definitions.json @@ -7,7 +7,8 @@ "include_or_exclude_subtypes": ["including subtypes", "excluding subtypes"], "first_or_final": ["first", "final"], "absence_or_presence": ["absence", "presence"], - "equal_or_not_equal": ["=", "!=", "is not", "is"], + "equal_or_not_equal": ["=", "!=", "is not", "is", "must be", "must be not"], + "length_and_or_angle_units": ["length", "length and angle", "angle"], "nested_sentences": ["must nest only 1", "must not a list of only", "is nested by only 1", "is nested by a list of only"], "aggregated_or_contained_or_positioned": ["aggregated", "contained", "positioned"], "prefix_condition": ["starts", "does not start", "must not start"], diff --git a/features/steps/steps/attribute_value.py b/features/steps/steps/attribute_value.py index 72e113fe0..066bfe5fd 100644 --- a/features/steps/steps/attribute_value.py +++ b/features/steps/steps/attribute_value.py @@ -1,7 +1,7 @@ import ast import operator -from utils import geometry, ifc, misc +from utils import misc from validation_handling import gherkin_ifc from . import ValidationOutcome, OutcomeSeverity @@ -24,11 +24,6 @@ def step_impl(context, inst, comparison_operator, attribute, value, subtype_hand start_value = value pred = operator.eq - def negate(fn): - def inner(*args): - return not fn(*args) - return inner - if value == 'empty': value = () elif value == 'not empty': @@ -43,7 +38,7 @@ def inner(*args): pred = operator.contains if comparison_operator in {"is not", "!="}: # avoid using != together with (not)empty stmt - pred = negate(pred) + pred = misc.negate(pred) observed_v = () if attribute.lower() in ['its type', 'its entity type']: # it's entity type is a special case using ifcopenshell 'is_a()' func diff --git a/features/steps/steps/crs.py b/features/steps/steps/crs.py index db4e69d90..17794465a 100644 --- a/features/steps/steps/crs.py +++ b/features/steps/steps/crs.py @@ -1,6 +1,8 @@ from pyproj.database import query_crs_info from pyproj import CRS from validation_handling import gherkin_ifc +from utils import misc +import operator from . import ValidationOutcome, OutcomeSeverity @@ -22,4 +24,85 @@ def step_impl(context, inst): yield ValidationOutcome(instance_id=inst, severity=OutcomeSeverity.PASSED) else: yield ValidationOutcome(inst=inst, severity=OutcomeSeverity.ERROR) - \ No newline at end of file + + +@gherkin_ifc.step("The {unit_types} unit(s) of the project ^{comparison_operator:equal_or_not_equal}^ equal to the {crs_unit_types} unit(s) of the .{crs_entity_type}.") +def step_impl(context, inst, unit_types, comparison_operator, crs_unit_types, crs_entity_type): + assert unit_types == crs_unit_types, "it's only possible to compare equal unit types" + pred = misc.negate(operator.eq) if comparison_operator in {"is not", "!="} else operator.eq + unit_types = unit_types.split(' and ') + + conversion_based = False + + unit_type_attr_map= { + 'length' : 'LENGTHUNIT', + 'area': 'AREAUNIT', + 'volume': 'VOLUMEUNIT', + 'angle': 'PLANEANGLEUNIT', + } + + def map_units(units): + map = {} + for unit in units: + if unit.is_a('IfcNamedUnit') + unit_type = unit.UnitType + if unit_type and unit_type not in map: + map[unit_type] = unit + return map + + # determine the project unit values + project = context.model.by_type("IfcProject")[0] + project_unit_map = map_units(getattr(project.UnitsInContext, 'Units', [])) + project_units = {utype : project_unit_map.get(unit_type_attr_map[utype]) for utype in unit_types} + + + if 'length' in unit_types and (length := project_units.get('length')): + prefix = getattr(length, 'Prefix', '') + name = getattr(length, 'Name', '').lower() + project_units['length'] = (f"{prefix}{name}" if prefix else name).lower() + + if 'angle' in unit_types and (angle := project_units.get('angle')): + if length.is_a('IfcSIUnit'): + project_units['angle'] = { + 'name': (f"{prefix}{name}" if prefix else name).lower(), + 'conversion_factor' : None + } + conversion_based=True + else: + project_units['angle'] = { + 'name': getattr(angle, 'Name', '').lower(), + 'conversion_factor': misc.do_try(lambda: angle.ConversionFactor.ValueComponent.wrappedValue, '') + } + + # determine the crs unit values + crs = context.model.by_type(crs_entity_type)[0] + epsg_crs = CRS.from_string(crs.Name) + unit_attrs = [attr for attr in dir(crs) if attr.endswith('Unit')] + crs_unit_map = map_units([getattr(crs, attr) for attr in unit_attrs if getattr(crs, attr, None) is not None]) + crs_units = {utype: crs_unit_map.get(unit_type_attr_map[utype]) for utype in unit_types} + + if 'length' in unit_types: + if length := crs_units.get('length'): + prefix = getattr(length, 'Prefix', '') + name = getattr(length, 'Name', '').lower() + crs_units['length'] = (f"{prefix}{name}" if prefix else name).lower() + else: + crs_units['length'] = epsg_crs.coordinate_system.axis_list[0].unit_name + + if 'angle' in unit_types: + if angle := crs_units.get('angle'): + crs_units['angle'] = { + 'name': getattr(angle, 'Name').lower(), + 'conversion_factor': misc.do_try(lambda : project_units['angle'].ConversionFactor.ValueComponent.wrappedValue, '') + } + else: + crs_units['angle'] = { + 'name' : epsg_crs.coordinate_system.axis_list[0].unit_name, + 'conversion_factor': misc.do_try(lambda : epsg_crs.coordinate_system.axis_list[0].unit_conversion_factor, None) if conversion_based else None + } + + + if pred(project_units, crs_units): + yield ValidationOutcome(instance_id=inst, severity=OutcomeSeverity.PASSED) + else: + yield ValidationOutcome(inst=inst, severity=OutcomeSeverity.ERROR) \ No newline at end of file diff --git a/features/steps/thens/existence.py b/features/steps/thens/existence.py index f2113349f..b21985bc9 100644 --- a/features/steps/thens/existence.py +++ b/features/steps/thens/existence.py @@ -28,7 +28,7 @@ def get_entities_in_model(context, constraint, entity, include_or_exclude_subtyp @gherkin_ifc.step( - "There must be {constraint} {num:d} instance(s) of {entity} ^{subtype_handling:include_or_exclude_subtypes}^" + "There must be {constraint} {num:d} instance(s) of .{entity}. ^{subtype_handling:include_or_exclude_subtypes}^" ) @global_rule def step_impl(context, inst, constraint, num, entity, subtype_handling="including subtypes"): @@ -40,15 +40,29 @@ def step_impl(context, inst, constraint, num, entity, subtype_handling="includin ) -@gherkin_ifc.step("There must be {constraint} {num:d} instance(s) of {entity}") +@gherkin_ifc.step("There must be {constraint} {num:d} instance(s) of .{entity}.") @global_rule def step_impl(context, inst, constraint, num, entity, include_or_exclude_subtypes="including subtypes"): + """ + The Given step_impl in combination with 'at least 1' is the equivalent of 'Given an IfcEntity', but without setting new applicable instances. + For example: + Given an IfcWall -> insts = [IfcWall, IfcWall] + Given an IfcRoof -> inst = [IfcRoof, IfcRoof] + + However, + Given an IfcWall -> insts = [IfcWall, IfcWall] + Given there must be at least 1 instance(s) of IfcRoof -> inst = [IfcWall, IfcWall] + """ op = misc.stmt_to_op(constraint) instances_in_model = get_entities_in_model(context, constraint, entity, include_or_exclude_subtypes) if not op(len(instances_in_model), num): yield ValidationOutcome( inst=inst, observed=instances_in_model, severity=OutcomeSeverity.ERROR ) + else: + yield ValidationOutcome( + instance_id=inst, severity=OutcomeSeverity.PASSED + ) @gherkin_ifc.step( diff --git a/features/steps/thens/relations.py b/features/steps/thens/relations.py index 057c5c926..82ab2044f 100644 --- a/features/steps/thens/relations.py +++ b/features/steps/thens/relations.py @@ -58,7 +58,7 @@ def step_impl(context, inst, relationship, table): yield ValidationOutcome(inst=inst, expected={"oneOf": expected_relationship_objects, "context": context}, observed=relationship_object, severity=OutcomeSeverity.ERROR) -@gherkin_ifc.step("It must be assigned to the {relating}") +@gherkin_ifc.step("It must be assigned to the .{relating}.") def step_impl(context, inst, relating): for rel in getattr(inst, 'Decomposes', []): if not rel.RelatingObject.is_a(relating): diff --git a/features/steps/utils/misc.py b/features/steps/utils/misc.py index 79eaca05d..adb87b11e 100644 --- a/features/steps/utils/misc.py +++ b/features/steps/utils/misc.py @@ -20,6 +20,22 @@ def inner(*args): return fn(*reversed(args)) return inner +def negate(fn): + """ + Returns a function that negates the result of the given predicate function. + + Example: + >>> import operator + >>> not_equal = negate(operator.eq) + >>> not_equal(1, 2) + True + >>> not_equal(1, 1) + False + """ + def inner(*args): + return not fn(*args) + return inner + def recursive_flatten(lst): flattened_list = [] @@ -150,12 +166,6 @@ def clean(s): - -def unpack_sequence_of_entities(instances): - # in case of [[inst1, inst2], [inst3, inst4]] - return [do_try(lambda: unpack_tuple(inst), None) for inst in instances] - - def unpack_tuple(tup): for item in tup: if isinstance(item, tuple): diff --git a/features/steps/validation_handling.py b/features/steps/validation_handling.py index 6aa8d62a3..38c1265aa 100644 --- a/features/steps/validation_handling.py +++ b/features/steps/validation_handling.py @@ -21,8 +21,8 @@ def global_rule(func): """ Use this decorator when the rule applies to the whole stack instead of a single instance. For instance - @gherkin_ifc.step('There must be {constraint} {num:d} instance(s) of {entity}') - @gherkin_ifc.step('There must be {constraint} {num:d} instance(s) of {entity} {tail:include_or_exclude_subtypes}') + @gherkin_ifc.step('There must be {constraint} {num:d} instance(s) of .{entity}.') + @gherkin_ifc.step('There must be {constraint} {num:d} instance(s) of .{entity}. {tail:include_or_exclude_subtypes}') @global_rule """ @functools.wraps(func) diff --git a/test/files/GRF/grf005/fail-grf005-project_meters_crs_foot_no_map_conversion_scaled.ifc b/test/files/GRF/grf005/fail-grf005-project_meters_crs_foot_no_map_conversion_scaled.ifc new file mode 100644 index 000000000..f0d239fb4 --- /dev/null +++ b/test/files/GRF/grf005/fail-grf005-project_meters_crs_foot_no_map_conversion_scaled.ifc @@ -0,0 +1,34 @@ +ISO-10303-21; +/* IFC units are in metres, ESPG:2277 uses survey foot and the map conversion scaled is not set */ +HEADER; +FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1'); +FILE_NAME('fail-grf005-project_meters_crs_foot_no_map_conversion_scaled.ifc','2023-01-25T18:40:40',(''),(''),'','IfcOpenShell contributors - IfcOpenShell - v0.7.0+6180d73f',''); +FILE_SCHEMA(('IFC4X3_ADD2')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,'',$,$,$,$,$); +#2=IFCORGANIZATION($,'',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'v0.7.0-6180d73f','IfcOpenShell-v0.7.0-6180d73f',''); +#5=IFCOWNERHISTORY(#3,#4,$,.NOCHANGE.,$,#3,#4,1674672040); +#6=IFCDIRECTION((1.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCCARTESIANPOINT((0.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); +#10=IFCDIRECTION((0.,1.)); +#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_FOOT.); +#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_FOOT.); +#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); +#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); +#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); +#20=IFCPROJECT('2X9wjf5oPB4PQjenCYrhHZ',#5,'',$,$,$,$,(#11),#19) +#21=IFCPROJECTEDCRS('EPSG:2277',$,'NAD83',$,'','3',$); +#22=IFCMAPCONVERSION(#11,#21,316131.64,5690966.11,1.,1.,0.,$); +#23=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#24=IFCMAPCONVERSION(#23,#21,316131.64,5690966.11,1.,1.,0.,$); +ENDSEC; +END-ISO-10303-21; diff --git a/test/files/GRF/grf005/fail-grf005-project_metres_crs_foot_default_map_conversion.ifc b/test/files/GRF/grf005/fail-grf005-project_metres_crs_foot_default_map_conversion.ifc new file mode 100644 index 000000000..1f59d7ceb --- /dev/null +++ b/test/files/GRF/grf005/fail-grf005-project_metres_crs_foot_default_map_conversion.ifc @@ -0,0 +1,34 @@ +ISO-10303-21; +/* IFC units are in metres, ESPG:2277 uses survey foot and the map conversion is not scaled (defaulted to '1') */ +HEADER; +FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1'); +FILE_NAME('fail-grf005-project_metres_crs_foot_default_map_conversion.ifc','2023-01-25T18:40:40',(''),(''),'','IfcOpenShell contributors - IfcOpenShell - v0.7.0+6180d73f',''); +FILE_SCHEMA(('IFC4X3_ADD2')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,'',$,$,$,$,$); +#2=IFCORGANIZATION($,'',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'v0.7.0-6180d73f','IfcOpenShell-v0.7.0-6180d73f',''); +#5=IFCOWNERHISTORY(#3,#4,$,.NOCHANGE.,$,#3,#4,1674672040); +#6=IFCDIRECTION((1.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCCARTESIANPOINT((0.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); +#10=IFCDIRECTION((0.,1.)); +#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_FOOT.); +#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_FOOT.); +#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); +#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); +#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); +#20=IFCPROJECT('2X9wjf5oPB4PQjenCYrhHZ',#5,'',$,$,$,$,(#11),#19) +#21=IFCPROJECTEDCRS('EPSG:2277',$,'NAD83',$,'','3',$); +#22=IFCMAPCONVERSION(#11,#21,316131.64,5690966.11,1.,1.,0.,1); +#23=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#24=IFCMAPCONVERSION(#23,#21,316131.64,5690966.11,1.,1.,0.,1); +ENDSEC; +END-ISO-10303-21; diff --git a/test/files/GRF/grf005/na-grf005-equal_local_and_projected_length_units.ifc b/test/files/GRF/grf005/na-grf005-equal_local_and_projected_length_units.ifc new file mode 100644 index 000000000..f76c8155a --- /dev/null +++ b/test/files/GRF/grf005/na-grf005-equal_local_and_projected_length_units.ifc @@ -0,0 +1,33 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1'); +FILE_NAME('na-grf005-equal_local_and_projected_length_units.ifc','2023-01-25T18:40:40',(''),(''),'','IfcOpenShell contributors - IfcOpenShell - v0.7.0+6180d73f',''); +FILE_SCHEMA(('IFC4X3_ADD2')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,'',$,$,$,$,$); +#2=IFCORGANIZATION($,'',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'v0.7.0-6180d73f','IfcOpenShell-v0.7.0-6180d73f',''); +#5=IFCOWNERHISTORY(#3,#4,$,.NOCHANGE.,$,#3,#4,1674672040); +#6=IFCDIRECTION((1.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCCARTESIANPOINT((0.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); +#10=IFCDIRECTION((0.,1.)); +#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); +#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); +#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); +#20=IFCPROJECT('2X9wjf5oPB4PQjenCYrhHZ',#5,'',$,$,$,$,(#11),#19); +#21=IFCPROJECTEDCRS('EPSG:27215',$,'WGS84',$,'WSG','3',#13); +#22=IFCMAPCONVERSION(#11,#21,316131.64,5690966.11,1.,1.,0.,$); +#23=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#24=IFCMAPCONVERSION(#23,#21,316131.64,5690966.11,1.,1.,0.,$); +ENDSEC; +END-ISO-10303-21; diff --git a/test/files/GRF/grf005/na-grf005-geographic_referencing_no_map_conversion.ifc b/test/files/GRF/grf005/na-grf005-geographic_referencing_no_map_conversion.ifc new file mode 100644 index 000000000..e8f48a044 --- /dev/null +++ b/test/files/GRF/grf005/na-grf005-geographic_referencing_no_map_conversion.ifc @@ -0,0 +1,75 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION (('ViewDefinition[NotAssigned]'), '2;1'); +FILE_NAME ('na-grf005-geographic_referencing_no_map_conversion.ifc', '2022-11-16T10:37:00', (''), (''), 'redacted', 'redacted - redacted - 3.14159', ''); +FILE_SCHEMA (('IFC4X3_ADD2')); +ENDSEC; +DATA; + +/*The basis definitions of an IfcProject and IfcGeometricRepresentationContext */ +#1=IFCPROJECT('2DAvEupIz0HQr73cMaawtY',$, 'Project name', $, $, $, $, (#21), #11); +#2=IFCDIRECTION((1., 0., 0.)); +#4=IFCDIRECTION((0., 0., 1.)); +#5=IFCCARTESIANPOINT((0., 0., 0.)); +#7=IFCDIRECTION((0.,1.)); +#11=IFCUNITASSIGNMENT((#12, #15)); +#12=IFCSIUNIT(*, .LENGTHUNIT., $, .METRE.); +#13=IFCSIUNIT(*, .PLANEANGLEUNIT., $, .RADIAN.); +#14=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295), #13); +#15=IFCCONVERSIONBASEDUNIT(#16,.PLANEANGLEUNIT., 'degree', #14); +#16=IFCDIMENSIONALEXPONENTS(0, 0, 0, 0, 0, 0, 0); +#21=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model', 3, 1.E-6, #22, #7); +#22=IFCAXIS2PLACEMENT3D(#5, #4, #2); + +/* Georeferencing data of an IFC dataset in a Geographic CRS. The site with this context lies at the Bell tower on the island of Lake Bled in Slovenia.*/ +#301=IFCGEOGRAPHICCRS('EPSG:4258', 'ETRS89', 'EPSG:6258', 'EPSG:8901', #15, #12); +#302=IFCRIGIDOPERATION(#21, #301, IFCPLANEANGLEMEASURE(14.0902217), IFCPLANEANGLEMEASURE(46.3623297), 475.); + + +/* IfcSite definition*/ +#30 = IFCSITE('27H$neCQf1NwtmczxBInPR', $, $, $, $, #31, $, $, .ELEMENT., $, $, $, $, $); +/* IfcSite's placement is the top-most placement and uses the same IfcAxis2Placement as IfcGeometricRepresentationContext does */ +#31 = IFCLOCALPLACEMENT($, #22); +/* Relationship between Project and Site */ +#33 = IFCRELAGGREGATES('3Hu7f6BmT14B_XS9yS78Jr', $, $, $, #1, (#30)); + + +/* Definition of a Footprint */ +#40 = IFCBUILTELEMENT('28H$neCQf1NwtmczxBInPR', $, 'Footprint', $, $, #50, #60, $); +/* Relationship: Footprint in Site */ +#41 = IFCRELCONTAINEDINSPATIALSTRUCTURE('3Uu7sw3ALEWKTWNPpz$fqn', $, 'Container', 'Container to Contained', (#40), #30); +/* Placement of the Footprint, relative to that of IfcSite. The way it is modelled, there is no offset and no rotation. */ +#50 = IFCLOCALPLACEMENT(#31, #22); +/* Representation of the Footprint: a polyline with six points */ +#60= IFCPRODUCTDEFINITIONSHAPE($,$,(#61)); +#61= IFCSHAPEREPRESENTATION(#21,'Axis','Curve2D',(#62)); +#62= IFCPOLYLINE((#63,#64,#67,#68,#65,#66,#63)); +#63 = IFCCARTESIANPOINT((32.58,3.68)); +#64 = IFCCARTESIANPOINT((31.82,9.10)); +#65 = IFCCARTESIANPOINT((28.26,0.37)); +#66 = IFCCARTESIANPOINT((27.95,2.90)); +#67 = IFCCARTESIANPOINT((17.37,6.98)); +#68 = IFCCARTESIANPOINT((18.67,-1.02)); + + +/* Definition of a Cube*/ +#70= IFCBUILTELEMENT('1kTvXnbbzCWw8lcMd1dR4o',$,'Cube',$,$,#80,#90,$); +/* Relationship: Cube in Site */ +#71=IFCRELCONTAINEDINSPATIALSTRUCTURE('2TnxZkTXT08eDuMuhUUFNy',$,'Container','Container to Contained',(#70),#30); +/* Placement of the cube, relative to that of IfcSite. */ +#80= IFCLOCALPLACEMENT(#31,#81); +/* Set local placement to 1 meter on x-axis, and 0 on y and z axes, no rotation. z and x axes are set to '$' and therefore identical to those of Site */ +#81= IFCAXIS2PLACEMENT3D(#82,$,$); +#82= IFCCARTESIANPOINT((1.,0.,0.)); +/* Representation of the Cube: a single swept solid shape */ +#90= IFCPRODUCTDEFINITIONSHAPE($,$,(#91)); +#91= IFCSHAPEREPRESENTATION(#21,'Body','SweptSolid',(#92)); +/* based on a profile (or cross section) of 3m by 4m being extruded by 1.5m */ +#92= IFCEXTRUDEDAREASOLID(#93,$,#4,1.5); +#93= IFCRECTANGLEPROFILEDEF(.AREA.,'3m x 4m rectangle',$,3.,4.); +/* extrusion body is placed centric with no rotation inside the object coordinate placement */ +/* extrusion position z = default = (0.,0.,1.), x = default = (1.,0.,0.) */ +/* the default position, i.e. no re-positioning of the results, hence the position is NIL */ +/* the extrusion is perpendicular to the profile - i.e. along the positive z-axis */ +ENDSEC; +END-ISO-10303-21; \ No newline at end of file diff --git a/test/files/GRF/grf005/pass-grf005-scaled_foot_to_metre.ifc b/test/files/GRF/grf005/pass-grf005-scaled_foot_to_metre.ifc new file mode 100644 index 000000000..a6bc2cb07 --- /dev/null +++ b/test/files/GRF/grf005/pass-grf005-scaled_foot_to_metre.ifc @@ -0,0 +1,34 @@ +ISO-10303-21; +/* IFC units are in metres, ESPG:2277 uses survey foot and the map conversion reflects this */ +HEADER; +FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1'); +FILE_NAME('pass-grf005-scaled_foot_to_metre.ifc','2023-01-25T18:40:40',(''),(''),'','IfcOpenShell contributors - IfcOpenShell - v0.7.0+6180d73f',''); +FILE_SCHEMA(('IFC4X3_ADD2')); +ENDSEC; +DATA; +#1=IFCPERSON($,$,'',$,$,$,$,$); +#2=IFCORGANIZATION($,'',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'v0.7.0-6180d73f','IfcOpenShell-v0.7.0-6180d73f',''); +#5=IFCOWNERHISTORY(#3,#4,$,.NOCHANGE.,253317$,#3,#4,1674672040); +#6=IFCDIRECTION((1.,0.,0.)); +#7=IFCDIRECTION((0.,0.,1.)); +#8=IFCCARTESIANPOINT((0.,0.,0.)); +#9=IFCAXIS2PLACEMENT3D(#8,#7,#6); +#10=IFCDIRECTION((0.,1.)); +#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); +#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_FOOT.); +#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_FOOT.); +#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); +#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); +#19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); +#20=IFCPROJECT('2X9wjf5oPB4PQjenCYrhHZ',#5,'',$,$,$,$,(#11),#19) +#21=IFCPROJECTEDCRS('EPSG:2277',$,'NAD83',$,'','3',$); +#22=IFCMAPCONVERSION(#11,#21,316131.64,5690966.11,1.,1.,0.,0.3046); +#23=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10); +#24=IFCMAPCONVERSION(#23,#21,316131.64,5690966.11,1.,1.,0.,0.3046); +ENDSEC; +END-ISO-10303-21;