Skip to content

Commit

Permalink
GEM113 Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aothms committed Dec 28, 2024
1 parent 7572e15 commit c300c4f
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 2 deletions.
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

@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')
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;

0 comments on commit c300c4f

Please sign in to comment.