Commit Graph

4 Commits

Author SHA1 Message Date
rob 067ec0ea46 Add cost estimate (Cost tab) with editable Kent NB price book
New shop-packet output: a printable cost estimate driven by the active
CutPlan's buy-counts × a curated, editable price book (HST 15%).

- prices.py: DEFAULT_PRICES seeded with real Kent (New Brunswick) shelf
  prices per buy-unit (lumber = 8' stick, plywood = 4x8 sheet); persisted to
  $XDG_CONFIG_HOME/woodshop/prices.json (defaults + saved overrides).
  estimate() -> CostEstimate (lines/subtotal/tax/total/missing); lumber price
  scales with stick length; unknown stock is flagged, never invented.
- BOM window: Cost tab with "Edit prices…" (PriceEditDialog), "Refresh from
  Kent…", and Print.
- fetch_kent_prices() + scripts/fetch_kent_prices.py: best-effort refresh.
  Kent renders prices client-side (not in HTML), so it tries a static parse
  then Playwright if installed — honest that it may need updating.
- tests: estimate math, per-sheet plywood, stick-length scaling, missing-price
  flagging, save/load roundtrip, corrupt-file fallback, JSON-LD parse, cost
  tab render + price edit persistence. 153 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 16:23:59 -03:00
rob 9d80be4e7f Fix lock-aware plywood reopt + honest Best-of-100 when locked
Codex findings:
1. reoptimize sent unlocked plywood to fresh sheets whenever any sheet
   placement was locked, instead of packing into free space on the locked
   sheet — so locking one of two panels that share a sheet split them onto
   two sheets. Added _free_rects_sheet (guillotine subtraction carving free
   rectangles around locked panels) + _pack_plywood_seeded, and refactored
   _pack_plywood_guillotine onto a shared _guillotine_pack core that accepts
   seeded sheets. reoptimize now uses it for the plywood branch.
2. "Best of 100" only tried the ~6 STRATEGIES when locks existed. The locked
   path now runs strategies + shuffle restarts up to 100 attempts via
   reoptimize, matching the label.

Tests: plywood lock keeps both panels on one sheet; locked Best-of-100 stays valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:46:39 -03:00
rob 274e87e239 Phase 1: bounded-exact lumber, guillotine plywood, Best-of-N
- _min_bins: branch-and-bound minimum stick count (FFD-seeded + count bound)
- _pack_lumber_exact: provably-minimum packing for small jobs (<=12 pieces)
- _pack_plywood_guillotine: free-rectangle best-area-fit packing + rotation
- build_cut_plan dispatches strategy=="exact"/"guillotine"; added to STRATEGIES
- richer scoring: reusable_in (longer offcuts) as _plan_key tie-break
- best_cut_plan tries exact+guillotine; "Best of 100" button in Cut Layout tab
- tests: exact<=FFD, oversize handling, guillotine packs/validates, best-of-N

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:37:11 -03:00
rob 38391175b4 Fix drag-drop crash + cover it with offscreen GUI tests
_drop_piece looked up plan.item(item.pid), but item.pid is a Placement id (pl2)
while CutPlan.item() expects a CutItem id (ci2) — every drop raised StopIteration
before validate/revert could run. Use the already-found placement's item_id
(plan.item(p.item_id)) for the stock-compat check and message.

Added tests/test_bom_window.py (offscreen QGraphicsScene): drop-overlap reverts
without crashing, drop-onto-incompatible-stock reverts, and a valid move commits.

128 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 15:18:01 -03:00