Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions features/rules/GRF/GRF005_CRS-unit-type-differences.feature
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
@industry-practice
@GRF
@version1
@E00100
@version2
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
then this attribute must be included and must have the value of the scale from the project units to the target of the map coordinate units.


Scenario: In cases where the length unit of the local CRS differs from the length unit of the external CRS, the map conversion scale must reflect the factor between the two units.
Scenario: IfcMapConversion Scale attribute

Given A model with Schema 'IFC4' or 'IFC4.3'
Given An .IfcMapConversion.
Expand Down
10 changes: 5 additions & 5 deletions features/steps/steps/crs.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def get_horizontal_unit_factors(crs: CRS) -> set[float]:
@gherkin_ifc.step("The map conversion scale must be the quotient of the project length units and the target CRS length units")
def step_impl(context, inst):
error_found = False
proj_unit_factor = unit.calculate_unit_scale(context.model, unit_type='LENGTHUNIT')
project_unit_factor = unit.calculate_unit_scale(context.model, unit_type='LENGTHUNIT')
map_conversion_scale = getattr(inst, 'Scale', 1.)
map_conversion_scale_factor = 1.0 if not map_conversion_scale else map_conversion_scale

Expand All @@ -75,19 +75,19 @@ def step_impl(context, inst):
# Relative tolerance of 1E-9 corresponds to 1 part per billion.
# This is appropriate for imperial projects using Northing and Easting coordinates
# that are often in the range 1E6 or even 1E7.
if not isclose(crs_unit_factor, proj_unit_factor, abs_tol=0., rel_tol=1E-9):
if not isclose(crs_unit_factor, project_unit_factor, abs_tol=0., rel_tol=1E-9):
error_found = True
yield ValidationOutcome(inst=inst,
observed=f"map conversion scale {map_conversion_scale} does not reflect mismatch of target CRS unit conversion factor {crs_unit_factor} and project length unit scale {proj_unit_factor}",
observed=f"map conversion scale {map_conversion_scale} does not reflect mismatch of target CRS unit conversion factor {crs_unit_factor} and project length unit scale {project_unit_factor}",
severity=OutcomeSeverity.ERROR)
else:
# Scale factor provided for IfcMapConversion.
# Confirm that it matches the expected value.
quotient = crs_unit_factor / proj_unit_factor
quotient = project_unit_factor / crs_unit_factor

if not isclose(quotient, map_conversion_scale_factor, abs_tol=0., rel_tol=1E-9):
error_found = True
yield ValidationOutcome(inst=inst, observed=f"map conversion scale {map_conversion_scale} does not reflect the quotient of the target CRS unit conversion factor {crs_unit_factor} divided by the project length unit scale {proj_unit_factor}", severity=OutcomeSeverity.ERROR)
yield ValidationOutcome(inst=inst, observed=f"map conversion scale {map_conversion_scale} does not reflect the value of the project length unit factor {project_unit_factor} divided by the target CRS unit factor {crs_unit_factor}", severity=OutcomeSeverity.ERROR)

if not error_found:
yield ValidationOutcome(inst=inst, severity=OutcomeSeverity.PASSED)
49 changes: 49 additions & 0 deletions test/files/GRF/grf005/fail-grf005-metre_millimetre_scale.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ISO-10303-21;
/* This file has a project length unit of mm, a CRS length unit of m and incorrect (set to reciprocal of correct value) map conversion scale of 1000. Correct value is 0.001. */
HEADER;
FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1');
FILE_NAME('fail-grf005-metre_millimetre_scale.ifc','2025-04-18T17:26:42',(''),(''),'redacted','redacted - redacted - 3.14159','None');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;
DATA;
#1= IFCPROJECTEDCRS('EPSG:25832','UTM in band 32','ETRS89',$,'UTM','UTM32',#3);
#2=IFCMAPCONVERSION(#100011,#1,458657.3,5438232.25,113.7,0.270600445976,0.962691746426,1000.);
#3=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#121=IFCLOCALPLACEMENT(#5044,#243);
#149=IFCBUILDINGELEMENTPROXY('0rBru4syGxGOZb$M8kVJuS',#100005,'1G5YB0Is','rcGZcCxu',$,#121,#5037,$,$);
#173=IFCCARTESIANPOINT((0.,0.,0.));
#217=IFCDIRECTION((0.,0.,1.));
#218=IFCDIRECTION((1.,0.,0.));
#243=IFCAXIS2PLACEMENT3D(#173,#217,#218);
#5000=IFCTRIANGULATEDFACESET(#5001,$,.T.,((1,3,2),(1,4,3),(1,5,4),(1,2,5),(2,3,6),(3,7,6),(3,4,7),(4,8,7),(4,5,8),(5,9,8),(5,2,9),(2,6,9),(6,7,8),(6,8,9)),$);
#5001=IFCCARTESIANPOINTLIST3D(((0.,0.,0.),(-500.,-500.,2000.),(500.,-500.,2000.),(500.,500.,2000.),(-500.,500.,2000.),(-500.,-500.,4000.),(500.,-500.,4000.),(500.,500.,4000.),(-500.,500.,4000.)),$);
#5037=IFCPRODUCTDEFINITIONSHAPE('TEqKVmeoo','wLTkVqDs',(#5038));
#5038=IFCSHAPEREPRESENTATION(#100011,'Body','Tessellation',(#5000));
#5043=IFCBUILDINGSTOREY('3_fv_WeK63IPclwapZa9MD',#100005,'oFyqDEMp','U27aC7',$,#5044,$,$,.ELEMENT.,-1.6);
#5044=IFCLOCALPLACEMENT(#100025,#100040);
#5045=IFCRELCONTAINEDINSPATIALSTRUCTURE('2lLFgu3KhSGw4jMC3$Ak50',#100005,'ifb7t1I0','wa0nsIOtz',(#149),#5043);
#5046=IFCRELAGGREGATES('3INy2PAPGPJ8Dpwhrq610o',#100005,'IfunXHpKIg','3jRSpKX',#100023,(#5043));
#100001=IFCPERSON($,'3VqUcnCJ','WJBECA3',$,$,$,$,$);
#100002=IFCORGANIZATION($,'qoFRBW','ih1j7R5',$,$);
#100003=IFCPERSONANDORGANIZATION(#100001,#100002,$);
#100004=IFCAPPLICATION(#100002,'redacted','OPHxMQ15FA','4ho8Ryz');
#100005=IFCOWNERHISTORY(#100003,#100004,$,.NOTDEFINED.,$,$,$,1122650864);
#100010=IFCPROJECT('01JwSt5ycUHvKFlMZUleKS',#100005,'Yu0c77684','MoSwVWh6X4',$,$,$,(#100011),#100060);
#100011=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#100040,$);
#100020=IFCSITE('1BPCQAtTW7GhKrUF$Sytrr',#100005,'EZZYBy','zIXNapcj',$,#100022,$,$,.ELEMENT.,(49,5,43,983700),(8,26,1,247300),113.7,$,$);
#100021=IFCRELAGGREGATES('2vOmTZNmbvJAJRbSGMMNrC',#100005,'s63CrGVO8e','T4hjEFRIZ',#100010,(#100020));
#100022=IFCLOCALPLACEMENT($,#100040);
#100023=IFCBUILDING('0cVTYHAI3nGeTXA2X4Gxyo',#100005,'2x3Nux','rNJKX4hNJ',$,#100025,$,$,.ELEMENT.,$,$,$);
#100024=IFCRELAGGREGATES('1F_GRhxz1GJgGgcJgzjvKX',#100005,'nRsNWiE','V7Jh3YcW',#100020,(#100023));
#100025=IFCLOCALPLACEMENT(#100022,#100040);
#100040=IFCAXIS2PLACEMENT3D(#100041,#100044,#100042);
#100041=IFCCARTESIANPOINT((0.,0.,0.));
#100042=IFCDIRECTION((1.,0.,0.));
#100044=IFCDIRECTION((0.,0.,1.));
#100060=IFCUNITASSIGNMENT((#100061,#100062,#100063,#100064));
#100061=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#100062=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#100063=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#100064=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
ENDSEC;
END-ISO-10303-21;
49 changes: 49 additions & 0 deletions test/files/GRF/grf005/pass-grf005-metre_millimetre_scale.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ISO-10303-21;
/* This file has a project length unit of mm, a CRS length unit of m and correct map conversion scale of 0.001. */
HEADER;
FILE_DESCRIPTION(('ViewDefinition [ReferenceView]'),'2;1');
FILE_NAME('fail-grf005-metre_millimetre_scale.ifc','2025-04-18T17:26:42',(''),(''),'redacted','redacted - redacted - 3.14159','None');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;
DATA;
#1= IFCPROJECTEDCRS('EPSG:25832','UTM in band 32','ETRS89',$,'UTM','UTM32',#3);
#2=IFCMAPCONVERSION(#100011,#1,458657.3,5438232.25,113.7,0.270600445976,0.962691746426,0.001);
#3=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#121=IFCLOCALPLACEMENT(#5044,#243);
#149=IFCBUILDINGELEMENTPROXY('0rBru4syGxGOZb$M8kVJuS',#100005,'1G5YB0Is','rcGZcCxu',$,#121,#5037,$,$);
#173=IFCCARTESIANPOINT((0.,0.,0.));
#217=IFCDIRECTION((0.,0.,1.));
#218=IFCDIRECTION((1.,0.,0.));
#243=IFCAXIS2PLACEMENT3D(#173,#217,#218);
#5000=IFCTRIANGULATEDFACESET(#5001,$,.T.,((1,3,2),(1,4,3),(1,5,4),(1,2,5),(2,3,6),(3,7,6),(3,4,7),(4,8,7),(4,5,8),(5,9,8),(5,2,9),(2,6,9),(6,7,8),(6,8,9)),$);
#5001=IFCCARTESIANPOINTLIST3D(((0.,0.,0.),(-500.,-500.,2000.),(500.,-500.,2000.),(500.,500.,2000.),(-500.,500.,2000.),(-500.,-500.,4000.),(500.,-500.,4000.),(500.,500.,4000.),(-500.,500.,4000.)),$);
#5037=IFCPRODUCTDEFINITIONSHAPE('TEqKVmeoo','wLTkVqDs',(#5038));
#5038=IFCSHAPEREPRESENTATION(#100011,'Body','Tessellation',(#5000));
#5043=IFCBUILDINGSTOREY('3_fv_WeK63IPclwapZa9MD',#100005,'oFyqDEMp','U27aC7',$,#5044,$,$,.ELEMENT.,-1.6);
#5044=IFCLOCALPLACEMENT(#100025,#100040);
#5045=IFCRELCONTAINEDINSPATIALSTRUCTURE('2lLFgu3KhSGw4jMC3$Ak50',#100005,'ifb7t1I0','wa0nsIOtz',(#149),#5043);
#5046=IFCRELAGGREGATES('3INy2PAPGPJ8Dpwhrq610o',#100005,'IfunXHpKIg','3jRSpKX',#100023,(#5043));
#100001=IFCPERSON($,'3VqUcnCJ','WJBECA3',$,$,$,$,$);
#100002=IFCORGANIZATION($,'qoFRBW','ih1j7R5',$,$);
#100003=IFCPERSONANDORGANIZATION(#100001,#100002,$);
#100004=IFCAPPLICATION(#100002,'redacted','OPHxMQ15FA','4ho8Ryz');
#100005=IFCOWNERHISTORY(#100003,#100004,$,.NOTDEFINED.,$,$,$,1122650864);
#100010=IFCPROJECT('01JwSt5ycUHvKFlMZUleKS',#100005,'Yu0c77684','MoSwVWh6X4',$,$,$,(#100011),#100060);
#100011=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#100040,$);
#100020=IFCSITE('1BPCQAtTW7GhKrUF$Sytrr',#100005,'EZZYBy','zIXNapcj',$,#100022,$,$,.ELEMENT.,(49,5,43,983700),(8,26,1,247300),113.7,$,$);
#100021=IFCRELAGGREGATES('2vOmTZNmbvJAJRbSGMMNrC',#100005,'s63CrGVO8e','T4hjEFRIZ',#100010,(#100020));
#100022=IFCLOCALPLACEMENT($,#100040);
#100023=IFCBUILDING('0cVTYHAI3nGeTXA2X4Gxyo',#100005,'2x3Nux','rNJKX4hNJ',$,#100025,$,$,.ELEMENT.,$,$,$);
#100024=IFCRELAGGREGATES('1F_GRhxz1GJgGgcJgzjvKX',#100005,'nRsNWiE','V7Jh3YcW',#100020,(#100023));
#100025=IFCLOCALPLACEMENT(#100022,#100040);
#100040=IFCAXIS2PLACEMENT3D(#100041,#100044,#100042);
#100041=IFCCARTESIANPOINT((0.,0.,0.));
#100042=IFCDIRECTION((1.,0.,0.));
#100044=IFCDIRECTION((0.,0.,1.));
#100060=IFCUNITASSIGNMENT((#100061,#100062,#100063,#100064));
#100061=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#100062=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#100063=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#100064=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
ENDSEC;
END-ISO-10303-21;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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_NAME('pass-grf005-scaled_metre_to_US_survey_foot.ifc','2023-01-25T18:40:40',(''),(''),'','IfcOpenShell contributors - IfcOpenShell - v0.7.0+6180d73f','');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;
DATA;
Expand All @@ -27,6 +27,6 @@ DATA;
#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.30480060960121924);
#22=IFCMAPCONVERSION(#11,#21,316131.64,5690966.11,1.,1.,0.,3.2808333333333);
ENDSEC;
END-ISO-10303-21;
Loading