Updates segment_vertices function

Renamed PI and CC to TI and NI
Fixes handling of units (now works correctly with US and SI units)
Changed tests to use US feet units
Fixed documentation
This commit is contained in:
Richard Brice
2026-02-23 08:08:48 -08:00
parent c3f325861c
commit 798ed0d502
2 changed files with 117 additions and 60 deletions
@@ -23,32 +23,35 @@ import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
from ifcopenshell import entity_instance, ifcopenshell_wrapper from ifcopenshell import entity_instance, ifcopenshell_wrapper
def _intersect_lines(p1,d1,p2,d2):
x1,y1 = p1
dx1,dy1 = d1
x2,y2 = p2
dx2,dy2 = d2
det = dx1*dy2 - dy1*dx2 def _intersect_lines(p1, d1, p2, d2):
x1, y1 = p1
dx1, dy1 = d1
x2, y2 = p2
dx2, dy2 = d2
det = dx1 * dy2 - dy1 * dx2
if abs(det) < 1e-12: if abs(det) < 1e-12:
return None # lines are parallel return None # lines are parallel
t = ((x2 - x1) * dy2 - (y2 - y1) * dx2)/det t = ((x2 - x1) * dy2 - (y2 - y1) * dx2) / det
x = x1 + t*dx1 x = x1 + t * dx1
y = y1 + t*dy1 y = y1 + t * dy1
return (x,y) return (x, y)
def segment_vertices(file: ifcopenshell.file, curve_segment: entity_instance): def segment_vertices(curve_segment: entity_instance):
""" """
Genererates segment vertices. Segment vertices are the points where the tangents Generates segment vertices. Segment vertices are at the start and end as well as the points where the tangents
at the start and end of the segment intersect (the PI point) and where lines at the start and end of the segment intersect (the TI point) and where lines
perpendicular to the start and end of the segment intersect (CC). normal (perpendicular) to the start and end of the segment intersect (NI).
TI and NI are None if intersection points do not exist, such as in the case of a line.
:param curve_segment: A curve segment :param curve_segment: A curve segment
:return: tuples for Start, End, PI, CC :return: tuples for Start, End, TI, NI
""" """
supported_segment_types = ["IFCCURVESEGMENT"] supported_segment_types = ["IFCCURVESEGMENT"]
segment_type = curve_segment.is_a().upper() segment_type = curve_segment.is_a().upper()
@@ -57,34 +60,31 @@ def segment_vertices(file: ifcopenshell.file, curve_segment: entity_instance):
f"Expected entity type to be one of {[_ for _ in supported_segment_types]}, got '{segment_type}" f"Expected entity type to be one of {[_ for _ in supported_segment_types]}, got '{segment_type}"
) )
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
settings = ifcopenshell.geom.settings() settings = ifcopenshell.geom.settings()
segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data) segment_fn = ifcopenshell_wrapper.map_shape(settings, curve_segment.wrapped_data)
segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn) segment_evaluator = ifcopenshell_wrapper.function_item_evaluator(settings, segment_fn)
s = segment_evaluator.evaluate(segment_fn.start()) s = segment_evaluator.evaluate(segment_fn.start())
start = np.array(s) start = np.array(s)
sx = float(start[0, 3]) / unit_scale sx = float(start[0, 3])
sy = float(start[1, 3]) / unit_scale sy = float(start[1, 3])
sdx = float(start[0, 0]) sdx = float(start[0, 0])
sdy = float(start[1, 0]) sdy = float(start[1, 0])
e = segment_evaluator.evaluate(segment_fn.end()) e = segment_evaluator.evaluate(segment_fn.end())
end = np.array(e) end = np.array(e)
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) ex = float(end[0, 3])
ex = float(end[0, 3]) / unit_scale ey = float(end[1, 3])
ey = float(end[1, 3]) / unit_scale
edx = float(end[0, 0]) edx = float(end[0, 0])
edy = float(end[1, 0]) edy = float(end[1, 0])
pi = _intersect_lines((sx,sy),(sdx,sdy),(ex,ey),(edx,edy)) ti = _intersect_lines((sx, sy), (sdx, sdy), (ex, ey), (edx, edy)) # tangent intersection
sdx = float(start[0, 1]) sdx = float(start[0, 1])
sdy = float(start[1, 1]) sdy = float(start[1, 1])
edx = float(end[0, 1]) edx = float(end[0, 1])
edy = float(end[1, 1]) edy = float(end[1, 1])
cc = _intersect_lines((sx,sy),(sdx,sdy),(ex,ey),(edx,edy)) ni = _intersect_lines((sx, sy), (sdx, sdy), (ex, ey), (edx, edy)) # normal intersection
return (sx,sy), (ex,ey), pi, cc return (sx, sy), (ex, ey), ti, ni
@@ -22,11 +22,21 @@ import ifcopenshell.api.context
import ifcopenshell.api.unit import ifcopenshell.api.unit
def unit_convert(unit_scale, p):
if p == None:
return p
x, y = p
return (x / unit_scale, y / unit_scale)
def test_segment_vertices(): def test_segment_vertices():
file = ifcopenshell.file(schema="IFC4X3") file = ifcopenshell.file(schema="IFC4X3")
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test") project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT") length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
ifcopenshell.api.unit.assign_unit(file, units=[length]) angle = ifcopenshell.api.unit.add_si_unit(file, unit_type="PLANEANGLEUNIT")
ifcopenshell.api.unit.assign_unit(file, units=[length, angle])
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model") geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
axis_model_representation_subcontext = ifcopenshell.api.context.add_context( axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
file, file,
@@ -45,43 +55,90 @@ def test_segment_vertices():
file, "TestAlignment", coordinates, radii, vpoints, lengths file, "TestAlignment", coordinates, radii, vpoints, lengths
) )
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
# test the horizontal alignment geometry segments # test the horizontal alignment geometry segments
expect = [[(500.0, 2500.0), (2142.2379952109395, 1436.01482000418), None, None], expect = [
[(2142.2379952109395, 1436.01482000418), (3660.446122847804, 2050.7361731594674), (3340.0, 659.9999999999998), (2685.9792975637306, 2275.267699722618)], [(500.0, 2500.0), (2142.2379952109395, 1436.01482000418), None, None],
[(3660.4461228478035, 2050.7361731594674), (4084.115884236641, 3889.4629375870213), None, None], [
[(4084.115884236641, 3889.4629375870218), (5469.395067206271, 4847.5663099476205), (4340.0, 5000.000000000001), (5302.199415841732, 3608.7985293830834)], (2142.2379952109395, 1436.01482000418),
[(5469.395067206271, 4847.56630994762), (7019.971366858418, 4638.286073184753), None, None], (3660.446122847804, 2050.7361731594674),
[(7019.971366858417, 4638.286073184753), (7790.932128312586, 4006.7307645487535), (7600.0, 4560.0), (6892.902671821368, 3696.8225599557054)], (3340.0, 659.9999999999998),
[(7790.932128312587, 4006.7307645487535), (8480.0, 2010.0000000000002), None, None], (2685.9792975637306, 2275.267699722618),
[(8480.0, 2010.0000000000002), (8480.0, 2010.0000000000002), None, None] ],
] [(3660.4461228478035, 2050.7361731594674), (4084.115884236641, 3889.4629375870213), None, None],
[
(4084.115884236641, 3889.4629375870218),
(5469.395067206271, 4847.5663099476205),
(4340.0, 5000.000000000001),
(5302.199415841732, 3608.7985293830834),
],
[(5469.395067206271, 4847.56630994762), (7019.971366858418, 4638.286073184753), None, None],
[
(7019.971366858417, 4638.286073184753),
(7790.932128312586, 4006.7307645487535),
(7600.0, 4560.0),
(6892.902671821368, 3696.8225599557054),
],
[(7790.932128312587, 4006.7307645487535), (8480.0, 2010.0000000000002), None, None],
[(8480.0, 2010.0000000000002), (8480.0, 2010.0000000000002), None, None],
]
curve = ifcopenshell.api.alignment.get_basis_curve(alignment) curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
for segment,expected in zip(curve.Segments,expect): for segment, expected in zip(curve.Segments, expect):
s,e,pi,cc = ifcopenshell.api.alignment.segment_vertices(file,segment) s, e, ti, ni = ifcopenshell.api.alignment.segment_vertices(segment)
s = unit_convert(unit_scale, s)
e = unit_convert(unit_scale, e)
ti = unit_convert(unit_scale, ti)
ni = unit_convert(unit_scale, ni)
assert s == pytest.approx(expected[0]) assert s == pytest.approx(expected[0])
assert e == pytest.approx(expected[1]) assert e == pytest.approx(expected[1])
assert pi == pytest.approx(expected[2]) assert ti == pytest.approx(expected[2])
assert cc == pytest.approx(expected[3]) assert ni == pytest.approx(expected[3])
# test vertical curve segments # test vertical curve segments
expect = [[(0.0, 100.0), (1200.0, 121.0), None, None], expect = [
[(1200.0, 121.0), (2799.99999384661, 127.00000006153391), (1999.9999969233054, 134.99999994615786), (2218.1436363635016, -58058.63636362867)], [(0.0, 100.0), (1200.0, 121.0), None, None],
[(2800.0, 127.0), (4400.0, 111.0), None, None], [
[(4400.0, 111.0), (5599.999994508736, 116.9999998901747), (4999.999997254367, 105.00000002745632), (4800.039999999177, 40114.99999991764)], (1200.0, 121.0),
[(5600.0, 117.0), (6400.0, 133.0), None, None], (2799.99999384661, 127.00000006153391),
[(6400.0, 133.0), (8399.999995932576, 133.0000000813485), (7399.999997966288, 152.99999995932575), (7399.999999999187, -49866.99999995936)], (1999.9999969233054, 134.99999994615786),
[(8400.0, 133.0), (9400.0, 113.0), None, None], (2218.1436363635016, -58058.63636362867),
[(9400.0, 113.0), (10199.99999633883, 103.00000001830585), (9799.999998169415, 105.00000003661171), (10466.733333334432, 53449.66666672164)], ],
[(10200.0, 103.0), (12800.0, 90.0), None, None], [(2800.0, 127.0), (4400.0, 111.0), None, None],
[(12800.0, 90.0), (12800.0, 90.0), None, None] [
(4400.0, 111.0),
(5599.999994508736, 116.9999998901747),
(4999.999997254367, 105.00000002745632),
(4800.039999999177, 40114.99999991764),
],
[(5600.0, 117.0), (6400.0, 133.0), None, None],
[
(6400.0, 133.0),
(8399.999995932576, 133.0000000813485),
(7399.999997966288, 152.99999995932575),
(7399.999999999187, -49866.99999995936),
],
[(8400.0, 133.0), (9400.0, 113.0), None, None],
[
(9400.0, 113.0),
(10199.99999633883, 103.00000001830585),
(9799.999998169415, 105.00000003661171),
(10466.733333334432, 53449.66666672164),
],
[(10200.0, 103.0), (12800.0, 90.0), None, None],
[(12800.0, 90.0), (12800.0, 90.0), None, None],
] ]
curve = ifcopenshell.api.alignment.get_curve(alignment) curve = ifcopenshell.api.alignment.get_curve(alignment)
for segment,expected in zip(curve.Segments,expect): for segment, expected in zip(curve.Segments, expect):
s,e,pi,cc = ifcopenshell.api.alignment.segment_vertices(file,segment) s, e, ti, ni = ifcopenshell.api.alignment.segment_vertices(segment)
s = unit_convert(unit_scale, s)
e = unit_convert(unit_scale, e)
ti = unit_convert(unit_scale, ti)
ni = unit_convert(unit_scale, ni)
assert s == pytest.approx(expected[0]) assert s == pytest.approx(expected[0])
assert e == pytest.approx(expected[1]) assert e == pytest.approx(expected[1])
assert pi == pytest.approx(expected[2]) assert ti == pytest.approx(expected[2])
assert cc == pytest.approx(expected[3]) assert ni == pytest.approx(expected[3])
test_segment_vertices() test_segment_vertices()