Everything the Python side of Local Mesh needs. The Electron main process runs
these scripts in place from the app bundle, using the uv-managed venv at
~/.local-mesh/env; nothing is copied out. Nothing here is bundled into the
renderer, and nothing here imports Electron.
manifest.json what to install: torch pin + index, base requirements,
per-model requirements and git repos (read by main)
VERSION bumped when anything here changes; main writes it into
models/<id>/.deps-installed to detect a stale env
PROTOCOL.md the worker's JSON-lines contract (mirrored in src/core/generation.ts)
hf_download.py one-shot weight downloader with JSON progress
worker.py the long-lived generation process
backends/ one module per model, plus the shared Backend base and the registry
requirements/ base.txt (always) + one file per model
shims/ stand-ins for packages we refuse to build or pull in —
CUDA extensions (torchmcubes, diso, kaolin, open3d) and
optional cruft a repo imports at module scope (wandb,
streaming); one package directory each, appended to
sys.path by the worker so a real install always wins
Environment setup, once:
uv venv ~/.local-mesh/env --python 3.11
# torch: the app picks a variant from manifest.json `torch.variants` by compute
# capability (nvidia-smi). Pre-Volta cards get the cu126 pin below; newer GPUs
# (Volta through RTX 50xx) get current PyPI torch; no NVIDIA GPU -> the cpu index.
uv pip install --python ~/.local-mesh/env/bin/python torch==2.9.1 torchvision \
--index-url https://download.pytorch.org/whl/cu126 # pre-Volta only
uv pip install --python ~/.local-mesh/env/bin/python -r <app>/resources/python/requirements/base.txtThe cu126 pin is not cosmetic for old cards: it is the last PyTorch wheel channel
that still compiles sm_61 (GTX 10xx). It has no Blackwell kernels, which is why
newer cards install from PyPI instead.
Per model:
# weights — the app downloads these itself in the main process (src/main/hfDownload.ts,
# straight from Hugging Face, no python needed); hf_download.py is an equivalent CLI
~/.local-mesh/env/bin/python -u <app>/resources/python/hf_download.py \
--repo tencent/Hunyuan3D-2mini \
--dest ~/.local-mesh/models/hunyuan3d-2mini \
--allow "hunyuan3d-dit-v2-mini-turbo/*" --allow "*.json"
# code the backend imports (manifest.json -> models.<id>.repos)
git clone --depth 1 https://github.com/Tencent-Hunyuan/Hunyuan3D-2.git ~/.local-mesh/repos/Hunyuan3D-2
# dependencies
uv pip install --python ~/.local-mesh/env/bin/python -r <app>/resources/python/requirements/hunyuan3d.txthf_download.py writes one JSON object per line to stdout — start,
progress (at most four per second), then done or error — and leaves a
.complete marker in the destination so main can tell a finished snapshot from
an interrupted one. SIGTERM exits 130 and keeps the partial files; the next run
resumes. Exit code 1 means the error event is the reason.
Generation:
~/.local-mesh/env/bin/python -u <app>/resources/python/worker.pyMain keeps that process alive across jobs, writes commands to its stdin and
reads events from its stdout. Everything on stderr is log noise and goes to the
generation log channel at debug level. See PROTOCOL.md for the message shapes.
echo '{"cmd":"ping"}' | python -u worker.pyA whole job, against the mock backend (no torch, no weights, no GPU):
printf '%s\n' \
'{"cmd":"load","model_id":"mock","model_dir":"/tmp","device":"cpu","precision":"fp32","low_vram":false}' \
'{"cmd":"generate","job":{"jobId":"j1","modelId":"mock","imagePath":"in.png","removeBackground":false,
"settings":{"shape":"torus-knot","seconds":1,"seed":7},
"postProcess":[{"op":"remove-floaters","threshold":0.1},{"op":"remove-degenerate","mergeVertices":true}],
"export":{"format":"glb","outputDir":"/tmp/out","baseName":"demo"}}}' \
'{"cmd":"shutdown"}' | python -u worker.py(Keep each command on a single line — the reader is line-delimited.)
LOCAL_MESH_HOME overrides ~/.local-mesh if you want to point the repo and
shim search at a scratch tree.
- Add the entry to
MODELSinsrc/core/models.tswith abackendname. - Mirror the id → backend mapping in
backends/registry.py. - Write
backends/<backend>.pywith aBackendclass (seebackends/base.py;backends/mock.pyis the smallest complete example). - Add
requirements/<name>.txtand amodels.<id>entry inmanifest.json. - Bump
VERSIONso existing installs reinstall their dependencies.
Backends receive a PIL RGBA image with the background already removed, report
progress through the callbacks they were constructed with, and return a
trimesh.Trimesh. Cleanup, decimation, orientation and export belong to
worker.py, so every model produces comparable output.