Files
settled-reach/tooling/render_quaternius_test.py
T
jpmschweitzerandClaude Opus 4.6 093b24e38e feat(assets): face-based segmentation pipeline + solidify for hair/clothing
Segmentation:
- Face-based exclusive assignment (no overlap, no caps needed)
- Hips split from torso (pelvis as own segment)
- Torso_upper independent (spine_03 + clavicle, not a variant)
- Clavicle moved from arm_upper to torso_upper

Hair pipeline:
- Solidify modifier (20mm) for scalp hair volume
- Normal recalculation before solidify for consistent direction
- Eyebrows/facial hair NOT solidified
- Fixed mask generation (was outputting black, now white)

Clothing pipeline:
- Solidify modifier (25mm) for clothing thickness
- Peasant shoes added from Fantasy pack

Tooling:
- convert_outfit.py with solidify support
- inspect_glb.py, check_hair_symmetry.py for debugging
- Segment reference distribution locked
- Q-063 footsteps VFX filed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 17:43:04 +01:00

76 lines
2.6 KiB
Python

"""Render the raw Quaternius body + hair from multiple angles to check for clipping."""
import sys
import os
import math
import bpy
if __name__ == "__main__":
argv = sys.argv
if "--" not in argv:
sys.exit(1)
out_dir = argv[argv.index("--") + 1]
os.makedirs(out_dir, exist_ok=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
# Load body
body_path = "/var/mnt/data/projects/settled-reach/main/docs/assets/downloads/Universal Base Characters[Source]/Base Characters/Exports/Godot - UE/Superhero_Female_FullBody.gltf"
bpy.ops.import_scene.gltf(filepath=body_path)
# Load bob hair
hair_path = "/var/mnt/data/projects/settled-reach/main/docs/assets/downloads/Universal Base Characters[Source]/Hairstyles/Rigged to Head Bone/glTF (Godot -Unreal)/Female/Hair_Bob.gltf"
bpy.ops.import_scene.gltf(filepath=hair_path)
# Remove icospheres
for obj in list(bpy.context.scene.objects):
if "Icosphere" in obj.name:
bpy.data.objects.remove(obj, do_unlink=True)
# Setup camera
cam_data = bpy.data.cameras.new("TestCam")
cam_data.lens = 85
cam = bpy.data.objects.new("TestCam", cam_data)
bpy.context.scene.collection.objects.link(cam)
bpy.context.scene.camera = cam
# Light
light_data = bpy.data.lights.new("TestLight", type='SUN')
light_data.energy = 3.0
light = bpy.data.objects.new("TestLight", light_data)
light.rotation_euler = (math.radians(45), 0, math.radians(30))
bpy.context.scene.collection.objects.link(light)
# Render settings
bpy.context.scene.render.engine = 'BLENDER_EEVEE_NEXT'
bpy.context.scene.render.resolution_x = 800
bpy.context.scene.render.resolution_y = 600
bpy.context.scene.render.film_transparent = True
# Head center (approx)
target = (0.0, 0.0, 1.65)
dist = 0.5
angles = {
"front": 0,
"right": 90,
"back": 180,
"left": 270,
}
for name, deg in angles.items():
rad = math.radians(deg)
cx = target[0] + dist * math.sin(rad)
cy = target[1] - dist * math.cos(rad)
cz = target[2]
cam.location = (cx, cy, cz)
# Point at target
direction = (target[0] - cx, target[1] - cy, target[2] - cz)
rot_z = math.atan2(direction[0], -direction[1])
rot_x = math.atan2(math.sqrt(direction[0]**2 + direction[1]**2), direction[2])
cam.rotation_euler = (rot_x, 0, rot_z)
filepath = os.path.join(out_dir, f"raw_bob_{name}.png")
bpy.context.scene.render.filepath = filepath
bpy.ops.render.render(write_still=True)
print(f"Rendered: {filepath}")