Skip to content

Commit

Permalink
IFC105
Browse files Browse the repository at this point in the history
  • Loading branch information
aothms committed Dec 19, 2024
1 parent 6f4c7ae commit d3d12f0
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 17 deletions.
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'}
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
42 changes: 29 additions & 13 deletions features/steps/givens/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@
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)

try:
instances = context.model.by_type(entity, include_subtypes)
except:
instances = []
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)

try:
instances = context.model.by_type(entity, include_subtypes)
except:
instances = []

context.within_model = getattr(context, 'within_model', True) and within_model
if instances:
Expand Down Expand Up @@ -73,4 +76,17 @@ def step_impl(context, inst, relationship_direction):
def step_impl(context, inst):
# Note that this includes `inst` as the first element in this list
instances = context.model.traverse(inst)
yield ValidationOutcome(instance_id=instances, severity=OutcomeSeverity.PASSED)
yield ValidationOutcome(instance_id=instances, severity=OutcomeSeverity.PASSED)

"""
@gherkin_ifc.step("its entity type is {entity}")
def step_impl(context, inst, entity):
negate = False
entity = entity.split(' ')
if entity[0] == 'not':
negate = not negate
entity = entity[1:]
entity = entity[0]
if inst.is_a(entity):
yield ValidationOutcome(instance_id=inst, severity=OutcomeSeverity.PASSED)
"""
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 @@ -65,10 +65,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

0 comments on commit d3d12f0

Please sign in to comment.