Skip to content

Commit

Permalink
ALB004 - Alignment container (#67)
Browse files Browse the repository at this point in the history
* Initial commit

* Change file schema

* Added indirectly

* consider RuleState error class inheritance

* Fixes

* WiP IF rule change

* Added contain Then

* Added some test files + fixes

* Remove unnecessary changes

* Remove unnecessary changes

---------

Co-authored-by: Geert Hesselink <[email protected]>
Co-authored-by: Thomas Krijnen <[email protected]>
  • Loading branch information
3 people authored Sep 21, 2023
1 parent 2cf1295 commit 05329ef
Show file tree
Hide file tree
Showing 9 changed files with 4,229 additions and 3 deletions.
12 changes: 12 additions & 0 deletions features/ALB004_Alignment-in-spatial-structure.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@implementer-agreement
@ALB
Feature: ALB004 - Alignment in spatial structure
The rule verifies, that each IfcAlignment must be related to IfcProject using the IfcRelAggregates relationship - either directly or indirectly. The indirect case is when a child alignment is aggregated to a parent alignment.
In this case, only the parent alignment shall be related to the project. Additionally instances of IfcAlignment must not be related to spatial entities using the IfcRelContainedInSpatialStructure relationship.

Scenario: Agreement on each IfcAlignment being aggregated to IfcProject and not contained in IfcSpatialElement

Given A file with Schema Identifier "IFC4X3_TC1" or "IFC4X3_ADD1" or "IFC4X3"
And An IfcAlignment
Then Each IfcAlignment must be aggregated to IfcProject directly or indirectly
Then Each IfcAlignment must not be contained in IfcSpatialElement directly or indirectly
18 changes: 18 additions & 0 deletions features/steps/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,21 @@ class RepresentationTypeError(RuleState):

def __str__(self):
return f"On instance {misc.fmt(self.inst)} the {self.representation_id} shape representation does not have {self.representation_type} as RepresentationType"


@dataclass
class RelationshipError(RuleState):
entity: ifcopenshell.entity_instance
decision: str
condition: str
relationship: str
preposition: str
other_entity: str
def __str__(self):

if self.decision == 'must':
decision_str = 'not'
elif self.decision == 'must not':
decision_str = ''

return f"The instance {self.entity} is {decision_str} {self.condition} {self.relationship} {self.preposition} {self.other_entity}"
73 changes: 70 additions & 3 deletions features/steps/thens/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from behave import *
from utils import ifc, misc, system

@then('Each {entity} {condition} be {directness} contained in {other_entity}')
def step_impl(context, entity, condition, directness, other_entity):
stmt_to_op = ['must', 'must not']
Expand Down Expand Up @@ -30,14 +31,16 @@ def step_impl(context, entity, condition, directness, other_entity):
if is_indirectly_contained:
observed_directness.update({'indirectly'})
break
else:
relating_spatial_element = None

common_directness = required_directness & observed_directness # values the required and observed situation have in common
directness_achieved = bool(common_directness) # if there's a common value -> relationship achieved
directness_expected = condition == 'must' # check if relationship is expected
if directness_achieved != directness_expected:
errors.append(err.InstanceStructureError(False, ent, [other_entity], 'contained', optional_values={'condition': condition, 'directness': directness}))
errors.append(err.InstanceStructureError(False, ent, [relating_spatial_element], 'contained', optional_values={'condition': condition, 'directness': directness}))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccess(True, ent))
errors.append(err.RuleSuccessInsts(True, ent))

misc.handle_errors(context, errors)

Expand Down Expand Up @@ -108,4 +111,68 @@ def step_impl(context, related, relating, other_entity, condition):
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInst(True, inst))

misc.handle_errors(context, errors)
misc.handle_errors(context, errors)


@then('Each {entity} {decision} be {relationship} {preposition} {other_entity} {condition}')
def step_impl(context, entity, decision, relationship, preposition, other_entity, condition):
acceptable_decisions = ['must', 'must not']
assert decision in acceptable_decisions

acceptable_relationships = {'aggregated': ['Decomposes', 'RelatingObject'], 'contained': ['ContainedInStructure', 'RelatingStructure']}
assert relationship in acceptable_relationships

acceptable_conditions = ['directly', 'indirectly', 'directly or indirectly', 'indirectly or directly']
assert condition in acceptable_conditions

if 'directly' in condition:
required_directness = {condition} if condition not in ['directly or indirectly', 'indirectly or directly'] else {
'directly', 'indirectly'}
check_directness = True
else:
check_directness = False

errors = []

other_entity_reference = acceptable_relationships[relationship][0] # eg Decomposes
other_entity_relation = acceptable_relationships[relationship][1] # eg RelatingObject

if context.instances and getattr(context, 'applicable', True):
for ent in context.instances:
relationship_reached = False
if check_directness:
observed_directness = set()
if len(getattr(ent, other_entity_reference)) > 0:
relation = getattr(ent, other_entity_reference)[0]
relating_element = getattr(relation, other_entity_relation)
relationship_reached = relating_element.is_a(other_entity)
if relationship_reached:
if check_directness:
observed_directness.update({'directly'})
if decision == 'must not':
errors.append(err.RelationshipError(False, ent, decision, condition, relationship, preposition, other_entity))
break
if hasattr(relating_element, other_entity_reference): # in case the relation points to a wrong instance
while len(getattr(relating_element, other_entity_reference)) > 0:
relation = getattr(relating_element, other_entity_reference)[0]
relating_element = getattr(relation, other_entity_relation)
relationship_reached = relating_element.is_a(other_entity)
if relationship_reached:
if check_directness:
observed_directness.update({'indirectly'})
break
if decision == 'must not':
errors.append(err.RelationshipError(False, ent, decision, condition, relationship, preposition, other_entity))
break

if check_directness:
common_directness = required_directness & observed_directness # values the required and observed situation have in common
directness_achieved = bool(common_directness) # if there's a common value -> relationship achieved
directness_expected = decision == 'must' # check if relationship is expected
if directness_achieved != directness_expected:
errors.append(err.RelationshipError(False, ent, decision, condition, relationship, preposition, other_entity))
elif context.error_on_passed_rule:
errors.append(err.RuleSuccessInsts(True, ent))
if context.error_on_passed_rule and decision == 'must not' and not relationship_reached:
errors.append(err.RuleSuccessInsts(True, ent))
misc.handle_errors(context, errors)
7 changes: 7 additions & 0 deletions test/files/alb004/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
| File name | Expected result | Error log | Description |
|----------------------------------------------------------------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| pass-alb004-correct-alignment-behaviour-directly-aggregated | success | n.a. | 27=IfcAlignment aggregated directly to #1=IfcProject via #815=IfcRelAggregates |
| pass-alb004-correct-alignment-behaviour-in directly-aggregated | success | n.a. | 27=IfcAlignment aggregated directly to #816=IfcBuilding via #815=IfcRelAggregates. #816=IfcBuilding aggregated directly to #1=IfcProject via #817=IfcRelAggregates |
| fail-alb004-not-aggregated-to-ifcproject | fail | The instance #27=IfcAlignment('3TcFoHol92d8ZdNIHJpM21',#3,'Track alignment','','Railway track alignment',#519,#522,.USERDEFINED.) is not directly or indirectly aggregated to IfcProject | There is no IfcRelAggregates relation between #27=IfcAlignment and #1=IFCPROJECT |
| fail-alb004-aggregated-to-ifcperson | fail | The instance #27=IfcAlignment('3TcFoHol92d8ZdNIHJpM21',#3,'Track alignment','','Railway track alignment',#519,#522,.USERDEFINED.) is not directly or indirectly aggregated to IfcProject | 27=IfcAlignment aggregated directly to #4=IfcPerson via #815=IfcRelAggregates instead to #1=IfcProject |
| fail-alb004-contained-in-spatial-entity | fail | The instance #27=IfcAlignment('3TcFoHol92d8ZdNIHJpM21',#3,'Track alignment','','Railway track alignment',#519,#522,.USERDEFINED.) is directly or indirectly contained in IfcSpatialElement | #27=IfcAlignment is contained in #15=IfcRailway with ##19=IfcRelContainedInSpatialStructure relationship |
Loading

0 comments on commit 05329ef

Please sign in to comment.