Skip to content

Commit

Permalink
SWE002 (1. Mirroring within _IfcDerivedProfileDef.Operator_ shall not…
Browse files Browse the repository at this point in the history
… be used) initial impl
  • Loading branch information
aothms committed Dec 26, 2024
1 parent 7572e15 commit de2f623
Show file tree
Hide file tree
Showing 7 changed files with 792 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@informal-proposition
@SWE
@version1
@E00050
Feature: SWE002 - Mirroring within IfcDerivedProfileDef shall not be used

The rule verifies that IfcDerivedProfileDef is

Scenario: IfcDerivedProfileDef must not use mirroring as there is a dedicated subtype for that

Given An IfcDerivedProfileDef without subtypes
Given Its attribute Operator
Given The determinant of the placement matrix

Then The value must be greater than 0

Scenario Outline: Tapered sweeps must not use mirroring altogether

Given An <entity>
Given Its attribute <attribute>
Given Its entity type is 'IfcDerivedProfileDef' or 'IfcMirroredProfileDef'
Given Its attribute Operator
Given The determinant of the placement matrix

Then The value must be greater than 0

Examples:
| entity | attribute |
| IfcExtrudedAreaSolidTapered | SweptArea |
| IfcExtrudedAreaSolidTapered | EndSweptArea |
| IfcRevolvedAreaSolidTapered | SweptArea |
| IfcRevolvedAreaSolidTapered | EndSweptArea |
2 changes: 1 addition & 1 deletion features/steps/givens/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def step_impl(context, inst, comparison_op, attribute, value, tail=SubTypeHandli
if attribute.lower() in ['its type', 'its entity type']: # it's entity type is a special case using ifcopenshell 'is_a()' func
observed_v = misc.do_try(lambda : inst.is_a(), ())
values = {value} if isinstance(value, str) else value
if any(pred(check_entity_type(inst, v, tail), True) for v in values):
if any(check_entity_type(inst, v, tail) for v in values):
entity_is_applicable = True
else:
observed_v = getattr(inst, attribute, ()) or ()
Expand Down
18 changes: 18 additions & 0 deletions features/steps/givens/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,21 @@ def step_impl(context, inst, excluding=None):
def step_impl(context, inst):
inst = itertools.pairwise(inst)
yield ValidationOutcome(instance_id=inst, severity=OutcomeSeverity.PASSED)

@gherkin_ifc.step("The determinant of the placement matrix")
def step_impl(context, inst):
import numpy as np
import ifcopenshell.ifcopenshell_wrapper

if inst.wrapped_data.file_pointer() == 0:
# In some case we're processing operations on attributes that are 'derived in subtype', for
# example the Operator on an IfcMirroredProfileDef. Derived attribute values are generated
# on the fly and are not part of a file. Due to a limitation on the mapping expecting a file
# object, such instances can also not be mapped. Therefore in such case we create a temporary
# file to add the instance to.
f = ifcopenshell.file(schema=context.model.schema_identifier)
inst = f.add(inst)

shp = ifcopenshell.ifcopenshell_wrapper.map_shape(ifcopenshell.geom.settings(), inst.wrapped_data)
d = np.linalg.det(np.array(shp.components))
yield ValidationOutcome(instance_id=d, severity=OutcomeSeverity.PASSED)
28 changes: 20 additions & 8 deletions features/steps/thens/attributes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import operator
import re
import ifcopenshell

from utils import misc, system, geometry
Expand Down Expand Up @@ -63,7 +64,8 @@ def accumulate_errors(i):
@gherkin_ifc.step('The value of attribute {attribute} must be {value_or_comparison_op}')
@gherkin_ifc.step('The value of attribute {attribute} must be {value_or_comparison_op} {display_entity:display_entity}')
@gherkin_ifc.step('The value of attribute {attribute} must be {value_or_comparison_op} the expression: {expression}')
def step_impl(context, inst, attribute:str, value_or_comparison_op:str, expression:str=None, display_entity=0):
@gherkin_ifc.step('The value must be {value_or_comparison_op}')
def step_impl(context, inst, value_or_comparison_op:str, attribute:str=None, expression:str=None, display_entity=0):
"""
Compare an attribute to an expression based on attributes.
Expand All @@ -78,19 +80,22 @@ def step_impl(context, inst, attribute:str, value_or_comparison_op:str, expressi
** : exponentiation.
"""

binary_operators = {
'equal to' : operator.eq,
'not equal to' : operator.ne,
'greater than' : operator.gt,
'less than' : operator.lt,
'greater than or equal to' : operator.ge,
'less than or equal to' : operator.le,
}
operators = {
'+' : operator.add,
'-' : operator.sub,
'*' : operator.mul,
'/' : operator.truediv,
'%' : operator.mod,
'**' : operator.pow,
'equal to' : operator.eq,
'not equal to' : operator.ne,
'greater than' : operator.gt,
'less than' : operator.gt,
'greater than or equal to' : operator.ge,
'less than or equal to' : operator.le,
**binary_operators
}

if expression is not None:
Expand Down Expand Up @@ -162,10 +167,17 @@ def step_impl(context, inst, attribute:str, value_or_comparison_op:str, expressi
opts = value_or_comparison_op.split(' or ')
value_or_comparison_op = tuple(opts)
pred = misc.reverse_operands(operator.contains)
elif m := re.match(rf"^({'|'.join(binary_operators.keys())})\s+(\d+(\.\d+)?)$", value_or_comparison_op):
pred_str, val_str, *_ = m.groups()
value_or_comparison_op = float(val_str)
pred = binary_operators[pred_str]

if isinstance(inst, (tuple, list)):
inst = inst[0]
attribute_value = getattr(inst, attribute, 'Attribute not found')
if attribute is None:
attribute_value = inst
else:
attribute_value = getattr(inst, attribute, 'Attribute not found')
if attribute_value is None:
attribute_value = ()
if inst is None:
Expand Down
Loading

0 comments on commit de2f623

Please sign in to comment.