diff --git a/src/ifcblenderexport/blenderbim/bim/helper.py b/src/ifcblenderexport/blenderbim/bim/helper.py index 9e598198d4..185f631121 100644 --- a/src/ifcblenderexport/blenderbim/bim/helper.py +++ b/src/ifcblenderexport/blenderbim/bim/helper.py @@ -329,49 +329,39 @@ def clip_segment(bounds, segm): """ # Liang–Barsky algorithm - def iszero(v): - return abs(v) < 1e-10 - xmin, xmax, ymin, ymax = bounds p1, p2 = segm + def clip_side(p, q): + if abs(p) < 1e-10: # ~= 0, parallel to the side + if q < 0: + return None # outside + else: + return 0, 1 # inside + + t = q / p # the intersection point + + if p < 0: # entering + return t, 1 + else: # leaving + return 0, t + dlt = p2 - p1 - q_l = p1.x - xmin - q_r = xmax - p1.x - q_t = p1.y - ymin - q_b = ymax - p1.y - pos = [1] - neg = [0] + tt = ( + clip_side(-dlt.x, p1.x - xmin), # left + clip_side(+dlt.x, xmax - p1.x), # right + clip_side(-dlt.y, p1.y - ymin), # bottom + clip_side(+dlt.y, ymax - p1.y), # top + ) - if (iszero(dlt.x) and (q_l < 0 or q_r < 0)) or (iszero(dlt.y) and (q_t < 0 or q_b < 0)): - # parallel to a boundary and outside it + if None in tt: return None - if not iszero(dlt.x): - t_l = q_l / -dlt.x - t_r = q_r / dlt.x - if t_l < 0: - neg.append(t_l) - pos.append(t_r) - else: - neg.append(t_r) - pos.append(t_l) + t1 = max(0, max(t[0] for t in tt)) + t2 = min(1, min(t[1] for t in tt)) - if not iszero(dlt.y): - t_t = q_t / -dlt.y - t_b = q_b / dlt.y - if t_t < 0: - neg.append(t_t) - pos.append(t_b) - else: - neg.append(t_b) - pos.append(t_t) - - t1 = max(neg) - t2 = min(pos) - - if t1 > t2: + if t1 >= t2: return None p1c = p1 + dlt * t1 diff --git a/src/ifcblenderexport/runpytest.py b/src/ifcblenderexport/runpytest.py new file mode 100755 index 0000000000..ec07e21d49 --- /dev/null +++ b/src/ifcblenderexport/runpytest.py @@ -0,0 +1,16 @@ +""" +Requires pytest installed under blender + +Usage: `blender -b -P runpytest.py -- ARGS` +""" + +import sys +import pytest + +argv = [__file__] + +if '--' in sys.argv: + i = sys.argv.index('--') + argv += sys.argv[i+1:] + +pytest.main(argv) diff --git a/src/ifcblenderexport/tests/test_segment_clipping.py b/src/ifcblenderexport/tests/test_segment_clipping.py new file mode 100644 index 0000000000..d36291fbe1 --- /dev/null +++ b/src/ifcblenderexport/tests/test_segment_clipping.py @@ -0,0 +1,53 @@ +import pytest +from mathutils import Vector + +from blenderbim.bim.helper import clip_segment + + +BOUNDS = (10, 30, 10, 30) + +SEGMENTS_INSIDE = ( + (Vector((15, 20)), Vector((25, 20))), + (Vector((20, 15)), Vector((20, 25))), + (Vector((15, 15)), Vector((25, 25))), + (Vector((15, 25)), Vector((25, 15))), +) + +SEGMENTS_OUTSIDE = ( + (Vector((15, 35)), Vector((25, 35))), + (Vector((25, 35)), Vector((35, 25))), + (Vector((35, 25)), Vector((35, 15))), + (Vector((35, 15)), Vector((25, 5))), + (Vector((25, 5)), Vector((15, 5))), + (Vector((15, 5)), Vector((5, 15))), + (Vector((5, 15)), Vector((5, 25))), + (Vector((5, 25)), Vector((15, 35))) +) + +SEGMENTS_CLIPPED = ( + ((Vector((5, 20)), Vector((15, 20))), (Vector((10, 20)), Vector((15, 20)))), + ((Vector((25, 20)), Vector((35, 20))), (Vector((25, 20)), Vector((30, 20)))), + ((Vector((20, 5)), Vector((20, 15))), (Vector((20, 10)), Vector((20, 15)))), + ((Vector((20, 15)), Vector((20, 35))), (Vector((20, 15)), Vector((20, 30)))), + ((Vector((5, 20)), Vector((20, 35))), (Vector((10, 25)), Vector((15, 30)))), + ((Vector((5, 20)), Vector((20, 5))), (Vector((10, 15)), Vector((15, 10)))), + ((Vector((35, 20)), Vector((20, 35))), (Vector((30, 25)), Vector((25, 30)))), + ((Vector((35, 20)), Vector((20, 5))), (Vector((30, 15)), Vector((25, 10)))), +) + +@pytest.mark.parametrize('segment', SEGMENTS_INSIDE) +def test_clip_inside(segment): + clipped = clip_segment(BOUNDS, segment) + assert clipped == segment + + +@pytest.mark.parametrize('segment', SEGMENTS_OUTSIDE) +def test_clip_outside(segment): + clipped = clip_segment(BOUNDS, segment) + assert clipped is None + + +@pytest.mark.parametrize('segment,expected', SEGMENTS_CLIPPED) +def test_clip_crossing(segment, expected): + clipped = clip_segment(BOUNDS, segment) + assert clipped == expected