woodshop/tests/test_miter.py

160 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""End miter / bevel angled cuts."""
import json
import pytest
from woodshop.scene import Scene
def test_add_miter_defaults_to_45_on_end():
s = Scene()
s.place("2x4", 24)
f = s.add_feature("p1", "miter") # no angle given
assert f.kind == "miter" and f.face == "end_b" and f.miter_deg == 45.0
def test_miter_forced_to_end_face():
s = Scene()
s.place("2x4", 24)
f = s.add_feature("p1", "miter", face="top", miter_deg=30) # top is invalid for miter
assert f.face == "end_b" and f.miter_deg == 30
def test_miter_roundtrips_through_json():
s = Scene()
s.place("2x4", 24)
s.add_feature("p1", "miter", miter_deg=45, bevel_deg=15)
s2 = Scene.from_dict(json.loads(json.dumps(s.to_dict())))
f = s2.get_part("p1").features[0]
assert f.kind == "miter" and f.miter_deg == 45 and f.bevel_deg == 15
def test_cutlist_notes_the_miter():
from woodshop.cutplan import build_cut_plan
s = Scene()
s.place("2x4", 24)
s.add_feature("p1", "miter", miter_deg=45)
note = build_cut_plan(s).items[0].note
assert "miter 45" in note
def test_instructions_describe_miter():
from woodshop.instructions import build_steps, format_steps
s = Scene()
s.place("2x4", 24)
s.add_feature("p1", "miter", miter_deg=45, bevel_deg=10)
text = format_steps(build_steps(s))
assert "miter 45" in text and "bevel 10" in text
def test_jigs_suggest_miter_sled_for_repeats():
from woodshop.jigs import suggest_jigs
s = Scene()
for _ in range(4):
s.place("2x4", 24)
for pid in ("p1", "p2", "p3", "p4"):
s.add_feature(pid, "miter", miter_deg=45)
kinds = [j.kind for j in suggest_jigs(s)]
assert "miter-sled" in kinds
def test_miter_geometry_removes_material():
pytest.importorskip("build123d")
from woodshop.geometry import part_solid
s = Scene()
s.place("2x4", 24)
square_vol = part_solid(s.get_part("p1")).volume
s.add_feature("p1", "miter", miter_deg=45)
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
def test_miter_cuts_full_width_not_a_corner():
"""A 45° miter on a 2x4 should span the FULL width (edge-to-edge), not just
notch a corner — the removed wedge's width ≈ the board width."""
pytest.importorskip("build123d")
from woodshop.geometry import miter_cutter
from build123d import Box, Pos
s = Scene()
s.place("2x4", 24)
p = s.get_part("p1")
t, w = p.section_in
feat = s.add_feature("p1", "miter", miter_deg=45)
board = Pos(24 / 2, 0, 0) * Box(24, w, t)
wedge = board & miter_cutter(feat, 24, w, t)
b = wedge.bounding_box()
y_extent = b.max.Y - b.min.Y
assert y_extent > 0.9 * w # spans (nearly) the full 3.5" width
def test_edit_feature_keeps_miter_on_an_end():
s = Scene()
s.place("2x4", 24)
f = s.add_feature("p1", "miter", miter_deg=45)
s.edit_feature(f.id, face="top") # invalid for a miter — should be ignored
assert f.face in ("end_a", "end_b")
def test_offset_to_centre_notches_a_corner():
"""A 45° miter hinged at the CENTRE (offset = half width) removes only a
corner, unlike the full-width edge cut (offset 0)."""
pytest.importorskip("build123d")
from woodshop.geometry import miter_cutter
from build123d import Box, Pos
s = Scene()
s.place("2x4", 24)
t, w = s.get_part("p1").section_in
f = s.add_feature("p1", "miter", miter_deg=45, miter_offset_in=w / 2)
board = Pos(12, 0, 0) * Box(24, w, t)
wedge = board & miter_cutter(f, 24, w, t)
b = wedge.bounding_box()
assert (b.max.Y - b.min.Y) < 0.75 * w # only a corner, not full width
def test_two_centre_offset_miters_make_a_point():
"""+45 and 45 hinged at centre on the same end bring it to a point."""
pytest.importorskip("build123d")
from woodshop.geometry import part_solid
s = Scene()
s.place("2x4", 24)
half = s.get_part("p1").section_in[1] / 2
full = part_solid(s.get_part("p1")).volume
s.add_feature("p1", "miter", miter_deg=45, miter_offset_in=half)
s.add_feature("p1", "miter", miter_deg=-45, miter_offset_in=half)
pointed = part_solid(s.get_part("p1")).volume
assert pointed < full # both corners gone -> a point
def test_offset_roundtrips():
s = Scene()
s.place("2x4", 24)
s.add_feature("p1", "miter", miter_deg=45, miter_offset_in=1.75)
s2 = Scene.from_dict(json.loads(json.dumps(s.to_dict())))
assert s2.get_part("p1").features[0].miter_offset_in == 1.75
def test_chamfer_preview_shows_removed_bevel():
pytest.importorskip("pyvista")
pytest.importorskip("build123d")
from woodshop.viewer import feature_preview_mesh
s = Scene()
s.place("2x4", 24)
f = s.add_feature("p1", "chamfer", face="top", width_in=0.25)
mesh = feature_preview_mesh(s.get_part("p1"), f)
assert mesh is not None and mesh.n_points > 0 # the bevel slivers, not a slab