fix(task-2): order-independent T cuts and bounded near-straight joins
A T recorded its cut on the crossing segment as a distance from that segment's start; when the start was carried onto a third wall later in the same pass the cut went stale, so [c,b,a] drew a free-end jamb inside wall a, an uncut face and a double-hatched overlap where [c,a,b] was right. Cuts are now stored as points and measured against the final axis. A near-straight join whose faces are offset (a thickness or justification change, or a rounding kink between such walls) mitred at offset/sin(kink): faces 1e8 mm long and a clockwise region for a 0.01 mm rounding. A straight-through pair whose mitre lies more than _MITRE_REACH (2) thicknesses from the node now steps on the line bisecting the join - both sides of a two-end node together, so the pieces tile without a sliver - and every piece and hub ring is refused unless counter-clockwise, not only when it crosses itself. Pure geometry: no ActiveX member is touched, so no live probe applies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dae48d9037
commit
9e9138f22d
+63
-16
@@ -7,7 +7,11 @@ segments meet:
|
|||||||
|
|
||||||
* **L and straight joins** - two segment ends at one point. Each face is met by
|
* **L and straight joins** - two segment ends at one point. Each face is met by
|
||||||
the face of its neighbour and both are trimmed or extended to their
|
the face of its neighbour and both are trimmed or extended to their
|
||||||
intersection, so the corner is a clean mitre.
|
intersection, so the corner is a clean mitre. A near-straight join whose
|
||||||
|
faces are offset from each other (a thickness or justification change, or a
|
||||||
|
rounding kink between two such walls) would mitre kilometres away; past
|
||||||
|
``_MITRE_REACH`` thicknesses it steps instead: both faces stop on the line
|
||||||
|
bisecting the join and a short face closes the step.
|
||||||
* **T** - a segment ends *inside* another wall. Its axis is carried to the
|
* **T** - a segment ends *inside* another wall. Its axis is carried to the
|
||||||
crossing axis, the crossing segment is split there, and the same rule stops
|
crossing axis, the crossing segment is split there, and the same rule stops
|
||||||
the stem's faces on the crossing wall's near face. That face is cut where
|
the stem's faces on the crossing wall's near face. That face is cut where
|
||||||
@@ -54,6 +58,14 @@ from engineering.mech.primitives import HatchArea, Line, Pt
|
|||||||
#: this engine, not a building rule.
|
#: this engine, not a building rule.
|
||||||
MIN_JOIN_ANGLE_DEG = 5.0
|
MIN_JOIN_ANGLE_DEG = 5.0
|
||||||
|
|
||||||
|
#: How far from its node, in thicknesses of the thicker wall, the face mitre of
|
||||||
|
#: a near-straight join may lie. Two faces at equal offsets meet within 1.42
|
||||||
|
#: thicknesses of the node at any kink up to 90 degrees; faces at different
|
||||||
|
#: offsets (a thickness or justification change) meet at offset / sin(kink),
|
||||||
|
#: kilometres away for a rounding kink, so past this reach the join steps
|
||||||
|
#: instead of flaring.
|
||||||
|
_MITRE_REACH = 2.0
|
||||||
|
|
||||||
_EPS = 1e-9
|
_EPS = 1e-9
|
||||||
#: Decimals a point is rounded to when face lines are matched end to end.
|
#: Decimals a point is rounded to when face lines are matched end to end.
|
||||||
_KEY_DECIMALS = 6
|
_KEY_DECIMALS = 6
|
||||||
@@ -123,7 +135,9 @@ def _polygon(points: Sequence[Pt]) -> tuple[Pt, ...] | None:
|
|||||||
|
|
||||||
Every caller builds its ring counter-clockwise - a piece as right face out,
|
Every caller builds its ring counter-clockwise - a piece as right face out,
|
||||||
left face back; a hub in the angular order of its corners - so the ring is
|
left face back; a hub in the angular order of its corners - so the ring is
|
||||||
not turned here. The area is `engineering.measure`'s, not a second shoelace.
|
not turned here; a ring that comes out clockwise is inside out, and the
|
||||||
|
callers refuse it (`_counter_clockwise`). The area is
|
||||||
|
`engineering.measure`'s, not a second shoelace.
|
||||||
"""
|
"""
|
||||||
kept: list[Pt] = []
|
kept: list[Pt] = []
|
||||||
for p in points:
|
for p in points:
|
||||||
@@ -137,6 +151,12 @@ def _polygon(points: Sequence[Pt]) -> tuple[Pt, ...] | None:
|
|||||||
return tuple(kept) if area > _EPS else None
|
return tuple(kept) if area > _EPS else None
|
||||||
|
|
||||||
|
|
||||||
|
def _counter_clockwise(ring: Sequence[Pt]) -> bool:
|
||||||
|
"""The ring's turning sense - the sign `polygon_area_perimeter` folds away."""
|
||||||
|
twice = sum(_cross(p, q) for p, q in zip(ring, (*ring[1:], ring[0]), strict=True))
|
||||||
|
return twice > 0.0
|
||||||
|
|
||||||
|
|
||||||
def _key(p: Pt) -> tuple[float, float]:
|
def _key(p: Pt) -> tuple[float, float]:
|
||||||
return (round(p[0], _KEY_DECIMALS) + 0.0, round(p[1], _KEY_DECIMALS) + 0.0)
|
return (round(p[0], _KEY_DECIMALS) + 0.0, round(p[1], _KEY_DECIMALS) + 0.0)
|
||||||
|
|
||||||
@@ -179,7 +199,10 @@ class _Seg:
|
|||||||
vb: tuple[int, int]
|
vb: tuple[int, int]
|
||||||
left: float
|
left: float
|
||||||
right: float
|
right: float
|
||||||
cuts: list[float] = field(default_factory=list)
|
#: Where other axes stop on or cross this one, as points, never as distances:
|
||||||
|
#: a distance is measured from the segment's start, and that start may still
|
||||||
|
#: be carried onto a third wall later in the same pass.
|
||||||
|
cuts: list[Pt] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -296,7 +319,7 @@ def _resolve_tees(walls, pos, segs, tol: float) -> None:
|
|||||||
pos[vertex] = b
|
pos[vertex] = b
|
||||||
else:
|
else:
|
||||||
pos[vertex] = x
|
pos[vertex] = x
|
||||||
other.cuts.append(sx)
|
other.cuts.append(x)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
@@ -319,8 +342,9 @@ def _resolve_crossings(walls, pos, segs, tol: float) -> None:
|
|||||||
l2 = math.hypot(*q)
|
l2 = math.hypot(*q)
|
||||||
if tol < t * l1 < l1 - tol and tol < w * l2 < l2 - tol:
|
if tol < t * l1 < l1 - tol and tol < w * l2 < l2 - tol:
|
||||||
_refuse_shallow(walls, one, other, _unit(a1, b1), _unit(a2, b2))
|
_refuse_shallow(walls, one, other, _unit(a1, b1), _unit(a2, b2))
|
||||||
one.cuts.append(t * l1)
|
crossing = _add(a1, _mul(r, t))
|
||||||
other.cuts.append(w * l2)
|
one.cuts.append(crossing)
|
||||||
|
other.cuts.append(crossing)
|
||||||
|
|
||||||
|
|
||||||
def _pieces(walls, pos, segs, tol: float) -> list[_Piece]:
|
def _pieces(walls, pos, segs, tol: float) -> list[_Piece]:
|
||||||
@@ -335,7 +359,7 @@ def _pieces(walls, pos, segs, tol: float) -> list[_Piece]:
|
|||||||
)
|
)
|
||||||
u = _unit(a, b)
|
u = _unit(a, b)
|
||||||
stops: list[float] = []
|
stops: list[float] = []
|
||||||
for cut in sorted(seg.cuts):
|
for cut in sorted(_dot(_sub(point, a), u) for point in seg.cuts):
|
||||||
if tol < cut < length - tol and (not stops or cut - stops[-1] > tol):
|
if tol < cut < length - tol and (not stops or cut - stops[-1] > tol):
|
||||||
stops.append(cut)
|
stops.append(cut)
|
||||||
bounds = [0.0, *stops, length]
|
bounds = [0.0, *stops, length]
|
||||||
@@ -362,8 +386,8 @@ def _canonical(end: int, side: str) -> str:
|
|||||||
return "right" if side == "left" else "left"
|
return "right" if side == "left" else "left"
|
||||||
|
|
||||||
|
|
||||||
def _foot(node: Pt, p: Pt, u: Pt) -> Pt:
|
def _thickness(piece: _Piece) -> float:
|
||||||
return _add(p, _mul(u, _dot(_sub(node, p), u)))
|
return piece.seg.left - piece.seg.right
|
||||||
|
|
||||||
|
|
||||||
def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float) -> dict:
|
def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float) -> dict:
|
||||||
@@ -405,7 +429,7 @@ def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float)
|
|||||||
jambs.append((Line(pl, pr, "wall"), walls[piece.seg.wall].id))
|
jambs.append((Line(pl, pr, "wall"), walls[piece.seg.wall].id))
|
||||||
continue
|
continue
|
||||||
ordered = sorted(members, key=lambda m: _heading(_outgoing(*m)[1]))
|
ordered = sorted(members, key=lambda m: _heading(_outgoing(*m)[1]))
|
||||||
hub: list[Pt] = []
|
pairs: list[list] = []
|
||||||
for idx, (pi, ei) in enumerate(ordered):
|
for idx, (pi, ei) in enumerate(ordered):
|
||||||
pj, ej = ordered[(idx + 1) % len(ordered)]
|
pj, ej = ordered[(idx + 1) % len(ordered)]
|
||||||
oi, ui, ni, li, _ri = _outgoing(pi, ei)
|
oi, ui, ni, li, _ri = _outgoing(pi, ei)
|
||||||
@@ -419,15 +443,33 @@ def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float)
|
|||||||
)
|
)
|
||||||
face_i = _add(oi, _mul(ni, li))
|
face_i = _add(oi, _mul(ni, li))
|
||||||
face_j = _add(oj, _mul(nj, rj))
|
face_j = _add(oj, _mul(nj, rj))
|
||||||
if abs(_cross(ui, uj)) <= 1e-12:
|
mitre = None
|
||||||
ci = _foot(node, face_i, ui)
|
if abs(_cross(ui, uj)) > 1e-12:
|
||||||
cj = _foot(node, face_j, uj)
|
mitre = _meet(face_i, ui, face_j, uj)
|
||||||
|
reach = _MITRE_REACH * max(_thickness(pi), _thickness(pj))
|
||||||
|
if _dot(ui, uj) < 0.0 and math.dist(mitre, node) > reach:
|
||||||
|
mitre = None # a near-straight join whose faces are offset: a step
|
||||||
|
pairs.append([pi, ei, pj, ej, face_i, ui, face_j, uj, mitre])
|
||||||
|
if len(pairs) == 2 and any(pair[8] is None for pair in pairs):
|
||||||
|
# both sides of one straight-through join step on the same cut line,
|
||||||
|
# or the two pieces would leave a sliver between their ends
|
||||||
|
for pair in pairs:
|
||||||
|
pair[8] = None
|
||||||
|
hub: list[Pt] = []
|
||||||
|
for pi, ei, pj, ej, face_i, ui, face_j, uj, mitre in pairs:
|
||||||
|
if mitre is not None:
|
||||||
|
ci = cj = mitre
|
||||||
|
else:
|
||||||
|
# the step: both faces stop on the line through the node that
|
||||||
|
# bisects the join - square across a straight join, and the line
|
||||||
|
# every equal-offset mitre of this node lies on anyway
|
||||||
|
cut = _left(_sub(ui, uj))
|
||||||
|
ci = _meet(face_i, ui, node, cut)
|
||||||
|
cj = _meet(face_j, uj, node, cut)
|
||||||
if math.dist(ci, cj) <= _EPS:
|
if math.dist(ci, cj) <= _EPS:
|
||||||
cj = ci
|
cj = ci
|
||||||
else:
|
else:
|
||||||
faces.append((Line(ci, cj, "wall"), walls[pi.seg.wall].id))
|
faces.append((Line(ci, cj, "wall"), walls[pi.seg.wall].id))
|
||||||
else:
|
|
||||||
ci = cj = _meet(face_i, ui, face_j, uj)
|
|
||||||
pi.corners[(ei, _canonical(ei, "left"))] = ci
|
pi.corners[(ei, _canonical(ei, "left"))] = ci
|
||||||
pj.corners[(ej, _canonical(ej, "right"))] = cj
|
pj.corners[(ej, _canonical(ej, "right"))] = cj
|
||||||
hub.append(ci)
|
hub.append(ci)
|
||||||
@@ -435,6 +477,11 @@ def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float)
|
|||||||
hub.append(cj)
|
hub.append(cj)
|
||||||
if len(ordered) >= 3:
|
if len(ordered) >= 3:
|
||||||
polygon = _polygon(hub)
|
polygon = _polygon(hub)
|
||||||
|
if polygon and not _counter_clockwise(polygon):
|
||||||
|
raise ValueError(
|
||||||
|
f"the junction at ({node[0]:g}, {node[1]:g}) cannot be resolved into a "
|
||||||
|
"clean outline - its middle turns inside out; move the walls' ends apart."
|
||||||
|
)
|
||||||
if polygon:
|
if polygon:
|
||||||
owner = min(piece.seg.wall for piece, _end in ordered)
|
owner = min(piece.seg.wall for piece, _end in ordered)
|
||||||
regions.append((polygon, walls[owner].id))
|
regions.append((polygon, walls[owner].id))
|
||||||
@@ -522,7 +569,7 @@ def _build(walls: Sequence[Wall], openings: Sequence[Opening], join_tol: float)
|
|||||||
if math.dist(sr, er) > _EPS:
|
if math.dist(sr, er) > _EPS:
|
||||||
faces.append((Line(sr, er, "wall"), owner))
|
faces.append((Line(sr, er, "wall"), owner))
|
||||||
polygon = _polygon((sr, er, el, sl))
|
polygon = _polygon((sr, er, el, sl))
|
||||||
if polygon and is_self_intersecting(polygon):
|
if polygon and (is_self_intersecting(polygon) or not _counter_clockwise(polygon)):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{_name(walls, seg.wall)}: segment {seg.index} is too short for the "
|
f"{_name(walls, seg.wall)}: segment {seg.index} is too short for the "
|
||||||
"junctions at its ends - its outline would cross itself; lengthen it "
|
"junctions at its ends - its outline would cross itself; lengthen it "
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ the stem's axis.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from engineering.arch.model import Opening, Wall
|
from engineering.arch.model import Opening, Wall
|
||||||
@@ -132,6 +135,117 @@ def test_every_region_is_counter_clockwise():
|
|||||||
assert all(signed_area(r) > 0 for r in geo.regions)
|
assert all(signed_area(r) > 0 for r in geo.regions)
|
||||||
|
|
||||||
|
|
||||||
|
# -- straight joins whose faces do not line up ---------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_justification_change_on_a_straight_axis_steps_across_the_node():
|
||||||
|
geo = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", (0, 0), (3000, 0), justification="left"),
|
||||||
|
wall("b", (3000, 0), (6000, 0), justification="right"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert segs(geo.faces) == {
|
||||||
|
S(0, 0, 3000, 0),
|
||||||
|
S(0, 200, 3000, 200),
|
||||||
|
S(3000, 200, 3000, 0), # the step: each face of a stops on the square cut
|
||||||
|
S(3000, 0, 3000, -200), # through the node, and so does b's
|
||||||
|
S(3000, 0, 6000, 0),
|
||||||
|
S(3000, -200, 6000, -200),
|
||||||
|
}
|
||||||
|
assert {ring(r) for r in geo.regions} == {
|
||||||
|
ring([(0, 0), (3000, 0), (3000, 200), (0, 200)]),
|
||||||
|
ring([(3000, -200), (6000, -200), (6000, 0), (3000, 0)]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_rounding_kink_steps_like_the_straight_join_instead_of_mitring_kilometres_away():
|
||||||
|
# 0.5 mm over 3 m: the faces 200 mm apart would meet at x = 1 203 000
|
||||||
|
geo = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", (0, 0), (3000, 0), justification="left"),
|
||||||
|
wall("b", (3000, 0), (6000, 0.5), justification="right"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
points = [p for line in geo.faces for p in (line.p1, line.p2)]
|
||||||
|
# b's free end is square to its own axis: 200 * sin(0.5 / 3000) past x = 6000
|
||||||
|
assert max(abs(x) for x, _y in points) <= 6000.0 + 0.04
|
||||||
|
assert max(abs(y) for _x, y in points) <= 200.5 + 1e-6
|
||||||
|
assert all(signed_area(r) > 0 for r in geo.regions)
|
||||||
|
assert sorted(signed_area(r) for r in geo.regions) == pytest.approx([600000, 600000], abs=50)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_thickness_change_at_a_small_kink_steps_at_the_node_and_does_not_flare():
|
||||||
|
kink = math.radians(2.0)
|
||||||
|
geo = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", (0, 0), (3000, 0), thickness=100.0),
|
||||||
|
wall(
|
||||||
|
"b",
|
||||||
|
(3000, 0),
|
||||||
|
(3000 + 3000 * math.cos(kink), 3000 * math.sin(kink)),
|
||||||
|
thickness=300.0,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
a_faces = [line for line in geo.faces if abs(line.p1[1] - line.p2[1]) < 1e-9]
|
||||||
|
# a's two 100 mm faces stop at the node (the bisector of a 2 degree join), not
|
||||||
|
# 2865 mm into b where they would meet b's 300 mm faces
|
||||||
|
assert {round(y, 6) for line in a_faces for y in (line.p1[1], line.p2[1])} == {50.0, -50.0}
|
||||||
|
assert max(x for line in a_faces for x in (line.p1[0], line.p2[0])) < 3002.0
|
||||||
|
assert all(signed_area(r) > 0 for r in geo.regions)
|
||||||
|
assert sorted(signed_area(r) for r in geo.regions) == pytest.approx([300000, 900000], abs=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_rounded_rotated_thickness_change_draws_the_same_step_as_the_exact_one():
|
||||||
|
c, s = math.cos(math.radians(30)), math.sin(math.radians(30))
|
||||||
|
|
||||||
|
def along(d, digits=None):
|
||||||
|
x, y = d * c, d * s
|
||||||
|
return (round(x, digits), round(y, digits)) if digits is not None else (x, y)
|
||||||
|
|
||||||
|
exact = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", along(0), along(3000), thickness=100.0),
|
||||||
|
wall("b", along(3000), along(6000), thickness=300.0),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
rounded = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", along(0, 2), along(3000, 2), thickness=100.0),
|
||||||
|
wall("b", along(3000, 2), along(6000, 2), thickness=300.0),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert len(rounded.faces) == len(exact.faces) == 6
|
||||||
|
for mine, theirs in zip(
|
||||||
|
sorted(seg(line) for line in rounded.faces),
|
||||||
|
sorted(seg(line) for line in exact.faces),
|
||||||
|
strict=True,
|
||||||
|
):
|
||||||
|
assert math.dist(mine[0], theirs[0]) < 0.05 and math.dist(mine[1], theirs[1]) < 0.05
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("kink", [0.0, 0.01, 0.5, 2.0, 8.0, 20.0, 45.0, -2.0, -30.0])
|
||||||
|
@pytest.mark.parametrize("justs", list(itertools.product(("center", "left", "right"), repeat=2)))
|
||||||
|
@pytest.mark.parametrize("thicknesses", [(200.0, 200.0), (100.0, 300.0)])
|
||||||
|
def test_a_straight_or_kinked_join_stays_near_its_node_and_every_region_is_ccw(
|
||||||
|
kink, justs, thicknesses
|
||||||
|
):
|
||||||
|
turn = math.radians(30.0 + kink)
|
||||||
|
p1 = (round(3000 * math.cos(math.radians(30)), 2), round(3000 * math.sin(math.radians(30)), 2))
|
||||||
|
p2 = (round(p1[0] + 3000 * math.cos(turn), 2), round(p1[1] + 3000 * math.sin(turn), 2))
|
||||||
|
geo = wall_geometry(
|
||||||
|
[
|
||||||
|
wall("a", (0, 0), p1, thickness=thicknesses[0], justification=justs[0]),
|
||||||
|
wall("b", p1, p2, thickness=thicknesses[1], justification=justs[1]),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
reach = 6000 + 2 * max(thicknesses)
|
||||||
|
assert all(math.hypot(*p) < reach for line in geo.faces for p in (line.p1, line.p2))
|
||||||
|
assert len(geo.regions) == 2
|
||||||
|
assert all(signed_area(r) > 0 for r in geo.regions)
|
||||||
|
|
||||||
|
|
||||||
# -- T -----------------------------------------------------------------------------
|
# -- T -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -163,6 +277,47 @@ def test_a_stem_drawn_to_the_face_is_carried_to_the_axis():
|
|||||||
assert segs(touching.faces) == segs(on_axis.faces)
|
assert segs(touching.faces) == segs(on_axis.faces)
|
||||||
|
|
||||||
|
|
||||||
|
# c runs along y = 0; a is drawn to c's face (its start is carried from y = 100
|
||||||
|
# down to c's axis) and b is a T into a's middle at y = 2000. The T on a must not
|
||||||
|
# depend on whether a's start has been carried yet when b is resolved.
|
||||||
|
T_ON_A_CARRIED_WALL = {
|
||||||
|
S(-1000, -100, 5000, -100), # c's far face, whole
|
||||||
|
S(-1000, 100, 1900, 100), # c's near face, cut by a
|
||||||
|
S(2100, 100, 5000, 100),
|
||||||
|
S(1900, 100, 1900, 1900), # a's near face to b, cut by b at 1900 / 2100
|
||||||
|
S(1900, 2100, 1900, 4000),
|
||||||
|
S(2100, 100, 2100, 4000), # a's far face to b, whole
|
||||||
|
S(0, 1900, 1900, 1900), # b stops on a's near face
|
||||||
|
S(0, 2100, 1900, 2100),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("order", list(itertools.permutations("cab")))
|
||||||
|
def test_a_t_into_a_carried_wall_does_not_depend_on_the_order_of_the_walls(order):
|
||||||
|
walls = {
|
||||||
|
"c": wall("c", (-1000, 0), (5000, 0)),
|
||||||
|
"a": wall("a", (2000, 100), (2000, 4000)),
|
||||||
|
"b": wall("b", (0, 2000), (2000, 2000)),
|
||||||
|
}
|
||||||
|
geo = wall_geometry([walls[name] for name in order])
|
||||||
|
assert segs(geo.faces) == T_ON_A_CARRIED_WALL
|
||||||
|
assert segs(geo.jambs) == {
|
||||||
|
S(-1000, -100, -1000, 100),
|
||||||
|
S(5000, -100, 5000, 100),
|
||||||
|
S(1900, 4000, 2100, 4000),
|
||||||
|
S(0, 1900, 0, 2100), # b's free end - and no jamb inside wall a
|
||||||
|
}
|
||||||
|
assert {ring(r) for r in geo.regions} == {
|
||||||
|
ring([(-1000, -100), (2000, -100), (1900, 100), (-1000, 100)]),
|
||||||
|
ring([(2000, -100), (5000, -100), (5000, 100), (2100, 100)]),
|
||||||
|
ring([(2100, 100), (1900, 100), (2000, -100)]), # the hub on c
|
||||||
|
ring([(2100, 100), (2100, 2000), (1900, 1900), (1900, 100)]), # a, split at y = 2000
|
||||||
|
ring([(2100, 2000), (2100, 4000), (1900, 4000), (1900, 2100)]),
|
||||||
|
ring([(1900, 2100), (1900, 1900), (2100, 2000)]), # the hub on a
|
||||||
|
ring([(0, 1900), (1900, 1900), (1900, 2100), (0, 2100)]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# -- X -----------------------------------------------------------------------------
|
# -- X -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user