Fix miter preview/highlight: show the cut-off wedge, not a tenon box
The cyan (select) / red (edit) overlay for a miter fell through to the tenon/mortise box branch, so it looked like a 1" pocket at the end instead of an angled cut. - viewer._miter_wedge_mesh: build board ∩ cutter (the piece the miter removes), placed in world space, and return that as the preview/highlight mesh; falls back to highlighting the end face when the angle is 0. - factored tessellation into _solid_to_polydata; miter excluded from the normal-axis spin step. - test: the miter preview is a wedge reaching the board end, not a centre box. 238 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b284b58229
commit
12e4bbab88
|
|
@ -34,12 +34,11 @@ def _board_color(part: Part, index: int) -> str:
|
|||
return lighten(base, delta) if delta >= 0 else darken(base, -delta)
|
||||
|
||||
|
||||
def _featured_mesh(part: Part):
|
||||
"""Tessellate the true build123d solid (with joinery booleans) for display."""
|
||||
def _solid_to_polydata(solid):
|
||||
"""Tessellate a build123d solid into a pyvista PolyData."""
|
||||
import pyvista as pv
|
||||
|
||||
from .geometry import part_solid
|
||||
verts, tris = part_solid(part).tessellate(0.02)
|
||||
verts, tris = solid.tessellate(0.02)
|
||||
points = [(v.X, v.Y, v.Z) for v in verts]
|
||||
faces = []
|
||||
for tri in tris:
|
||||
|
|
@ -47,6 +46,43 @@ def _featured_mesh(part: Part):
|
|||
return pv.PolyData(points, faces)
|
||||
|
||||
|
||||
def _featured_mesh(part: Part):
|
||||
"""Tessellate the true build123d solid (with joinery booleans) for display."""
|
||||
from .geometry import part_solid
|
||||
return _solid_to_polydata(part_solid(part))
|
||||
|
||||
|
||||
def _miter_wedge_mesh(part: Part, feat):
|
||||
"""The piece a miter cuts OFF (board ∩ cutter), placed in world space — so the
|
||||
preview/highlight shows the angled cut, not a tenon-like box. None if no cut."""
|
||||
try:
|
||||
from build123d import Box, Pos, Rot
|
||||
L = part.length_in
|
||||
t, w = part.section_in
|
||||
board = Pos(L / 2, 0, 0) * Box(L, w, t)
|
||||
big = 3 * max(L, w, t) + 10
|
||||
block = Box(big, big, big)
|
||||
if feat.face == "end_a":
|
||||
cutter = Pos(-big / 2, 0, 0) * block
|
||||
pivot = (0.0, 0.0, 0.0)
|
||||
else:
|
||||
cutter = Pos(L + big / 2, 0, 0) * block
|
||||
pivot = (L, 0.0, 0.0)
|
||||
cutter = (Pos(*pivot) * Rot(Z=feat.miter_deg, Y=feat.bevel_deg)
|
||||
* Pos(*[-c for c in pivot]) * cutter)
|
||||
wedge = board & cutter # the removed piece
|
||||
mesh = _solid_to_polydata(wedge)
|
||||
if mesh.n_points == 0:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
mesh.rotate_x(part.roll_deg, point=(0, 0, 0), inplace=True)
|
||||
mesh.rotate_y(-part.tilt_deg, point=(0, 0, 0), inplace=True)
|
||||
mesh.rotate_z(part.yaw_deg, point=(0, 0, 0), inplace=True)
|
||||
mesh.translate(part.position_in, inplace=True)
|
||||
return mesh
|
||||
|
||||
|
||||
def _part_mesh(part: Part):
|
||||
import pyvista as pv
|
||||
|
||||
|
|
@ -92,6 +128,16 @@ def feature_preview_mesh(part, feat):
|
|||
h = feat.depth_in if feat.depth_in > 0 else thru
|
||||
c = tuple(fp[i] - n[i] * h / 2 for i in range(3))
|
||||
mesh = pv.Cylinder(center=c, direction=n, radius=feat.diameter_in / 2, height=h)
|
||||
elif feat.kind == "miter": # show the wedge it cuts off; fall back to the end face
|
||||
wedge = _miter_wedge_mesh(part, feat)
|
||||
if wedge is not None:
|
||||
return wedge
|
||||
ue, ve, thin = _axis_extent(u, L, w, t), _axis_extent(v, L, w, t), 0.08
|
||||
dims = tuple(ue * abs(u[i]) + ve * abs(v[i]) + thin * abs(n[i]) for i in range(3))
|
||||
c = fp
|
||||
mesh = pv.Box(bounds=(c[0] - dims[0] / 2, c[0] + dims[0] / 2,
|
||||
c[1] - dims[1] / 2, c[1] + dims[1] / 2,
|
||||
c[2] - dims[2] / 2, c[2] + dims[2] / 2))
|
||||
elif feat.kind == "chamfer": # can't cheaply preview the bevel — highlight the face
|
||||
ue, ve, thin = _axis_extent(u, L, w, t), _axis_extent(v, L, w, t), 0.08
|
||||
dims = tuple(ue * abs(u[i]) + ve * abs(v[i]) + thin * abs(n[i]) for i in range(3))
|
||||
|
|
@ -109,7 +155,7 @@ def feature_preview_mesh(part, feat):
|
|||
c[1] - dims[1] / 2, c[1] + dims[1] / 2,
|
||||
c[2] - dims[2] / 2, c[2] + dims[2] / 2))
|
||||
|
||||
if feat.rotation_deg and feat.kind not in ("hole", "chamfer"):
|
||||
if feat.rotation_deg and feat.kind not in ("hole", "chamfer", "miter"):
|
||||
mesh.rotate_vector(n, feat.rotation_deg, point=fp, inplace=True)
|
||||
mesh.rotate_x(part.roll_deg, point=(0, 0, 0), inplace=True)
|
||||
mesh.rotate_y(-part.tilt_deg, point=(0, 0, 0), inplace=True)
|
||||
|
|
|
|||
|
|
@ -68,3 +68,17 @@ def test_miter_geometry_removes_material():
|
|||
mitered_vol = part_solid(s.get_part("p1")).volume
|
||||
assert mitered_vol < square_vol # a wedge was cut off
|
||||
|
||||
|
||||
|
||||
def test_miter_preview_is_a_wedge_not_a_box():
|
||||
pytest.importorskip("pyvista")
|
||||
pytest.importorskip("build123d")
|
||||
from woodshop.viewer import feature_preview_mesh
|
||||
s = Scene()
|
||||
s.place("2x4", 24)
|
||||
feat = s.add_feature("p1", "miter", miter_deg=45)
|
||||
mesh = feature_preview_mesh(s.get_part("p1"), feat)
|
||||
assert mesh is not None and mesh.n_points > 0
|
||||
# the wedge sits at the mitered end (x≈24), not a 1" box at the end centre
|
||||
xmax = mesh.bounds[1]
|
||||
assert xmax > 20 # spans out to the board end, like the real cut
|
||||
|
|
|
|||
Loading…
Reference in New Issue