hatchimera / tools /shared /sync_voxel_rig.py
arkai2025's picture
feat(tools): live-3D Part Catalog page + compendium rework
8b324db
Raw
History Blame Contribute Delete
2.93 kB
#!/usr/bin/env python3
"""Extract the canonical in-game voxel render rig into a standalone JS file.
The app's render rig lives in `src/buddy_fusion/voxel_embed.py` as the
`VOXEL_SETUP_JS` string (Three.js scene, studio lighting, real shadows, ACES
tone mapping, turntable, the `window.voxel*` builders, and `setupVoxel` which
boots any `.buddy-voxel-root` from its `.buddyVoxelConfig` JSON). The Tools
report pages (`tools/parts-catalog/`, `tools/compendium/`) render live 3D
creatures with that exact rig instead of static thumbnails.
Rather than copy-paste (and drift), this script imports `voxel_embed`, grabs
`VOXEL_SETUP_JS` verbatim, and writes `tools/shared/voxel-rig.js` with a
generated-file header plus a standalone boot (observer + initial scan + a cheap
retry poll). The app render path in voxel_embed.py is NOT touched.
Run: python tools/shared/sync_voxel_rig.py
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "src"))
from buddy_fusion import voxel_embed # noqa: E402
OUT = Path(__file__).resolve().parent / "voxel-rig.js"
HEADER = """\
// GENERATED FILE — do not edit by hand.
// Source of truth: src/buddy_fusion/voxel_embed.py (VOXEL_SETUP_JS).
// Regenerate with: python tools/shared/sync_voxel_rig.py
//
// This is the production in-game voxel render rig (Three.js scene, studio
// lighting, real shape-based shadows, ACES tone mapping, turntable, OrbitControls,
// and the window.voxel* builders). Booting it scans the document for
// .buddy-voxel-root nodes and renders each from its .buddyVoxelConfig JSON.
"""
# Standalone boot: the app appends its own observer/poll wired to canvas/tree
# helpers; the reports only need the voxel scan. setupVoxel is idempotent and
# bails (without marking inited) on a 0x0 stage, so the cheap retry poll picks up
# any card whose layout settled a frame late.
BOOT = """
// --- standalone boot (reports only; app uses its own VOXEL_JS_ON_LOAD) ---
// A page with many cards sets window.__VOXEL_NO_AUTOBOOT before loading this rig
// and drives setupVoxel itself through an IntersectionObserver context-cap
// manager (one live WebGL context per visible card would blow the browser's
// ~16-context limit). Pages with a handful of cards just let it auto-boot.
if (!window.__VOXEL_NO_AUTOBOOT) {
installVoxelObserver();
scanAndSetupVoxels();
setInterval(scanAndSetupVoxels, 400);
}
"""
def main() -> None:
js = voxel_embed.VOXEL_SETUP_JS
for name in ("setupVoxel", "voxelsFromBoxes", "voxelBuildMesh",
"installVoxelObserver", "scanAndSetupVoxels"):
if name not in js:
raise SystemExit(f"VOXEL_SETUP_JS is missing expected symbol: {name}")
OUT.write_text(HEADER + js + BOOT, encoding="utf-8")
print(f"wrote {OUT} ({len(OUT.read_text(encoding='utf-8'))} bytes)")
if __name__ == "__main__":
main()