Skip to content

Commit

Permalink
Merge pull request #329 from buildingSMART/ivs99-ifc105-resource-enti…
Browse files Browse the repository at this point in the history
…ties-etc

IVS-99 IFC105 resource entities ...
  • Loading branch information
civilx64 authored Jan 25, 2025
2 parents 397dc9f + 9b17b80 commit c4a2144
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@IFC
@version1
@E00030
@implementer-agreement
Feature: IFC105 - Resource entities need to be referenced by rooted entity

The rule verifies that resource entities are directly or indirectly related to at least one rooted entity instance by means of forward or inverse attributes.
Resource entities are the schema classes that do not inherit from IfcRoot, typically defined in the resource layer of the schema (e.g Geometry Resource).

Scenario: Resource entities need to be referenced by rooted entity

Given a traversal over the full model originating from subtypes of IfcRoot
Given an entity instance
Given its entity type is not 'IfcRoot'
Then it must be referenced by an entity instance inheriting from IfcRoot directly or indirectly
29 changes: 29 additions & 0 deletions features/steps/givens/attributes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ast
import functools
import operator

import ifcopenshell
Expand Down Expand Up @@ -147,6 +148,34 @@ def step_impl(context, file_or_model, field, values):
context.applicable = getattr(context, 'applicable', True) and applicable


@gherkin_ifc.step('a traversal over the full model originating from subtypes of {entity}')
def step_impl(context, entity):
WHITELISTED_INVERSES = {'StyledByItem', 'HasCoordinateOperation'}
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(context.model.schema_identifier)
@functools.cache
def names(entity_type):
decl = schema.declaration_by_name(entity_type)
if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity):
non_derived_forward_attributes = map(operator.itemgetter(1), filter(lambda t: not t[0], zip(decl.derived(), decl.all_attributes())))
whitelisted_inverse_attributes = filter(lambda attr: attr.name() in WHITELISTED_INVERSES, decl.all_inverse_attributes())
return {a.name() for a in [*non_derived_forward_attributes, *whitelisted_inverse_attributes]}
else:
return set()

visited = set()
def visit(inst, path=None):
if inst in visited:
return
visited.add(inst)
for attr in names(inst.is_a()):
for ref in filter(lambda inst: isinstance(inst, ifcopenshell.entity_instance), misc.iflatten(getattr(inst, attr))):
visit(ref, (path or ()) + (inst, attr,))

for inst in context.model.by_type(entity):
visit(inst)

context.visited_instances = visited

@gherkin_ifc.step('Its attribute {attribute}')
def step_impl(context, inst, attribute, tail="single"):
yield ValidationOutcome(instance_id=getattr(inst, attribute, None), severity=OutcomeSeverity.PASSED)
Expand Down
20 changes: 11 additions & 9 deletions features/steps/givens/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@
def step_impl(context, entity_opt_stmt, insts=False):
within_model = (insts == 'instances') # True for given statement containing {insts}

entity2 = pyparsing.Word(pyparsing.alphas)('entity')
sub_stmts = ['with subtypes', 'without subtypes', pyparsing.LineEnd()]
incl_sub_stmt = functools.reduce(operator.or_, [misc.rtrn_pyparse_obj(i) for i in sub_stmts])('include_subtypes')
grammar = entity2 + incl_sub_stmt
parse = grammar.parseString(entity_opt_stmt)
entity = parse['entity']
include_subtypes = misc.do_try(lambda: not 'without' in parse['include_subtypes'], True)

instances = context.model.by_type(entity, include_subtypes) or []
if entity_opt_stmt == "entity instance":
instances = list(context.model)
else:
entity2 = pyparsing.Word(pyparsing.alphas)('entity')
sub_stmts = ['with subtypes', 'without subtypes', pyparsing.LineEnd()]
incl_sub_stmt = functools.reduce(operator.or_, [misc.rtrn_pyparse_obj(i) for i in sub_stmts])('include_subtypes')
grammar = entity2 + incl_sub_stmt
parse = grammar.parseString(entity_opt_stmt)
entity = parse['entity']
include_subtypes = misc.do_try(lambda: not 'without' in parse['include_subtypes'], True)
instances = context.model.by_type(entity, include_subtypes) or []

context.within_model = getattr(context, 'within_model', True) and within_model
if instances:
Expand Down
11 changes: 10 additions & 1 deletion features/steps/thens/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,13 @@ def schema_has_declaration_name(s):
# @tfk not sure about this one, but for now empty values on a property are probably
# 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)
# yield ValidationOutcome(inst=inst, expected= {"oneOf": accepted_data_type['instance']}, observed = {'value':None}, severity=OutcomeSeverity.ERROR)

@gherkin_ifc.step('it must be referenced by an entity instance inheriting from IfcRoot directly or indirectly')
def step_impl(context, inst):
# context.visited_instances is set in the gherkin statement:
# 'Given a traversal over the full model originating from subtypes of IfcRoot'
assert hasattr(context, 'visited_instances')

if inst not in context.visited_instances:
yield ValidationOutcome(inst=inst, severity=OutcomeSeverity.ERROR)
8 changes: 8 additions & 0 deletions features/steps/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ def recursive_flatten(lst):
return flattened_list


def iflatten(any):
if isinstance(any, (tuple, list)):
for v in any:
yield from iflatten(v)
else:
yield any


def do_try(fn, default=None):
try:
return fn()
Expand Down
5 changes: 2 additions & 3 deletions features/steps/validation_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ class SubtypeHandling(Enum):

def generate_error_message(context, errors):
"""
Function to trigger the behave error mechanism so that the JSON output is generated correctly.
Miscellaneous errors also are also printed to the console this way.
Function to trigger the behave error mechanism by raising an exception so that errors are printed to the console.
"""
assert not errors, "Behave errors occured:\n{}".format([str(error) for error in errors])
assert not errors, "Errors occured:" + ''.join(f'\n - {error}' for error in errors)


"""
Expand Down
72 changes: 72 additions & 0 deletions test/files/ifc105/fail-ifc105-product_representation.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2022-11-08T16:54:40',(''),(''),'IfcOpenShell-v0.7.0-fa6bbf2d','IfcOpenShell-v0.7.0-fa6bbf2d','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'v0.7.0-fa6bbf2d','IfcOpenShell-v0.7.0-fa6bbf2d','');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1667926480);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.,0.));
#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('3KwPrT3OP4Gg7qpalgQO1_',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINT((0.,0.,0.));
#22=IFCDIRECTION((0.,0.,1.));
#23=IFCDIRECTION((1.,0.,0.));
#24=IFCAXIS2PLACEMENT3D(#21,#22,#23);
#25=IFCLOCALPLACEMENT($,#24);
#26=IFCSITE('098$knNuOHxPTbB6t11Jwi',#5,'Site',$,$,#25,$,$,.ELEMENT.,$,$,$,$,$);
#27=IFCCARTESIANPOINT((0.,0.,0.));
#28=IFCDIRECTION((0.,0.,1.));
#29=IFCDIRECTION((1.,0.,0.));
#30=IFCAXIS2PLACEMENT3D(#27,#28,#29);
#31=IFCLOCALPLACEMENT(#25,#30);
#32=IFCBUILDING('098$koNuOHxPGoB6t11Jwi',#5,'Building',$,$,#31,$,$,.ELEMENT.,$,$,$);
#33=IFCCARTESIANPOINT((0.,0.,0.));
#34=IFCDIRECTION((0.,0.,1.));
#35=IFCDIRECTION((1.,0.,0.));
#36=IFCAXIS2PLACEMENT3D(#33,#34,#35);
#37=IFCLOCALPLACEMENT(#31,#36);
#38=IFCBUILDINGSTOREY('098$kpNuOHxQvuB6t11Jwi',#5,'Storey',$,$,#37,$,$,.ELEMENT.,0.);
#39=IFCRELAGGREGATES('098$kqNuOHxRbKB6t11Jwi',#5,'Building Container',$,#32,(#38));
#40=IFCRELAGGREGATES('098$krNuOHxQdqB6t11Jwi',#5,'Site Container',$,#26,(#32));
#41=IFCRELAGGREGATES('098$ksNuOHxO9OB6t11Jwi',#5,'Project Container',$,#20,(#26));
#42=IFCCARTESIANPOINT((0.,0.,0.));
#43=IFCDIRECTION((0.,0.,1.));
#44=IFCDIRECTION((1.,0.,0.));
#45=IFCAXIS2PLACEMENT3D(#42,#43,#44);
#47=IFCCARTESIANPOINT((0.,0.,0.));
#48=IFCCARTESIANPOINT((5.,0.,0.));
#49=IFCPOLYLINE((#47,#48));
#50=IFCCARTESIANPOINT((0.,0.,0.));
#51=IFCDIRECTION((0.,0.,1.));
#52=IFCDIRECTION((1.,0.,0.));
#53=IFCAXIS2PLACEMENT3D(#50,#51,#52);
#54=IFCCARTESIANPOINT((0.,-0.1));
#55=IFCCARTESIANPOINT((5.,-0.1));
#56=IFCCARTESIANPOINT((10.,0.1));
#57=IFCCARTESIANPOINT((0.,0.1));
#58=IFCCARTESIANPOINT((0.,-0.1));
#59=IFCPOLYLINE((#54,#55,#56,#57,#58));
#60=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#59);
#61=IFCDIRECTION((0.,0.,1.));
#62=IFCEXTRUDEDAREASOLID(#60,#53,#61,3.);
#63=IFCSHAPEREPRESENTATION(#11,'Body','SweptSolid',(#62));
#64=IFCREPRESENTATIONMAP(#53,#63);
ENDSEC;
END-ISO-10303-21;
72 changes: 72 additions & 0 deletions test/files/ifc105/pass-ifc105-product_representation.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2022-11-08T16:54:40',(''),(''),'IfcOpenShell-v0.7.0-fa6bbf2d','IfcOpenShell-v0.7.0-fa6bbf2d','');
FILE_SCHEMA(('IFC2X3'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'v0.7.0-fa6bbf2d','IfcOpenShell-v0.7.0-fa6bbf2d','');
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1667926480);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.,0.));
#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('3KwPrT3OP4Gg7qpalgQO1_',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINT((0.,0.,0.));
#22=IFCDIRECTION((0.,0.,1.));
#23=IFCDIRECTION((1.,0.,0.));
#24=IFCAXIS2PLACEMENT3D(#21,#22,#23);
#25=IFCLOCALPLACEMENT($,#24);
#26=IFCSITE('098$knNuOHxPTbB6t11Jwi',#5,'Site',$,$,#25,$,$,.ELEMENT.,$,$,$,$,$);
#27=IFCCARTESIANPOINT((0.,0.,0.));
#28=IFCDIRECTION((0.,0.,1.));
#29=IFCDIRECTION((1.,0.,0.));
#30=IFCAXIS2PLACEMENT3D(#27,#28,#29);
#31=IFCLOCALPLACEMENT(#25,#30);
#32=IFCBUILDING('098$koNuOHxPGoB6t11Jwi',#5,'Building',$,$,#31,$,$,.ELEMENT.,$,$,$);
#33=IFCCARTESIANPOINT((0.,0.,0.));
#34=IFCDIRECTION((0.,0.,1.));
#35=IFCDIRECTION((1.,0.,0.));
#36=IFCAXIS2PLACEMENT3D(#33,#34,#35);
#37=IFCLOCALPLACEMENT(#31,#36);
#38=IFCBUILDINGSTOREY('098$kpNuOHxQvuB6t11Jwi',#5,'Storey',$,$,#37,$,$,.ELEMENT.,0.);
#39=IFCRELAGGREGATES('098$kqNuOHxRbKB6t11Jwi',#5,'Building Container',$,#32,(#38));
#40=IFCRELAGGREGATES('098$krNuOHxQdqB6t11Jwi',#5,'Site Container',$,#26,(#32));
#41=IFCRELAGGREGATES('098$ksNuOHxO9OB6t11Jwi',#5,'Project Container',$,#20,(#26));
#4100=IFCRELAGGREGATES('098$ksNuOHxO9OB6t11Jwj',#5,'Storey Container',$,#38,(#65));
#42=IFCCARTESIANPOINT((0.,0.,0.));
#43=IFCDIRECTION((0.,0.,1.));
#44=IFCDIRECTION((1.,0.,0.));
#45=IFCAXIS2PLACEMENT3D(#42,#43,#44);
#46=IFCLOCALPLACEMENT(#37,#45);
#50=IFCCARTESIANPOINT((0.,0.,0.));
#51=IFCDIRECTION((0.,0.,1.));
#52=IFCDIRECTION((1.,0.,0.));
#53=IFCAXIS2PLACEMENT3D(#50,#51,#52);
#54=IFCCARTESIANPOINT((0.,-0.1));
#55=IFCCARTESIANPOINT((5.,-0.1));
#56=IFCCARTESIANPOINT((10.,0.1));
#57=IFCCARTESIANPOINT((0.,0.1));
#58=IFCCARTESIANPOINT((0.,-0.1));
#59=IFCPOLYLINE((#54,#55,#56,#57,#58));
#60=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#59);
#61=IFCDIRECTION((0.,0.,1.));
#62=IFCEXTRUDEDAREASOLID(#60,#53,#61,3.);
#63=IFCSHAPEREPRESENTATION(#11,'Body','SweptSolid',(#62));
#64=IFCPRODUCTDEFINITIONSHAPE($,$,(#63));
#65=IFCSPACE('098$ktNuOHxOUOB6t11Jwi',#5,'space','An awesome space',$,#46,#64,$,.ELEMENT.,.INTERNAL.,$);
ENDSEC;
END-ISO-10303-21;
71 changes: 71 additions & 0 deletions test/files/ifc105/pass-ifc105-with_georeferencing.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
ISO-10303-21;
HEADER;

FILE_DESCRIPTION(
/* description */ ('GeoReference'),
/* implementation level */ '2;1');
FILE_NAME(
/* name */ 'pass-ifc105-with_referencing.ifc',
/* time_stamp */ '2017-07-10T15:03:00',
/* author */ ('redacted'),
/* organization */ ('redacted'),
/* preprocessor_version */ 'redacted',
/* originating_system */ '',
/* authorisation */ 'none');

FILE_SCHEMA (('IFC4X3_ADD2'));
ENDSEC;
DATA;
/* Geographic reference*/
#1= IFCPROJECTEDCRS('EPSG:31467','DHDN / 3-Degree Gauss-Krueger Zone 3','ETRS89',$,'Gaus-Krueger','3',#3);
#2= IFCMAPCONVERSION(#100011,#1,3458715.92,5439966.65,113.7,0.270600445976,0.962691746426,$);
#3= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);


/* BuildingElementProxy */
#149= IFCBUILDINGELEMENTPROXY('0rBru4syGxGOZb$M8kVJuS',#100005,'Geographic Position',$,$,#121,#5037,$,$);
#121= IFCLOCALPLACEMENT(#5044,#243);
#243= IFCAXIS2PLACEMENT3D(#173,#217,#218);
#173= IFCCARTESIANPOINT((0.,0.,0.));
#217= IFCDIRECTION((0.,0.,1.));
#218= IFCDIRECTION((1.,0.,0.));
#5037= IFCPRODUCTDEFINITIONSHAPE('',$,(#5038));
#5038= IFCSHAPEREPRESENTATION(#100011,'Body','Tessellation',(#5000));
/* Tesselated geometry */
#5000= IFCTRIANGULATEDFACESET(#5001,$,.T.,((1,3,2),(1,4,3),(1,5,4),(1,2,5),/*(2,3,4),(4,5,2),*/(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.0,0.0),(-500.0,-500.0,2000.0),(500.0,-500.0,2000.0),(500.0,500.0,2000.0),(-500.0,500.0,2000.0),(-500.0,-500.0,4000.0),(500.0,-500.0,4000.0),(500.0,500.0,4000.0),(-500.0,500.0,4000.0)),$);
/* BuildingStorey */
#5043= IFCBUILDINGSTOREY('3_fv_WeK63IPclwapZa9MD',#100005,'Storey 1',$,$,#5044,$,$,.ELEMENT.,0.);
#5044= IFCLOCALPLACEMENT(#100025,#100040);
#5045= IFCRELCONTAINEDINSPATIALSTRUCTURE('2lLFgu3KhSGw4jMC3$Ak50',#100005,'Storey 1',$,(#149),#5043);
#5046= IFCRELAGGREGATES('3INy2PAPGPJ8Dpwhrq610o',#100005,'All stories',$,#100023,(#5043));
/* Person, Org, App, Project */

#100001= IFCPERSON($,'redacted',$,$,$,$,$,$);
#100002= IFCORGANIZATION($,'redacted',$,$,$);
#100003= IFCPERSONANDORGANIZATION(#100001,#100002,$);
#100004= IFCAPPLICATION(#100002,'redacted','redacted','redacted');
#100005= IFCOWNERHISTORY(#100003,#100004,$,.NOTDEFINED.,$,$,$,1122650864);
#100010= IFCPROJECT('01JwSt5ycUHvKFlMZUleKS',#100005,'Notch',$,$,$,$,(#100011),#100060);
#100011= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.0E-5,#100040,$);
/* Site */
#100020= IFCSITE('1BPCQAtTW7GhKrUF$Sytrr',#100005,'Site',$,$,#100022,$,$,.ELEMENT.,(49,5,43,983700),(8,26,1,247300),113.7,$,$);
#100021= IFCRELAGGREGATES('2vOmTZNmbvJAJRbSGMMNrC',#100005,$,$,#100010,(#100020));
#100022= IFCLOCALPLACEMENT($,#100040);
/* Building */
#100023= IFCBUILDING('0cVTYHAI3nGeTXA2X4Gxyo',#100005,'Building',$,$,#100025,$,$,.ELEMENT.,$,$,$);
#100024= IFCRELAGGREGATES('1F_GRhxz1GJgGgcJgzjvKX',#100005,$,$,#100020,(#100023));
#100025= IFCLOCALPLACEMENT(#100022,#100040);
/* Placement */
#100040= IFCAXIS2PLACEMENT3D(#100041,#100044,#100042);
#100041= IFCCARTESIANPOINT((0.,0.,0.));
#100042= IFCDIRECTION((1.,0.,0.));
#100044= IFCDIRECTION((0.,0.,1.));
/* Units */
#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;

0 comments on commit c4a2144

Please sign in to comment.