111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
"""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")
|