Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GEM113 no colinear arcs (IVS-303) #335

Open
wants to merge 2 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
echo $CONDA/bin >> $GITHUB_PATH
- name: Install dependencies
run: |
pip install behave pytest tabulate pyparsing sqlalchemy numpy pydantic pydot sqlalchemy_utils django python-dotenv deprecated pandas pyspellchecker rtree
pip install behave pytest tabulate pyparsing sqlalchemy numpy pydantic pydot sqlalchemy_utils django python-dotenv deprecated pandas pyspellchecker rtree mpmath
wget -O /tmp/ifcopenshell_python.zip https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-`python3 -c 'import sys;print("".join(map(str, sys.version_info[0:2])))'`-v0.8.1-1d27161-linux64.zip
mkdir -p `python3 -c 'import site; print(site.getusersitepackages())'`
unzip -d `python3 -c 'import site; print(site.getusersitepackages())'` /tmp/ifcopenshell_python.zip
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@implementer-agreement
@GEM
@version1
Feature: GEM113 - Indexed poly curve arcs must not be defined using colinear points
The rule verifies, that all the three points of any IfcArcIndex segment of an IfcIndexedPolyCurve are not colinear after taking the Precision factor into account
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The rule verifies, that all the three points of any IfcArcIndex segment of an IfcIndexedPolyCurve are not colinear after taking the Precision factor into account
The rule verifies that all the three points of any IfcArcIndex segment of an IfcIndexedPolyCurve are not colinear after taking the Precision factor into account


@E00050
Scenario: No poly curve arcs using colinear points

Given An IfcIndexedPolyCurve
Then It must have no arc segments that use colinear points after taking the Precision factor into account
19 changes: 18 additions & 1 deletion features/steps/thens/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,21 @@ def step_impl(context, inst: ifcopenshell.entity_instance, clause: str):
if clause == 'including' or (clause == 'excluding' and (i, j) != (0, len(points_coordinates) - 1)):
if math.dist(points_coordinates[i], points_coordinates[j]) < precision:
yield ValidationOutcome(inst=inst, observed=(points_coordinates[i], points_coordinates[j]),
severity=OutcomeSeverity.ERROR)
severity=OutcomeSeverity.ERROR)

@gherkin_ifc.step("It must have no arc segments that use colinear points after taking the Precision factor into account")
def step_impl(context, inst: ifcopenshell.entity_instance):
import mpmath as mp
mp.prec = 128

representation_context = geometry.recurrently_get_entity_attr(context, inst, 'IfcRepresentation', 'ContextOfItems')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We continue to generate splinters in regards to how geometric precision is handled. It would be nice to consolidate this all in one spot, maybe in sth like /utils/precision.py.

Does not need to be addressed immediately and possibly fits nicely with refactoring in IVS-306.

precision = mp.mpf(geometry.get_precision_from_contexts(representation_context))

for seg in (inst.Segments or ()):
ps = inst.Points.CoordList
if seg.is_a('IfcArcIndex') and len(seg[0]) == 3 and all((i >= 1) and ((i - 1) < len(ps)) for i in seg[0]):
a, b, c = (ps[i-1] for i in seg[0])
l = geometry.Line.from_points(a, c)
if l.distance(b) < precision:
yield ValidationOutcome(inst=inst, observed=str(seg),
severity=OutcomeSeverity.ERROR)
25 changes: 24 additions & 1 deletion features/steps/utils/geometry.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from dataclasses import dataclass
import operator
import math
from typing import Dict
from typing import Dict, Tuple

import numpy as np
import mpmath as mp

import ifcopenshell.entity_instance
import ifcopenshell.geom as ifcos_geom
Expand Down Expand Up @@ -306,3 +307,25 @@ def compare_with_precision(value_1: float, value_2: float, precision: float, com
return value_1 < value_2 or math.isclose(value_1, value_2, rel_tol=0., abs_tol=precision)
case _:
raise ValueError(f"Invalid comparison operator: {comparison_operator}")

@dataclass
class Line:
"""
Represents a line a + d*b where a is a position and b a normalized unit vector
"""
a: Tuple[mp.mpf]
b: Tuple[mp.mpf]

def distance(self, point: Tuple[mp.mpf]) -> mp.mpf:
v = [p - ai for p, ai in zip(point, self.a)]
dot_prod = mp.fsum([x * y for x, y in zip(v, self.b)])
proj = [dot_prod * bi for bi in self.b]
dist_vec = [vi - pi for vi, pi in zip(v, proj)]
return mp.sqrt(mp.fsum([x*x for x in dist_vec]))

@staticmethod
def from_points(a, b):
a, b = (tuple(map(mp.mpf, p)) for p in (a,b))
l = mp.sqrt(mp.fsum([x*x for x in b]))
b = [x / l for x in b]
return Line(a, b)
38 changes: 38 additions & 0 deletions test/files/gem113/fail-gem113-arc_almost_colinear.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2024-12-28T14:43:26',(''),(''),'IfcOpenShell-0.8.0','IfcOpenShell-0.8.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.8.0','IfcOpenShell-0.8.0','');
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1735397006);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.));
#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.0174532925199433),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCPROJECT('24xp2u2Rr7uOpUKiXfPYXD',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINTLIST2D(((0.,0.),(0.5,1.E-06),(1.,0.)));
#22=IFCINDEXEDPOLYCURVE(#21,(IFCARCINDEX((1,2,3)),IFCLINEINDEX((3,1))),$);
#23=IFCSHAPEREPRESENTATION(#11,'FootPrint','Curve2D',(#22));
#24=IFCPRODUCTDEFINITIONSHAPE($,$,(#23));
#25=IFCCARTESIANPOINT((0.,0.,0.));
#26=IFCAXIS2PLACEMENT3D(#25,$,$);
#27=IFCLOCALPLACEMENT($,#26);
#28=IFCSPACE('1vgt69T2bFTu4vLXNn24Y8',$,$,$,$,#27,#24,$,$,$,$);
#29=IFCRELAGGREGATES('30p94qT6L2YQvAFbzZIwTk',$,$,$,#20,(#28));
ENDSEC;
END-ISO-10303-21;
38 changes: 38 additions & 0 deletions test/files/gem113/fail-gem113-arc_colinear.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2024-12-28T14:43:26',(''),(''),'IfcOpenShell-0.8.0','IfcOpenShell-0.8.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.8.0','IfcOpenShell-0.8.0','');
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1735397006);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.));
#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.0174532925199433),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCPROJECT('1dV8LfekbEiPWnicWc56Kx',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINTLIST2D(((0.,0.),(0.5,0.),(1.,0.)));
#22=IFCINDEXEDPOLYCURVE(#21,(IFCARCINDEX((1,2,3)),IFCLINEINDEX((3,1))),$);
#23=IFCSHAPEREPRESENTATION(#11,'FootPrint','Curve2D',(#22));
#24=IFCPRODUCTDEFINITIONSHAPE($,$,(#23));
#25=IFCCARTESIANPOINT((0.,0.,0.));
#26=IFCAXIS2PLACEMENT3D(#25,$,$);
#27=IFCLOCALPLACEMENT($,#26);
#28=IFCSPACE('0djY0fT75CuflGivoTukmi',$,$,$,$,#27,#24,$,$,$,$);
#29=IFCRELAGGREGATES('3ZawqDx_j4$wdFp5nfgwyT',$,$,$,#20,(#28));
ENDSEC;
END-ISO-10303-21;
67 changes: 67 additions & 0 deletions test/files/gem113/generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import ifcopenshell
import ifcopenshell.template
from math import sqrt

sqrt2 = sqrt(2.)/2.

pass_arc = [
(1.0, 0.0),
(sqrt2, sqrt2),
(0.0, 1.0)
],[
(1,2,3),(3,1)
]

fail_arc_colinear = [
(0.0, 0.0),
(0.5, 0.0),
(1.0, 0.0)
],[
(1,2,3),(3,1)
]

fail_arc_almost_colinear = [
(0.0, 0.0),
(0.5, 1.e-6),
(1.0, 0.0)
],[
(1,2,3),(3,1)
]

pass_arc_non_colinear_enough = [
(0.0, 0.0),
(0.5, 1.e-4),
(1.0, 0.0)
],[
(1,2,3),(3,1)
]

cases = ['pass_arc', 'fail_arc_colinear', 'fail_arc_almost_colinear', 'pass_arc_non_colinear_enough']
for case in cases:
pf, reason = case.split('_', 1)
f = ifcopenshell.template.create()
ctx = f.by_type('IfcGeometricRepresentationContext')[0]
proj = f.by_type('IfcProject')[0]
points, edges = globals()[case]
plist = f.createIfcCartesianPointList2D(points)
def create_segment(tup):
if len(tup) == 2:
return f.createIfcLineIndex((tup))
if len(tup) == 3:
return f.createIfcArcIndex((tup))
segments = list(map(create_segment, edges)) if edges else edges
crv = f.createIfcIndexedPolyCurve(plist, segments, None)
rep = f.createIfcShapeRepresentation(ctx, 'FootPrint', 'Curve2D', [crv])
pds = f.createIfcProductDefinitionShape(Representations=[rep])
road = f.createIfcSpace(
ifcopenshell.guid.new(),
ObjectPlacement=f.createIfcLocalPlacement(RelativePlacement=f.createIfcAxis2Placement3D(f.createIfcCartesianPoint((0., 0., 0.)))),
Representation=pds
)
f.createIfcRelAggregates(
ifcopenshell.guid.new(),
None, None, None,
proj,
[road]
)
f.write(f'{pf}-gem113-{reason}.ifc')
38 changes: 38 additions & 0 deletions test/files/gem113/pass-gem113-arc.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2024-12-28T14:43:26',(''),(''),'IfcOpenShell-0.8.0','IfcOpenShell-0.8.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.8.0','IfcOpenShell-0.8.0','');
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1735397006);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.));
#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.0174532925199433),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCPROJECT('1lmEZwk0jCxu1UhV3YDklD',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINTLIST2D(((1.,0.),(0.707106781186548,0.707106781186548),(0.,1.)));
#22=IFCINDEXEDPOLYCURVE(#21,(IFCARCINDEX((1,2,3)),IFCLINEINDEX((3,1))),$);
#23=IFCSHAPEREPRESENTATION(#11,'FootPrint','Curve2D',(#22));
#24=IFCPRODUCTDEFINITIONSHAPE($,$,(#23));
#25=IFCCARTESIANPOINT((0.,0.,0.));
#26=IFCAXIS2PLACEMENT3D(#25,$,$);
#27=IFCLOCALPLACEMENT($,#26);
#28=IFCSPACE('1l6UMBjaL0Iu2SkTL0BXbm',$,$,$,$,#27,#24,$,$,$,$);
#29=IFCRELAGGREGATES('33aK_7G8XC_uTta6bAcd86',$,$,$,#20,(#28));
ENDSEC;
END-ISO-10303-21;
38 changes: 38 additions & 0 deletions test/files/gem113/pass-gem113-arc_non_colinear_enough.ifc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
FILE_NAME('','2024-12-28T14:43:26',(''),(''),'IfcOpenShell-0.8.0','IfcOpenShell-0.8.0','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPERSON($,$,'',$,$,$,$,$);
#2=IFCORGANIZATION($,'',$,$,$);
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
#4=IFCAPPLICATION(#2,'0.8.0','IfcOpenShell-0.8.0','');
#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1735397006);
#6=IFCDIRECTION((1.,0.,0.));
#7=IFCDIRECTION((0.,0.,1.));
#8=IFCCARTESIANPOINT((0.,0.,0.));
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
#10=IFCDIRECTION((0.,1.));
#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.0174532925199433),#16);
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
#20=IFCPROJECT('1mJ5LZpnD8yh32dfmevuI8',#5,'',$,$,$,$,(#11),#19);
#21=IFCCARTESIANPOINTLIST2D(((0.,0.),(0.5,0.0001),(1.,0.)));
#22=IFCINDEXEDPOLYCURVE(#21,(IFCARCINDEX((1,2,3)),IFCLINEINDEX((3,1))),$);
#23=IFCSHAPEREPRESENTATION(#11,'FootPrint','Curve2D',(#22));
#24=IFCPRODUCTDEFINITIONSHAPE($,$,(#23));
#25=IFCCARTESIANPOINT((0.,0.,0.));
#26=IFCAXIS2PLACEMENT3D(#25,$,$);
#27=IFCLOCALPLACEMENT($,#26);
#28=IFCSPACE('2m1MQwdaHFr9rxB4Y7O3_5',$,$,$,$,#27,#24,$,$,$,$);
#29=IFCRELAGGREGATES('0J3nG9PyPEdgpUHCZII324',$,$,$,#20,(#28));
ENDSEC;
END-ISO-10303-21;
Loading