Getting started
Every tool in facet is a script you invoke directly, against paths you type. So this page covers what to have on the machine and the order the stages run in — that is the whole of the setup.
What you need
Section titled “What you need”| Python | 3.11+, with numpy, scipy, trimesh, open3d, Pillow, spandrel, torch |
| Blender | 5.x, reachable on PATH or by absolute path |
| GPU | developed against an RTX 5090. VRAM headroom matters more than raw speed |
| ComfyUI | local install, needed only for the inpainting brush stage |
Generation runs on metered cloud GPU; geometry and every measurement run locally.
git clone https://github.com/mcp-tool-shop-org/facetcd facetpython -m pytest -m "not artifacts" # the 887 hermetic tests CI reproducesThe full suite is 927 tests. The 40 it deselects are the artifacts tier — they replay recorded trees that are not in git, so they pass locally and are skipped in CI by design.
The order the stages run in
Section titled “The order the stages run in”Each stage exists for a measured reason, and the handbook’s front page gives the reasoning for every one. This is the sequence.
- A form-exaggerated clay concept. Sculpt-like, planes deliberately exaggerated, no surface noise. Reconstructors read weathered planks and painted grime as geometry.
- Image-to-3D reconstruction, plus a styled twin generated from the same control. The twin is the colour and identity reference for everything downstream.
- Weld, then decimate. A glTF export splits a vertex at every UV seam, so an exported mesh is per-triangle shells; collapse decimation on that tears holes. Merge by distance first.
- Allocate density where the form is — and only where a measurement says a region earns it. The bust crop is a per-subject decision, not a universal stage.
- Cull what no camera can see — from the atlas, never from the mesh. Deletion needs a perfect gate forever; exclusion makes the failure impossible.
- Generate twins from THIS mesh and project them. A twin carries the silhouette of the mesh it was rendered from, so a twin borrowed from another mesh paints into empty space.
- Brush the holes, spiralling outward from already-painted regions so each stroke extends an existing character rather than composing a new one.
- Finalize — surface-aware dilation closes what is left, sourcing from the nearest painted texel on the surface rather than the nearest one in the atlas.
Before you trust any number you produce
Section titled “Before you trust any number you produce”Three habits, each of which this repo paid for:
- Judge textures under flat light and geometry under clay. A Workbench STUDIO render
is not a texture readout — grey chalky facet mosaics are specular highlights on
flat-shaded normals and vanish under
--flat. Two debugging rounds were lost to this. - Put the asset beside its reference, with its provenance, at full size. The cheapest
diagnostic here is a
reference | asset | provenance | errorsheet, and it did not exist for the first seven experiments. When it was finally built, the whole thesis was readable off one panel. - Ask what a wrong answer would look like, then check for that. A gate written
against the success mode will pass a broken artifact confidently: a silhouette-IoU
cull gate returned
1.00000on a mesh with a hole clean through the torso.
Reading the record
Section titled “Reading the record”Every non-trivial change here ran as a numbered experiment, with its predictions written before the measurement:
spec written BEFORE the work → report written AFTER → advisor ruling LASTThirty-one experiments are in docs/experiments. You do not have to read them linearly — the record is indexed:
python tools/facet_index.py build # regenerate the SQLite + FTS5 indexpython tools/facet_index.py verify # four legs, all must passpython tools/facet_index.py q "blade band"python tools/facet_index.py claims # staleness sweep over the current-state docsq answers in roughly forty lines what reading the record takes six hundred to answer.
verify is the gate: byte-identical determinism across interpreters, counts against
independent greps, zero dangling pointers, and a seeded question set that grows with the
record.