Flush-by-default joins (corner alignment)
Boards now align to A's reference corner when they butt — top faces level and one side flush — instead of B floating centered on A. The flush step snaps B's +faces onto A's +faces along A's cross-section axes, skipping the axis B extends along so the butt contact is preserved. Equal-size flat joints are unchanged; mixed sizes (e.g. a 1x8 shelf on a 2x4) now line up cleanly (tops level). Test: a 1x8 joined to a 2x4 sits tops-flush (center z=0.375), not centered. 53 tests passing; verified with a render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7d01144143
commit
9d21816542
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -84,11 +84,13 @@ pytest # 25 tests
|
|||
|
||||
### Known limitations / next steps
|
||||
|
||||
1. **Joins are butt joints**: B's end sits flush against A's surface (offset by
|
||||
A's cross-section half-extent in B's approach direction), so boards meet at
|
||||
faces rather than centerlines and don't interpenetrate. Not yet modeled:
|
||||
joinery *cuts* (mortise/tenon, lap, pocket holes) and flush-outer-face vs
|
||||
centered alignment options (currently B is centered on A at the attach point).
|
||||
1. **Joins are flush butt joints**: B's end sits flush against A's surface, and
|
||||
B is aligned to A's reference corner (top faces level + one side flush) rather
|
||||
than centered — so mixed-size boards line up cleanly. The flush step snaps
|
||||
B's +faces to A's +faces on A's cross-section axes, skipping the axis B
|
||||
extends along (so the butt contact is preserved). Not yet modeled: joinery
|
||||
*cuts* (mortise/tenon, lap, pocket holes), and the flush corner is fixed
|
||||
(A's +width/+thick side; no per-join choice of which corner / centered).
|
||||
2. **Latency** ~7–13s per utterance (one `claude -p` call).
|
||||
3. Voice path (`--voice`) reuses `dictate`; the driver loop is hardened against
|
||||
failures but the mic path isn't exercised in the unit tests.
|
||||
|
|
|
|||
|
|
@ -112,8 +112,9 @@ Key modules:
|
|||
|
||||
### Known limitations
|
||||
|
||||
- Joins are butt joints (B's end sits flush against A's face, not its
|
||||
centerline); joinery *cuts* (mortise/tenon, lap, pocket holes) aren't modeled yet.
|
||||
- Joins are flush butt joints: B's end sits against A's face and B aligns to
|
||||
A's reference corner (tops level + one side flush), so mixed-size boards line
|
||||
up. Joinery *cuts* (mortise/tenon, lap, pocket holes) aren't modeled yet.
|
||||
- Command interpretation latency is ~7–13s per utterance (one `claude -p` call).
|
||||
|
||||
## License
|
||||
|
|
|
|||
|
|
@ -253,6 +253,25 @@ class Scene:
|
|||
+ a_half_t * abs(_dot(b_len, a_thick)))
|
||||
b.position_in = [anchor_pt[i] + b_len[i] * surface for i in range(3)]
|
||||
|
||||
# Flush-by-default: align B to A's reference corner — its faces line up
|
||||
# with A's top and one side, rather than B floating centered on A. For
|
||||
# each of A's cross-section axes (skipping the one B extends along, so
|
||||
# the butt contact is preserved) snap B's +face onto A's +face.
|
||||
b_l, b_w, b_t = b.local_frame()
|
||||
|
||||
def b_half_extent(axis):
|
||||
return (b.length_in / 2 * abs(_dot(b_l, axis))
|
||||
+ b.section_in[1] / 2 * abs(_dot(b_w, axis))
|
||||
+ b.section_in[0] / 2 * abs(_dot(b_t, axis)))
|
||||
|
||||
for axis, a_half in ((a_thick, a_half_t), (a_width, a_half_w)):
|
||||
if abs(_dot(b_len, axis)) > 0.9: # B runs along this axis — leave it
|
||||
continue
|
||||
a_face = _dot(a.position_in, axis) + a_half # A's far face
|
||||
b_face = _dot(b.position_in, axis) + b_half_extent(axis)
|
||||
delta = a_face - b_face
|
||||
b.position_in = [b.position_in[i] + delta * axis[i] for i in range(3)]
|
||||
|
||||
jid = f"j{self._next_joint}"
|
||||
self._next_joint += 1
|
||||
joint = Joint(id=jid, part_a=a.id, part_b=b.id,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,19 @@ def test_butt_joint_meets_surface_not_centerline():
|
|||
assert p2.end_point()[1] == pytest.approx(13.75)
|
||||
|
||||
|
||||
def test_flush_aligns_tops_for_different_thicknesses():
|
||||
"""A thinner board joined to a thicker one should sit with TOPS level, not
|
||||
centers level (flush-by-default)."""
|
||||
s = Scene()
|
||||
s.place("2x4", 48) # p1: thickness 1.5 -> top face at z=0.75
|
||||
s.place("1x8", 12) # p2: thickness 0.75
|
||||
s.join("p1", "p2", angle_deg=90, offset_in=24, anchor="end_a")
|
||||
p2 = s.get_part("p2")
|
||||
# p2's top (z + 0.375) is flush with p1's top (0.75) -> p2 center at 0.375,
|
||||
# NOT centered on p1 (which would leave p2 at z=0).
|
||||
assert p2.position_in[2] == pytest.approx(0.375)
|
||||
|
||||
|
||||
def test_join_preserves_vertical_tilt():
|
||||
"""A stood-up leg stays vertical when attached to a horizontal apron."""
|
||||
s = Scene()
|
||||
|
|
|
|||
Loading…
Reference in New Issue