Skip to content

Commit

Permalink
Merge branch 'main' into ri09_alignment-segment-shape-representation
Browse files Browse the repository at this point in the history
  • Loading branch information
aothms authored Sep 20, 2023
2 parents bb4a7f3 + c9e7bb2 commit be0f59e
Show file tree
Hide file tree
Showing 23 changed files with 3,532 additions and 70 deletions.
3 changes: 2 additions & 1 deletion features/ALB001_Alignment-in-spatial-structure.feature
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@disabled
@implementer-agreement
@ALB
Feature: ALB001 - Alignment in spatial structure
The rule verifies, that each IfcAlignment is contained in an IfcSite.
Expand All @@ -7,4 +8,4 @@ The rule verifies, that each IfcAlignment is contained in an IfcSite.

Given A file with Schema Identifier "IFC4X3"
And An IfcAlignment
Then Each IfcAlignment must be directly contained in IfcSite
Then Each IfcAlignment must be directly contained in IfcSite
15 changes: 15 additions & 0 deletions features/ALS003_Alignment-cant-shape-representation.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@implementer-agreement
@ALS
@disabled
Feature: ALS003 - Alignment cant shape representation
The rule verifies that each IfcAlignmentCant uses correct representation.

Scenario: Agreement on each IfcAlignmentCant using correct representation

Given A file with Schema Identifier "IFC4X3_TC1" or "IFC4X3_ADD1" or "IFC4X3"
And An IfcAlignmentCant
And Its attribute Representation
And Its attribute Representations
Then The value of attribute RepresentationIdentifier must be Axis
And The value of attribute RepresentationType must be Curve3D
And The type of attribute Items must be IfcSegmentedReferenceCurve
2 changes: 1 addition & 1 deletion features/GRF001_Identical-coordinate-operations.feature
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ IfcMapConversion in one representation context and IfcRigidOperation in another.

Scenario: IfcGeometricRepresentationContext

Given A file with Schema Identifier "IFC4X3" or "IFC4X3_TC1" or "IFC4X3_ADD1" or "IFC4x1"
Given A file with Schema Identifier "IFC4X3" or "IFC4X3_TC1" or "IFC4X3_ADD1"
And All instances of IfcGeometricRepresentationContext without subtypes
And Its Attribute HasCoordinateOperation
And Its values excluding SourceCRS
Expand Down
7 changes: 5 additions & 2 deletions features/environment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ifcopenshell

import sys
from behave.model import Scenario

model_cache = {}
Expand All @@ -14,7 +14,10 @@ def before_feature(context, feature):
# between features so we need to preserve only the bottom two stack
# frames when beginning a new feature.
context._stack = context._stack[-2:]

if "error_on_passed_rule" in context.config.userdata:
context.error_on_passed_rule = context.config.userdata["error_on_passed_rule"] == 'yes'
else:
context.error_on_passed_rule = False
context.model = read_model(context.config.userdata["input"])
Scenario.continue_after_failed_step = True

Expand Down
48 changes: 33 additions & 15 deletions features/steps/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,27 @@
from typing import Union
from utils import geometry, ifc, misc, system

@dataclass
class RuleState:
rule_passed: bool

@dataclass
class RuleSuccessInsts(RuleState):
insts: ifcopenshell.entity_instance

def __str__(self):
return f"The instance {self.insts} has passed the step criteria"

@dataclass
class RuleSuccessInst(RuleState):
inst: ifcopenshell.entity_instance

def __str__(self):
return f"The instance {self.inst} has passed the step criteria"


@dataclass
class AttributeTypeError:
class AttributeTypeError(RuleState):
inst: ifcopenshell.entity_instance
related: Union[Sequence, ifcopenshell.entity_instance]
attribute: str
Expand All @@ -18,11 +36,11 @@ def __str__(self):
if len(self.related):
return f"The instance {self.inst} expected type '{self.expected_entity_type}' for the attribute {self.attribute}, but found {misc.fmt(self.related)} "
else:
return f"This instance {self.inst} has no value for attribute {self.attribute}"
return f"The instance {self.inst} has no value for attribute {self.attribute}"


@dataclass
class DuplicateValueError:
class DuplicateValueError(RuleState):
inst: ifcopenshell.entity_instance
incorrect_values: typing.Sequence[typing.Any]
attribute: str
Expand All @@ -39,7 +57,7 @@ def __str__(self):


@dataclass
class EdgeUseError:
class EdgeUseError(RuleState):
inst: ifcopenshell.entity_instance
edge: typing.Any
count: int
Expand All @@ -49,7 +67,7 @@ def __str__(self):


@dataclass
class IdenticalValuesError:
class IdenticalValuesError(RuleState):
insts: typing.Sequence[ifcopenshell.entity_instance]
incorrect_values: typing.Sequence[typing.Any]
attribute: str
Expand All @@ -63,7 +81,7 @@ def __str__(self):


@dataclass
class InstanceCountError:
class InstanceCountError(RuleState):
insts: ifcopenshell.entity_instance
type_name: str

Expand All @@ -75,7 +93,7 @@ def __str__(self):


@dataclass
class InstancePlacementError:
class InstancePlacementError(RuleState):
entity: ifcopenshell.entity_instance
placement: str
container: Union[str, ifcopenshell.entity_instance]
Expand All @@ -92,7 +110,7 @@ def __str__(self):


@dataclass
class InstanceStructureError:
class InstanceStructureError(RuleState):
# @todo reverse order to relating -> nest-relationship -> related
related: ifcopenshell.entity_instance
relating: Union[Sequence, ifcopenshell.entity_instance]
Expand All @@ -110,17 +128,17 @@ def __str__(self):


@dataclass
class InvalidValueError:
related: ifcopenshell.entity_instance
class InvalidValueError(RuleState):
inst: ifcopenshell.entity_instance
attribute: str
value: str

def __str__(self):
return f"On instance {misc.fmt(self.related)} the following invalid value for {self.attribute} has been found: {self.value}"
return f"On instance {misc.fmt(self.inst)} the following invalid value for {self.attribute} has been found: {self.value}"


@dataclass
class PolyobjectDuplicatePointsError:
class PolyobjectDuplicatePointsError(RuleState):
inst: ifcopenshell.entity_instance
duplicates: set

Expand All @@ -133,7 +151,7 @@ def __str__(self):


@dataclass
class PolyobjectPointReferenceError:
class PolyobjectPointReferenceError(RuleState):
inst: ifcopenshell.entity_instance
points: list

Expand All @@ -142,7 +160,7 @@ def __str__(self):


@dataclass
class RepresentationShapeError:
class RepresentationShapeError(RuleState):
inst: ifcopenshell.entity_instance
representation_id: str

Expand All @@ -151,7 +169,7 @@ def __str__(self):


@dataclass
class RepresentationTypeError:
class RepresentationTypeError(RuleState):
inst: ifcopenshell.entity_instance
representation_id: str
representation_type: str
Expand Down
44 changes: 33 additions & 11 deletions features/steps/thens/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,19 @@ def step_impl(context, entity, other_entity, relationship):
if not entity_obj_placement_rel:
entity_obj_placement_rel = 'Not found'
if not is_correct:
errors.append(err.InstancePlacementError(related_object, '', relating_object, relationship, relating_obj_placement, entity_obj_placement_rel))
errors.append(err.InstancePlacementError(False, related_object, '', relating_object, relationship, relating_obj_placement, entity_obj_placement_rel))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccess(True, relating_object))

misc.handle_errors(context, errors)


@then('The {representation_id} shape representation has RepresentationType "{representation_type}"')
def step_impl(context, representation_id, representation_type):
errors = list(filter(None, list(map(lambda i: ifc.instance_getter(i, representation_id, representation_type, 1), context.instances))))
errors = [err.RepresentationTypeError(error, representation_id, representation_type) for error in errors]

errors = [err.RepresentationTypeError(False, error, representation_id, representation_type) for error in errors]
if not errors and context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, representation_id))
misc.handle_errors(context, errors)


Expand All @@ -56,7 +59,9 @@ def step_impl(context, entity, other_entity):
errors = []
for obj in context.instances:
if not misc.do_try(lambda: obj.ObjectPlacement.is_a(other_entity), False):
errors.append(err.InstancePlacementError(obj, other_entity, "", "", "", ""))
errors.append(err.InstancePlacementError(False, obj, other_entity, "", "", "", ""))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, obj))

misc.handle_errors(context, errors)

Expand All @@ -66,13 +71,16 @@ def step_impl(context, attribute, expected_entity_type):

def _():
for inst in context.instances:
if isinstance(inst, (tuple, list)):
inst = inst[0]
related_entity = getattr(inst, attribute, [])
if isinstance(related_entity, (tuple, list)):
related_entity = related_entity[0]
if isinstance(related_entity, (tuple, list)) or (not related_entity.is_a(expected_entity_type)):
yield err.AttributeTypeError(inst, [related_entity], attribute, expected_entity_type)
related_entity = misc.map_state(inst, lambda i: getattr(i, attribute, None))
errors = []
def accumulate_errors(i):
if not i.is_a(expected_entity_type):
misc.map_state(inst, lambda x: errors.append(err.AttributeTypeError(False, x, [i], attribute, expected_entity_type)))
misc.map_state(related_entity, accumulate_errors)
if errors:
yield from errors
elif context.error_on_passed_rule:
yield err.RuleSuccessInst(True, related_entity)

misc.handle_errors(context, list(_()))

Expand All @@ -89,3 +97,17 @@ def step_impl(context, attribute, value):

misc.handle_errors(context, errors)

@then('The value of attribute {attribute} must be {value}')
def step_impl(context, attribute, value):
if getattr(context, 'applicable', True):
errors = []
for inst in context.instances:
if isinstance(inst, (tuple, list)):
inst = inst[0]
attribute_value = getattr(inst, attribute, 'Attribute not found')
if attribute_value != value:
errors.append(err.InvalidValueError(False, inst, attribute, attribute_value))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))

misc.handle_errors(context, errors)
10 changes: 6 additions & 4 deletions features/steps/thens/existance.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ def step_impl(context, representation_id):
if inst.Representation:
present = representation_id in map(operator.attrgetter('RepresentationIdentifier'), inst.Representation.Representations)
if not present:
errors.append(err.RepresentationShapeError(inst, representation_id))

errors.append(err.RepresentationShapeError(False, inst, representation_id))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))
misc.handle_errors(context, errors)


Expand All @@ -26,6 +27,7 @@ def step_impl(context, constraint, num, entity):
if getattr(context, 'applicable', True):
insts = context.model.by_type(entity)
if not op(len(insts), num):
errors.append(err.InstanceCountError(insts, entity))

errors.append(err.InstanceCountError(False, insts, entity))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInsts(True, insts))
misc.handle_errors(context, errors)
7 changes: 5 additions & 2 deletions features/steps/thens/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@then("It must have no duplicate points {clause} first and last point")
def step_impl(context, clause):
assert clause in ('including', 'excluding')
emitted_one_passing = False
if getattr(context, 'applicable', True):
errors = []
for instance in context.instances:
Expand All @@ -25,8 +26,10 @@ def step_impl(context, clause):
break
comparison_nr += 1
if duplicates:
errors.append(err.PolyobjectDuplicatePointsError(instance, duplicates))

errors.append(err.PolyobjectDuplicatePointsError(False, instance, duplicates))
elif context.error_on_passed_rule and not emitted_one_passing:
errors.append(err.RuleSuccessInst(True, instance))
emitted_one_passing = True
misc.handle_errors(context, errors)


20 changes: 13 additions & 7 deletions features/steps/thens/nesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def step_impl(context, entity, num, constraint, other_entity):
for inst in context.model.by_type(entity):
nested_entities = [entity for rel in inst.IsNestedBy for entity in rel.RelatedObjects]
if not op(len([1 for i in nested_entities if i.is_a(other_entity)]), num):
errors.append(err.InstanceStructureError(inst, [i for i in nested_entities if i.is_a(other_entity)], 'nested by'))
errors.append(err.InstanceStructureError(False, inst, [i for i in nested_entities if i.is_a(other_entity)], 'nested by'))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))

misc.handle_errors(context, errors)

Expand All @@ -35,7 +37,9 @@ def step_impl(context, entity, other_entities):
nested_entity_types = set(i.is_a() for i in nested_entities)
if not nested_entity_types <= allowed_entity_types:
differences = list(nested_entity_types - allowed_entity_types)
errors.append(err.InstanceStructureError(inst, [i for i in nested_entities if i.is_a() in differences], 'nested by'))
errors.append(err.InstanceStructureError(False, inst, [i for i in nested_entities if i.is_a() in differences], 'nested by'))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))

misc.handle_errors(context, errors)

Expand All @@ -61,6 +65,7 @@ def step_impl(context, entity, fragment, other_entity):

if getattr(context, 'applicable', True):
for inst in context.model.by_type(entity):
amount_of_errors = len(errors)
related_entities = list(map(lambda x: getattr(x, extr['object_placement'], []), getattr(inst, extr['attribute'], [])))
if len(related_entities):
if isinstance(related_entities[0], tuple):
Expand All @@ -69,13 +74,14 @@ def step_impl(context, entity, fragment, other_entity):
correct_elements = list(filter(lambda x: x.is_a(other_entity), related_entities))

if condition == 'only 1' and len(correct_elements) > 1:
errors.append(err.InstanceStructureError(inst, correct_elements, f'{error_log_txt}'))
errors.append(err.InstanceStructureError(False, inst, correct_elements, f'{error_log_txt}'))
if condition == 'a list of only':
if len(getattr(inst, extr['attribute'], [])) > 1:
errors.append(err.InstanceStructureError(f'{error_log_txt} more than 1 list, including'))
errors.append(err.InstanceStructureError(False, f'{error_log_txt} more than 1 list, including'))
elif len(false_elements):
errors.append(err.InstanceStructureError(inst, false_elements, f'{error_log_txt} a list that includes'))
errors.append(err.InstanceStructureError(False, inst, false_elements, f'{error_log_txt} a list that includes'))
if condition == 'only' and len(false_elements):
errors.append(err.InstanceStructureError(inst, correct_elements, f'{error_log_txt}'))

errors.append(err.InstanceStructureError(False, inst, correct_elements, f'{error_log_txt}'))
if len(errors) == amount_of_errors and context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))
misc.handle_errors(context, errors)
Loading

0 comments on commit be0f59e

Please sign in to comment.