diff --git a/scripts/generate_asset.py b/scripts/generate_asset.py index f91987f..f4422d4 100755 --- a/scripts/generate_asset.py +++ b/scripts/generate_asset.py @@ -37,7 +37,23 @@ from trellis2.model_revisions import ( # noqa: E402 TRELLIS_REVISION, ) -SAFETY_FACE_TARGET = 200_000 +def _default_safety_faces() -> int: + """RAM-sized pre-bake face cap (MODELBEAST BENCHMARKS 2026-07-22): the + flat 200k laptop mtlbvh guard crushed 2.2M-tri decodes to ~95k-face GLBs + (fal ships ~495k from the same weights). 500k is verified crash-free on + the M3 Ultra Studio; small-memory boxes keep the safe cap. Env override: + TRELLIS2_MAX_BAKE_FACES (0 = uncapped).""" + env = os.environ.get("TRELLIS2_MAX_BAKE_FACES") + if env is not None: + return int(env) or 10_000_000 + try: + mem_gb = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES") / 1e9 + except (ValueError, OSError): + mem_gb = 0 + return 500_000 if mem_gb >= 96 else 200_000 + + +SAFETY_FACE_TARGET = _default_safety_faces() WATCHDOG_SIGNATURES = ( "non-zero size", "BVH needs at least 8 triangles", @@ -165,6 +181,43 @@ def _export_raw(mesh, path: Path) -> dict[str, Any]: } +def _force_opaque(glb_path: Path) -> None: + """Rewrite every material's alphaMode to OPAQUE in a GLB, in place. + + The o_voxel Metal baker samples the decoder's alpha attr noisily (same + family as the dark-patch base-color bug): solid hair bakes with scattered + near-zero alpha texels, to_glb's min<250 auto-detect flips the material + to BLEND, and the model renders as a transparent speckle veil. + TRELLIS2_ALPHA_MODE=auto keeps the baked behaviour for genuinely + transparent subjects (glass etc.); the default assumes solid objects. + """ + import struct + + data = glb_path.read_bytes() + magic, version, _length = struct.unpack_from(" tuple[bool, str]: try: from trellis2.backends import probe_metal_backends @@ -273,6 +326,8 @@ def _export_pbr(mesh, path: Path, *, baker: str, target: Optional[int], texture_ verbose=True, ) result.export(path) + if os.environ.get("TRELLIS2_ALPHA_MODE", "opaque") != "auto": + _force_opaque(Path(path)) return result, pre_simplified @@ -437,7 +492,7 @@ def _parse_args(): parser.add_argument("--pipeline-type", choices=("512", "1024", "1024_cascade"), default="512") parser.add_argument("--seed", type=int, default=42) parser.add_argument("--steps", type=int) - parser.add_argument("--texture-size", type=int, choices=(512, 1024, 2048), default=1024) + parser.add_argument("--texture-size", type=int, choices=(512, 1024, 2048, 4096), default=1024) parser.add_argument("--background", choices=("auto", "keep"), default="auto") parser.add_argument("--pbr-decimation-target", type=_parse_target, default=None, metavar="none|N") parser.add_argument("--cache-dir", type=Path)