- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
docs/HANDOFF.md: current state, the one unblocked first task (midtone starvation -- allocate levels inside the subject mask, background pinned open), seven ranked questions, and the traps not to re-derive. AGENTS.md now points at it as the entry point for a fresh session. Highest-value question by a distance is whether the hand-made barbarian masks can be shared. They are ground truth: a real fitness target instead of a proxy, the actual level-area allocation that is currently the blocker, whether the three masks are nested, and the vertex density that sets the tracing epsilon. Also asks the question that should have come first last session -- what the product actually is. Full automation has been the default assumption and was never checked; "tool produces masks, human refines" may be most of the value for a fraction of the difficulty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| docs | ||
| scripts | ||
| src/shadowbox | ||
| .gitignore | ||
| AGENTS.md | ||
| pyproject.toml | ||
| README.md | ||
shadowbox
Any image → three laser-cut layers for backlit shadowbox panels (xTool S1, 3mm ply, etc.).
Not a paper-cut-only tool and not an engraving optimizer. Structure comes from scene geometry (depth) so photos, painterly art, graphic gens, and mixed styles can all feed the same pipeline.
Stack modes
relief (default) — reverse-nested, every tonal band gets a distinct physical depth.
Dark-room physics of an opaque backlit stack: front face is darkest, each revealed
deeper sheet catches more rim glow, full open is brightest.
| Band (dark → bright) | Physical identity |
|---|---|
| darkest | solid on L3 (front silhouette) |
| next | open on L3, solid on L2 (revealed, rim-lit) |
| next | open on L3+L2, solid on L1 (deep, glowing) |
| lightest + background | open through all three (full bright) |
Invariant (relief): open(L1) ⊆ open(L2) ⊆ open(L3)
nested — the classic convention: L1 most open, L3 least open,
open(L3) ⊆ open(L2) ⊆ open(L1), every L3 hole full-bright. Two tones only —
kept for pumpkin-style pierced looks.
Either way: solid islands are dropped or bridged (material must connect to the panel frame).
Setup
cd shadowbox
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
ComfyUI must be running with API on 0.0.0.0:8188 (or pass --comfy).
Needs:
- Depth Anything 3:
geometry_estimation/depth_anything_3_mono_large.safetensors - Z-Image-Turbo (optional, for
generate):z_image_turbo_bf16.safetensors,qwen_3_4b.safetensors,ae.safetensors
Usage
shadowbox health
# Any image (photo, paint, gen, screenshot…)
shadowbox layers whatever.png -o out/run1 --width-mm 100 --height-mm 100
# Optional: generate a source first, then same depth path
shadowbox generate "oil painting of a foggy pier at dusk, rich brushwork" \
-o out/pier --seed 7 --width-mm 100 --height-mm 100
# Offline fallback only (no depth model) — brightness-driven, style-sensitive
shadowbox layers photo.png -o out/run_lum --mode luminance
Outputs
| File | Meaning |
|---|---|
source.png |
Input / generated image |
depth.png |
DA3 depth (bright ≈ near) |
open_score.png |
Field that was thresholded into layers |
L1_open.png … L3_open.png |
White = cut away |
L1_cut.svg … L3_cut.svg |
Vector cut paths (+ red frame) |
preview_backlit.png |
Simulated interior light |
contact_sheet.png |
Overview |
meta.json |
Thresholds, open fractions, nesting check |
Blue strokes in SVG = cut. Red = panel frame. Import into XCS / LightBurn and scale as needed.
Design
Default path is depth + form (flow engine) in relief mode:
- Depth (DA3) — subject vs background. BG open on every layer.
- Flow abstraction — iterated bilateral (edge-preserving flatten) → subject-masked k-means (tonal budget spent on the figure, not the wall) → edge-aware small-region absorption (small regions join across their weakest boundary). Boundaries hug real image edges — no label-median mush.
- Score linework (XDoG) — eyelashes / straps / feather shafts extracted as strokes, exported as a green engrave group on L3 and simulated in the preview. Detail returns as line, never as sub-kerf cut voids.
- Relief mapping — bands → distinct depths (see stack modes above).
- Fabrication cleanup — morph, min-web, island drop/bridge (relief-aware nesting).
Structure / engine knobs
| Flag | Default | Effect |
|---|---|---|
--structure |
form |
form / hybrid / edges / depth |
--form-engine |
flow |
flow (edge-aware + score lines) / classic (old posterize) |
--stack-mode |
relief |
relief (tonal depth bands) / nested (full-bright holes) |
--levels |
5 |
Posterize grey count (4–6 for relief; 3–4 for nested). |
--min-blob |
400 |
Min region area in px² (also see --min-blob-mm). |
--min-blob-mm |
1.5 |
Min form region size in mm at export scale. |
--bridge / --no-bridge |
on | Connect solid islands to the panel frame. |
--bridge-mm |
1.2 |
Bridge web width (mm). |
--min-web-mm |
0.5 |
Minimum solid web after morph (mm). Large values blob the design. |
--curve |
1.4 |
Contrast before quantize. |
--backend |
krea2 |
Gen backend for generate. |
Outputs (cut-ready)
Per run directory:
L1_cut.svg…L3_cut.svg— blue cut, red frame, green registrationall_layers.svg— overlay of all three for inspectionpreview_backlit.png/contact_sheet.pngmeta.json— includesnesting_ok,frame_connected, bridge reports
Not for cut-through (yet)
Floyd–Steinberg / fine halftone → speckles smaller than kerf. Keep those for a later engrave fill on a solid plate, not as cut voids.
Generation is optional source material. The layer engine takes any image.
Design notes
- Depth prep: robust stretch + bilateral smooth so soft/painterly depth maps still band cleanly.
- Layout / sheet nesting on plywood is left to you.
- Island removal is manufacturability (floating wood falls out), not a style choice.