Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
13 changes: 13 additions & 0 deletions features/resources/IFC2X3/valid_ConversionBasedUnits.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Name,UnitType,ConversionFactor,SIUnitPrefix,SIUnitName,Description
inch,LENGTHUNIT,25.4,MILLI,METRE
foot,LENGTHUNIT,304.8,MILLI,METRE
yard,LENGTHUNIT,914.,MILLI,METRE
mile,LENGTHUNIT,1609.,None,METRE
acre,AREAUNIT,4046.86,None,METRE
litre,VOLUMEUNIT,0.001,None,METRE
pint UK,VOLUMEUNIT,0.000568,None,METRE
pint US,VOLUMEUNIT,0.000473,None,METRE
gallon UK,VOLUMEUNIT,0.004546,None,METRE
gallon US,VOLUMEUNIT,0.003785,None,METRE
ounce,MASSUNIT,28.35,None,GRAM
pound,MASSUNIT,0.454,KILO,GRAM
33 changes: 33 additions & 0 deletions features/resources/IFC4/valid_ConversionBasedUnits.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Name,UnitType,ConversionFactor,SIUnitPrefix,SIUnitName,Description
inch,LENGTHUNIT,25.4,MILLI,METRE
foot,LENGTHUNIT,304.8,MILLI,METRE
yard,LENGTHUNIT,914.,MILLI,METRE
mile,LENGTHUNIT,1609.,None,METRE
square inch,AREAUNIT,0.0006452,None,METRE
square foot,AREAUNIT,0.09290,None,METRE
square yard,AREAUNIT,0.83612736,None,METRE
acre,AREAUNIT,4046.86,None,METRE
square mile,AREAUNIT,2588881.,None,METRE
cubic inch,VOLUMEUNIT,0.00001639,None,METRE
cubic foot,VOLUMEUNIT,0.02832,None,METRE
cubic yard,VOLUMEUNIT,0.7636,None,METRE
litre,VOLUMEUNIT,0.001,None,METRE
fluid ounce UK,VOLUMEUNIT,0.0000284130625,None,METRE
fluid ounce US,VOLUMEUNIT,0.00002957353,None,METRE
pint UK,VOLUMEUNIT,0.000568,None,METRE
pint US,VOLUMEUNIT,0.000473,None,METRE
gallon UK,VOLUMEUNIT,0.004546,None,METRE
gallon US,VOLUMEUNIT,0.003785,None,METRE
degree,PLANEANGLEUNIT,π/180,None,RADIAN
ounce,MASSUNIT,28.35,None,GRAM
pound,MASSUNIT,0.454,KILO,GRAM
ton UK,MASSUNIT,1016.0469088,KILO,GRAM,also known as long ton or gross ton or shipper's ton
ton US,MASSUNIT,907.18474,KILO,GRAM,also known as short ton or net ton
lbf,FORCEUNIT,4.4482216153,None,NEWTON,also known as pound-force
kip,FORCEUNIT,4448.2216153,None,NEWTON,also known as kilopound-force
psi,PRESSUREUNIT,6894.7572932,None,PASCAL,also known as pound-force per square inch
ksi,PRESSUREUNIT,6894757.2932,None,PASCAL,also known as kilopound-force per square inch
minute,TIMEUNIT,60.,None,SECOND
hour,TIMEUNIT,3600.,None,SECOND
day,TIMEUNIT,86400.,None,SECOND
btu,ENERGYUNIT,1055.056,None,JOULE, also known as British Thermal Unit
34 changes: 34 additions & 0 deletions features/resources/IFC4X3/valid_ConversionBasedUnits.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Name,UnitType,ConversionFactor,SIUnitPrefix,SIUnitName,Description
inch,LENGTHUNIT,25.4,MILLI,METRE
foot,LENGTHUNIT,304.8,MILLI,METRE
US survey foot,LENGTHUNIT,304.80060960122,MILLI,METRE,the approximate value of 1200/3937 meters
yard,LENGTHUNIT,914.,MILLI,METRE
mile,LENGTHUNIT,1609.,None,METRE
square inch,AREAUNIT,0.0006452,None,METRE
square foot,AREAUNIT,0.09290,None,METRE
square yard,AREAUNIT,0.83612736,None,METRE
acre,AREAUNIT,4046.86,None,METRE
square mile,AREAUNIT,2588881.,None,METRE
cubic inch,VOLUMEUNIT,0.00001639,None,METRE
cubic foot,VOLUMEUNIT,0.02832,None,METRE
cubic yard,VOLUMEUNIT,0.7636,None,METRE
litre,VOLUMEUNIT,0.001,None,METRE
fluid ounce UK,VOLUMEUNIT,0.0000284130625,None,METRE
fluid ounce US,VOLUMEUNIT,0.00002957353,None,METRE
pint UK,VOLUMEUNIT,0.000568,None,METRE
pint US,VOLUMEUNIT,0.000473,None,METRE
gallon UK,VOLUMEUNIT,0.004546,None,METRE
gallon US,VOLUMEUNIT,0.003785,None,METRE
degree,PLANEANGLEUNIT,π/180,None,RADIAN
ounce,MASSUNIT,28.35,None,GRAM
pound,MASSUNIT,0.454,KILO,GRAM
ton UK,MASSUNIT,1016.0469088,KILO,GRAM,also known as long ton or gross ton or shipper's ton
ton US,MASSUNIT,907.18474,KILO,GRAM,also known as short ton or net ton
lbf,FORCEUNIT,4.4482216153,None,NEWTON,also known as pound-force
kip,FORCEUNIT,4448.2216153,None,NEWTON,also known as kilopound-force
psi,PRESSUREUNIT,6894.7572932,None,PASCAL,also known as pound-force per square inch
ksi,PRESSUREUNIT,6894757.2932,None,PASCAL,also known as kilopound-force per square inch
minute,TIMEUNIT,60.,None,SECOND
hour,TIMEUNIT,3600.,None,SECOND
day,TIMEUNIT,86400.,None,SECOND
btu,ENERGYUNIT,1055.056,None,JOULE, also known as British Thermal Unit
22 changes: 22 additions & 0 deletions features/rules/PJS/PJS001_Correct-conversion-based-units.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@implementer-agreement
@PJS
@version1
Feature: PJS001 - Correct conversion based units

The rule verifies that conversion-based units used per Concept Template 4.1.9.9
(https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/concepts/Project_Context/Project_Units/content.html)
have names and corresponding conversion factors per the table of recommended values for each schema version.
IFC 4X3: https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConversionBasedUnit.htm
IFC 4: https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifcmeasureresource/lexical/ifcconversionbasedunit.htm
IFC 2X3: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/FINAL/HTML/ifcmeasureresource/lexical/ifcconversionbasedunit.htm

Background: Selection of conversion-based units in default unit assignment
Given an .IfcConversionBasedUnit.

Scenario: Validating correct names for area, length, and volume units
Given .UnitType. ^is^ 'AREAUNIT' or 'LENGTHUNIT' or 'VOLUMEUNIT' or 'PLANEANGLEUNIT'
Then its attribute .Name. must be defined [according to the table] 'valid_ConversionBasedUnits'

Scenario: Validating correct conversion factors
Then its attribute .ConversionFactor. must be defined [according to the table] 'valid_ConversionBasedUnits'

2 changes: 1 addition & 1 deletion features/steps/steps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from givens import attributes, entities, relationships, values
from thens import alignment, attributes, geometry, nesting, reference, relations, values, existence
from steps import attribute_selection, attribute_value, entity_selection, model_traversal, representation, \
propertysets_and_qtys, crs
propertysets_qtys_units, crs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from dataclasses import dataclass
import functools
import itertools
import json
import operator
import os
import math
import re
from typing import List

import ifcopenshell
from ifcopenshell.util.unit import convert
from validation_handling import gherkin_ifc
from utils import ifc, misc, system
from . import ValidationOutcome, OutcomeSeverity
Expand All @@ -19,6 +22,35 @@
'QTO_OCCURRENCEDRIVEN': 'IfcObject',
}

@dataclass
class ConversionBasedUnitDefinition:
"""
used to hold data from table of conversion-based units defined in the IFC spec
Ref: IFC2X3 - https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifcmeasureresource/lexical/ifcconversionbasedunit.htm
Ref: IFC4 - https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/FINAL/HTML/ifcmeasureresource/lexical/ifcconversionbasedunit.htm
Ref: IFC4X3 - https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConversionBasedUnit.htm
"""
Name: str
UnitType: str
ConversionFactor: str
SIUnitName: str
SIUnitPrefix: str = None
Description: str = None

def __post_init__(self):
# capture π in table definitions
if "π" in self.ConversionFactor:
denom = self.ConversionFactor.split("/")[-1]
self.ConversionFactor = math.pi / float(denom)
if not isinstance(self.ConversionFactor, float):
self.ConversionFactor = float(self.ConversionFactor)
if self.SIUnitPrefix == "None":
self.SIUnitPrefix = None
if self.Description == "None":
self.Description = None
if self.Description and len(self.Description) == 0:
self.Description = None


@functools.cache
def get_predefined_type(inst):
Expand Down Expand Up @@ -62,9 +94,8 @@ def upper_case_if_string(v):
except AttributeError:
return v


@functools.cache
def get_pset_definitions(schema, table):
def get_table_definition(schema, table):
schema_specific_path = system.get_abs_path(f"resources/{schema.upper()}/{table}.csv")

if os.path.exists(schema_specific_path):
Expand All @@ -73,8 +104,11 @@ def get_pset_definitions(schema, table):
tbl_path = system.get_abs_path(f"resources/{table}.csv")

tbl = system.get_csv(tbl_path, return_type='dict')
return {d['property_set_name']: d for d in tbl}

if table == "valid_ConversionBasedUnits":
name_key = "Name"
else:
name_key = "property_set_name"
return {d[name_key]: d for d in tbl}

class AlwaysEqualDict(dict):
"""
Expand Down Expand Up @@ -169,7 +203,7 @@ def normalize_pset(name: str) -> str:
@gherkin_ifc.step(
"The .{inst_type:property_set_or_element_quantity}. attribute .Name. must use standard values [according to the table] '{table}'")
def step_impl(context, inst, table, inst_type=None):
property_set_definitions = get_pset_definitions(context.model.schema, table)
property_set_definitions = get_table_definition(context.model.schema, table)
name = normalize_pset(getattr(inst, 'Name', 'Attribute not found'))

if name not in property_set_definitions.keys():
Expand All @@ -179,7 +213,7 @@ def step_impl(context, inst, table, inst_type=None):
@gherkin_ifc.step(
"Each associated .{inst_type:property_or_physical_quantity}. must be named [according to the table] '{table}'")
def step_impl(context, inst, table, inst_type=None):
property_set_definitions = get_pset_definitions(context.model.schema, table)
property_set_definitions = get_table_definition(context.model.schema, table)
name = normalize_pset(getattr(inst, 'Name', 'Attribute not found'))

accepted_values = establish_accepted_pset_values(name, context.model.schema, table,
Expand Down Expand Up @@ -287,7 +321,7 @@ def schema_has_declaration_name(s):
@gherkin_ifc.step(
"The .{inst_type:property_set_or_element_quantity}. must be related to a valid entity type [according to the table] '{table}'")
def step_impl(context, inst, table, inst_type=None):
property_set_definitions = get_pset_definitions(context.model.schema, table)
property_set_definitions = get_table_definition(context.model.schema, table)
name = normalize_pset(getattr(inst, 'Name', 'Attribute not found'))

accepted_values = establish_accepted_pset_values(name, context.model.schema, table,
Expand All @@ -306,7 +340,7 @@ def step_impl(context, inst, table, inst_type=None):
@gherkin_ifc.step(
"Each associated .{inst_type:property_or_physical_quantity}. must be of valid entity type [according to the table] '{table}'")
def step_impl(context, inst, table, inst_type=None):
property_set_definitions = get_pset_definitions(context.model.schema, table)
property_set_definitions = get_table_definition(context.model.schema, table)
name = normalize_pset(getattr(inst, 'Name', 'Attribute not found'))

accepted_values = establish_accepted_pset_values(name, context.model.schema, table,
Expand Down Expand Up @@ -334,7 +368,7 @@ def step_impl(context, inst, table, inst_type=None):
@gherkin_ifc.step(
"Each associated .{inst_type:property_or_physical_quantity}. value must be of valid data type [according to the table] '{table}'")
def step_impl(context, inst, table, inst_type=None):
property_set_definitions = get_pset_definitions(context.model.schema, table)
property_set_definitions = get_table_definition(context.model.schema, table)
name = normalize_pset(getattr(inst, 'Name', 'Attribute not found'))

accepted_values = establish_accepted_pset_values(name, context.model.schema, table,
Expand Down Expand Up @@ -375,3 +409,27 @@ def step_impl(context, inst, table, inst_type=None):
# not a universal error. This is more IDS territory.
# if not values:
# yield ValidationOutcome(inst=inst, expected= {"oneOf": accepted_data_type['instance']}, observed = {'value':None}, severity=OutcomeSeverity.ERROR)

@gherkin_ifc.step(
"Its attribute .{attr_name}. must be defined [according to the table] 'valid_ConversionBasedUnits'"
)
def step_impl(context, inst, attr_name):
table = "valid_ConversionBasedUnits"
unit_definitions = get_table_definition(context.model.schema, table)
accepted_names = list(unit_definitions.keys())
match attr_name.upper():
case "NAME":
attr_value = getattr(inst, attr_name)
if attr_value not in accepted_names:
yield ValidationOutcome(inst=inst, expected=accepted_names, observed=attr_value, severity=OutcomeSeverity.ERROR)
case "CONVERSIONFACTOR":
unit_name = inst.Name
if unit_name in accepted_names:
inst_factor = inst.ConversionFactor.ValueComponent.wrappedValue
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, one more scenario for a next iteration

We could also check the corresponding measure type of the IfcValue-typed ValueComponent of IfcMeasureWithUnit here. Not sure if this is written constrained somewhere, sometimes you see IFCRATIOMEASURE sometimes you see the measure that corresponds to the Unit, such as IFCTIMEMEASURE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it - I grabbed a link to this comment for future reference.

inst_si_unit = inst.ConversionFactor.UnitComponent
conv_unit_def = ConversionBasedUnitDefinition(**unit_definitions[unit_name])
expected_factor = ifcopenshell.util.unit.convert(value=conv_unit_def.ConversionFactor,from_unit=conv_unit_def.SIUnitName,from_prefix=conv_unit_def.SIUnitPrefix,to_unit=inst_si_unit.Name,to_prefix=inst_si_unit.Prefix)
if not math.isclose(a=inst_factor, b=expected_factor, rel_tol=1e-06, abs_tol=1e-06):
yield ValidationOutcome(inst=inst, expected=expected_factor, observed=inst_factor, severity=OutcomeSeverity.ERROR)
else:
print(f"{unit_name=} not found in table")
4 changes: 4 additions & 0 deletions features/steps/thens/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ def read_csv_values(schema, csv_file):
@gherkin_ifc.step("The {i:value_or_type} must be in '{csv_file}.csv'")
@gherkin_ifc.step("The {i:values_or_types} must be in '{csv_file}.csv'")
def step_impl(context, inst, i, csv_file):
"""
This implementation supports basic reading from CSV resources that have a single field and no header.
It validates a value against a single field, but does not support CSV resources with multiple fields per row.
"""
if not inst:
return []

Expand Down
3 changes: 3 additions & 0 deletions features/steps/validation_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ def apply_then_operation(fn, inst, context, current_path, depth=0, **kwargs):
if 'npath' in inspect.getargs(fn.__code__).args:
kwargs = kwargs | {'npath': current_path}
top_level_index = current_path[0] if current_path else None
max_index = len(current_path) - 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly related to my comment on #468

We should not be doing this, because in this way we're linking the wrong instance to the outcome presentation. We just really should make sure that the stack of values we build forms a proper tree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something needs to be done because this feature file generates an IndexError as-is. The code tries to access element at index 1 when there is a single value in the list. I'll review your comment for clues. Maybe the Given statement needs to be revised.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the IndexError makes sense in #468 based on the Given statement. I'm working through that now & will use lessons learned to resolve this PR.

if top_level_index > max_index:
top_level_index = max_index
activation_inst = inst if not current_path or activation_instances[top_level_index] is None else activation_instances[top_level_index]
# TODO: refactor into a more general solution that works for all rules
if context.is_global_rule and (
Expand Down
33 changes: 33 additions & 0 deletions test/files/PJS/pjs001/fail-pjs001-scenario01-degree_ifc2x3.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView_V2.0]'),'2;1');
FILE_NAME('fail-pjs001-scenario01-degree_ifc2x3.ifc','2025-11-18T20:39:40-05:00',(''),(''),'IfcOpenShell 0.0.0','redacted - redacted - 3.14159','Nobody');
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,'0.0.0','IfcOpenShell','IfcOpenShell');
#5=IFCPERSON('FW','Whalbanger','Frank',$,$,$,$,$);
#6=IFCORGANIZATION('SBB','Soggy Bottom Boys',$,$,$);
#7=IFCPERSONANDORGANIZATION(#5,#6,$);
#8=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1763516380,#7,#4,1763516380);
#9=IFCPROJECT('3qYCxaONHFGe86oex3KHpw',#8,'PJS001 Unit Test',$,$,$,$,(#14),#28);
#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',$,*,*,*,*,#14,$,.MODEL_VIEW.,$);

/* degree definition */
#16=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#17=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#18=IFCMEASUREWITHUNIT(IFCREAL(0.017453292519943295),#17);
#19=IFCCONVERSIONBASEDUNIT(#16,.PLANEANGLEUNIT.,'degree',#18);

/* unit assignment */
#28=IFCUNITASSIGNMENT((#19));
ENDSEC;
END-ISO-10303-21;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView_V2.0]'),'2;1');
FILE_NAME('fail-pjs001-fluid_oz_uk_ifc2x3.ifc','2025-11-18T20:39:40-05:00',(''),(''),'IfcOpenShell 0.0.0','redacted - redacted - 3.14159','Nobody');
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,'0.0.0','IfcOpenShell','IfcOpenShell');
#5=IFCPERSON('FW','Whalbanger','Frank',$,$,$,$,$);
#6=IFCORGANIZATION('SBB','Soggy Bottom Boys',$,$,$);
#7=IFCPERSONANDORGANIZATION(#5,#6,$);
#8=IFCOWNERHISTORY(#7,#4,.READWRITE.,.ADDED.,1763516380,#7,#4,1763516380);
#9=IFCPROJECT('3qYCxaONHFGe86oex3KHpw',#8,'PJS001 Unit Test',$,$,$,$,(#14),#28);
#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',$,*,*,*,*,#14,$,.MODEL_VIEW.,$);

/* volume unit definition */
#16=IFCDIMENSIONALEXPONENTS(3,0,0,0,0,0,0);
#17=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#18=IFCMEASUREWITHUNIT(IFCREAL(0.0000284130625),#17);
#19=IFCCONVERSIONBASEDUNIT(#16,.VOLUMEUNIT.,'fluid ounce UK',#18);

#28=IFCUNITASSIGNMENT((#19));
ENDSEC;
END-ISO-10303-21;
21 changes: 21 additions & 0 deletions test/files/PJS/pjs001/fail-pjs001-scenario01-furlong_ifc4x3.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [Alignment-basedView]'),'2;1');
FILE_NAME('fail-pjs001-scenario01-furlong_ifc4x3.ifc','2025-11-18T20:39:40-05:00',(''),(''),'IfcOpenShell 0.0.0','redacted - redacted - 3.14159','Nobody');
FILE_SCHEMA(('IFC4X3_ADD2'));
ENDSEC;
DATA;
#1=IFCPROJECT('0k7XaScqT91O1q2GuOZQU8',$,'PJS001 Unit Test',$,$,$,$,(#6),#20);
#2=IFCCARTESIANPOINT((0.,0.,0.));
#3=IFCDIRECTION((0.,0.,1.));
#4=IFCDIRECTION((1.,0.,0.));
#5=IFCAXIS2PLACEMENT3D(#2,#3,#4);
#6=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#5,$);
#7=IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body',$,*,*,*,*,#6,$,.MODEL_VIEW.,$);
#8=IFCDIMENSIONALEXPONENTS(1,0,0,0,0,0,0);
#9=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#10=IFCMEASUREWITHUNIT(IFCREAL(201.168),#9);
#11=IFCCONVERSIONBASEDUNIT(#8,.LENGTHUNIT.,'furlong',#10);
#20=IFCUNITASSIGNMENT((#11));
ENDSEC;
END-ISO-10303-21;
Loading