"""Phase 0 tests for the CutPlan model.""" import json from woodshop.cutplan import CutPlan, ShopSettings, build_cut_plan, validate_cut_plan from woodshop.scene import Scene def test_lumber_plan_packs_and_validates(): s = Scene() for _ in range(3): s.place("2x4", 40) plan = build_cut_plan(s) sticks = [sp for sp in plan.stock_pieces if not sp.is_sheet] assert len(sticks) == 2 assert sum(len(sp.placements) for sp in sticks) == 3 assert plan.score["stock_count"] == 2 assert validate_cut_plan(plan) == [] def test_kerf_prevents_two_48_in_one_stick(): s = Scene() s.place("2x4", 48) s.place("2x4", 48) assert build_cut_plan(s).score["stock_count"] == 2 def test_tenon_extends_cut_item_length(): s = Scene() s.place("2x4", 24) s.add_feature("p1", "tenon", face="end_b", depth_in=2) item = build_cut_plan(s).items[0] assert item.length_in == 26 and "tenon" in item.note def test_plywood_plan_and_validate(): s = Scene() s.place("ply-3/4", 40, width_in=20) s.place("ply-3/4", 40, width_in=20) plan = build_cut_plan(s) sheets = [sp for sp in plan.stock_pieces if sp.is_sheet] assert len(sheets) == 1 and len(sheets[0].placements) == 2 assert validate_cut_plan(plan) == [] def test_oversize_lumber_warns_and_is_unplaced(): s = Scene() s.place("2x4", 120) # longer than a 96" stick plan = build_cut_plan(s) assert plan.unplaced and plan.warnings assert validate_cut_plan(plan) == [] # flagged, so still valid def test_stable_ids_present(): s = Scene() s.place("2x4", 40) plan = build_cut_plan(s) assert all(it.id for it in plan.items) assert all(sp.id for sp in plan.stock_pieces) assert all(p.id for sp in plan.stock_pieces for p in sp.placements) def test_json_roundtrip(): s = Scene() s.place("2x4", 40) s.place("ply-3/4", 40, width_in=20) plan = build_cut_plan(s) plan2 = CutPlan.from_dict(json.loads(json.dumps(plan.to_dict()))) assert plan2.settings.kerf_in == plan.settings.kerf_in assert [sp.id for sp in plan2.stock_pieces] == [sp.id for sp in plan.stock_pieces] assert plan2.score["stock_count"] == plan.score["stock_count"] assert validate_cut_plan(plan2) == [] def test_plywood_rotation_fits_panel(): s = Scene() s.place("ply-3/4", 30, width_in=60) # 60" wide > 48" sheet — needs rotating plan = build_cut_plan(s) # rotation allowed by default sheets = [sp for sp in plan.stock_pieces if sp.is_sheet] assert len(sheets) == 1 p = sheets[0].placements[0] assert p.rotated and p.len_in == 60 and p.wid_in == 30 assert validate_cut_plan(plan) == [] def test_rotation_disabled_flags_unfit(): s = Scene() s.place("ply-3/4", 30, width_in=60) plan = build_cut_plan(s, settings=ShopSettings(allow_plywood_rotation=False)) assert plan.unplaced and plan.warnings def test_best_cut_plan_is_no_worse(): from woodshop.cutplan import _plan_key, best_cut_plan s = Scene() for ln in (50, 46, 30, 30, 20): s.place("2x4", ln) best = best_cut_plan(s) base = build_cut_plan(s, strategy="decreasing") assert _plan_key(best) <= _plan_key(base) assert best.strategy == "optimized" assert validate_cut_plan(best) == [] def test_custom_settings_kerf(): s = Scene() s.place("2x4", 48) s.place("2x4", 48) # zero kerf -> both 48" fit in one 96" stick assert build_cut_plan(s, settings=ShopSettings(kerf_in=0.0)).score["stock_count"] == 1