- autorig: landmark detection from vertex cloud (markers JSON override), mixamorig 22-bone skeleton fit, bone-heat weights + nearest-bone rescue - retarget: world-space quaternion delta transfer, hip-height scale compensation, fuzzy/explicit bone mapping (CMU map bundled) - smoke: procedural test humanoid + synthetic BVH -> rig -> retarget -> verify - deploy: push to gitea, pull + smoke on m3ultra and m1ultra Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""Generate a low-poly T-pose test humanoid (no rig) for the smoke test.
|
|
|
|
Usage: blender -b --python bpy/smoke_mesh.py -- --out out/test_human.glb
|
|
"""
|
|
import argparse
|
|
import math
|
|
import os
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from common import clean_scene, export_glb, select_only, stage_args
|
|
|
|
|
|
def cube(name, center, size):
|
|
bpy.ops.mesh.primitive_cube_add(location=center)
|
|
o = bpy.context.active_object
|
|
o.name = name
|
|
o.scale = (size[0] / 2, size[1] / 2, size[2] / 2)
|
|
return o
|
|
|
|
|
|
def sphere(name, center, r):
|
|
bpy.ops.mesh.primitive_uv_sphere_add(
|
|
location=center, radius=r, segments=12, ring_count=8)
|
|
o = bpy.context.active_object
|
|
o.name = name
|
|
return o
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--out", required=True)
|
|
args = ap.parse_args(stage_args())
|
|
|
|
clean_scene()
|
|
H = 1.75 # meters, floor at z=0, facing -Y, T-pose along X
|
|
parts = []
|
|
|
|
# torso: groin (0.52H) to shoulders (~0.82H)
|
|
parts.append(cube("torso", (0, 0, 0.67 * H), (0.32 * H, 0.14 * H, 0.30 * H)))
|
|
# head: chin 0.87H to top 1.0H
|
|
parts.append(sphere("head", (0, 0, 0.93 * H), 0.075 * H))
|
|
parts.append(cube("neck", (0, 0, 0.845 * H), (0.06 * H, 0.06 * H, 0.06 * H)))
|
|
# arms straight out along +/-X at shoulder height 0.82H
|
|
for s in (1, -1):
|
|
parts.append(cube(f"upperarm{s}",
|
|
(s * 0.26 * H, 0, 0.82 * H),
|
|
(0.17 * H, 0.055 * H, 0.055 * H)))
|
|
parts.append(cube(f"forearm{s}",
|
|
(s * 0.40 * H, 0, 0.82 * H),
|
|
(0.14 * H, 0.045 * H, 0.045 * H)))
|
|
parts.append(cube(f"hand{s}",
|
|
(s * 0.50 * H, 0, 0.82 * H),
|
|
(0.09 * H, 0.04 * H, 0.03 * H)))
|
|
# legs down from groin, feet forward (-Y)
|
|
for s in (1, -1):
|
|
parts.append(cube(f"thigh{s}",
|
|
(s * 0.09 * H, 0, 0.40 * H),
|
|
(0.075 * H, 0.075 * H, 0.24 * H)))
|
|
parts.append(cube(f"shin{s}",
|
|
(s * 0.09 * H, 0, 0.165 * H),
|
|
(0.06 * H, 0.06 * H, 0.23 * H)))
|
|
parts.append(cube(f"foot{s}",
|
|
(s * 0.09 * H, -0.05 * H, 0.03 * H),
|
|
(0.06 * H, 0.16 * H, 0.05 * H)))
|
|
|
|
select_only(parts, active=parts[0])
|
|
bpy.ops.object.join()
|
|
body = bpy.context.active_object
|
|
body.name = "test_human"
|
|
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
|
|
|
export_glb(args.out, objects=[body])
|
|
print("[mirpamo] smoke mesh OK")
|
|
|
|
|
|
main()
|