feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC. Content: - characters/ Lena/male body lanes, bakes, texture work, run logs - clothing/ garment pipeline, configs, gates, contract docs - garments/ MD-authored garment sources (.zprj/.zpac) - UAL-Lib/ Universal Animation Library 2 source (.blend/.fbx/.glb) - tools/ blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent - docs/, plans/, dev/, .agents/plans/ Repo hygiene: - .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out on purpose — ~250 are already tracked raw and converting them would rewrite every one without shrinking history. - .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies dropped by a `>/dev/null` redirect on Windows). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Stage 4: review renders + numeric profile gates for the hires sculpt.
|
||||
# blender --background --python 04_review.py -- <03_sculpted.blend> <review_dir>
|
||||
import bpy, sys, os, math, time
|
||||
import numpy as np
|
||||
from mathutils import Vector
|
||||
|
||||
argv = sys.argv[sys.argv.index("--") + 1:]
|
||||
BLEND, OUT = argv[0], argv[1]
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
t0 = time.time()
|
||||
|
||||
|
||||
def log(m):
|
||||
print(f"[review {time.time()-t0:6.1f}s] {m}", flush=True)
|
||||
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=BLEND)
|
||||
ob = max([o for o in bpy.data.objects if o.type == 'MESH'],
|
||||
key=lambda o: len(o.data.vertices))
|
||||
me = ob.data
|
||||
n_v = len(me.vertices)
|
||||
|
||||
co = np.empty(n_v * 3, dtype=np.float64)
|
||||
me.vertices.foreach_get("co", co)
|
||||
co = co.reshape(-1, 3)
|
||||
|
||||
# ---------- numeric gates ----------
|
||||
front = co[:, 1] < 0
|
||||
print("\n=== PROFILE y(z) at sternum x=0 (front) ===")
|
||||
for z0 in np.arange(0.58, 0.78, 0.01):
|
||||
m = front & (np.abs(co[:, 0]) < 0.004) & (np.abs(co[:, 2] - z0) < 0.005)
|
||||
if m.sum():
|
||||
print(f" z={z0:.2f} y={co[m,1].min():+.4f}")
|
||||
print("=== PROFILE y(z) at apex x=0.034 ===")
|
||||
for z0 in np.arange(0.58, 0.78, 0.01):
|
||||
m = front & (np.abs(co[:, 0] - 0.034) < 0.005) & (np.abs(co[:, 2] - z0) < 0.005)
|
||||
if m.sum():
|
||||
print(f" z={z0:.2f} y={co[m,1].min():+.4f}")
|
||||
print("=== PROFILE y(x) at z=0.688 ===")
|
||||
for x0 in np.arange(-0.10, 0.101, 0.01):
|
||||
m = front & (np.abs(co[:, 0] - x0) < 0.005) & (np.abs(co[:, 2] - 0.688) < 0.006)
|
||||
if m.sum():
|
||||
print(f" x={x0:+.2f} y={co[m,1].min():+.4f}")
|
||||
|
||||
# ---------- renders ----------
|
||||
scn = bpy.context.scene
|
||||
w = bpy.data.worlds.new("W")
|
||||
w.color = (0.22, 0.22, 0.24)
|
||||
scn.world = w
|
||||
key = bpy.data.objects.new("Key", bpy.data.lights.new("Key", 'SUN'))
|
||||
key.data.energy = 3.0
|
||||
key.data.use_shadow = False
|
||||
bpy.context.collection.objects.link(key)
|
||||
fill = bpy.data.objects.new("Fill", bpy.data.lights.new("Fill", 'SUN'))
|
||||
fill.data.energy = 1.0
|
||||
fill.data.use_shadow = False
|
||||
bpy.context.collection.objects.link(fill)
|
||||
cam = bpy.data.objects.new("Cam", bpy.data.cameras.new("Cam"))
|
||||
cam.data.lens = 85
|
||||
bpy.context.collection.objects.link(cam)
|
||||
scn.camera = cam
|
||||
scn.render.engine = 'BLENDER_EEVEE' if bpy.app.version >= (4, 2) else 'BLENDER_EEVEE_NEXT'
|
||||
scn.render.resolution_x = scn.render.resolution_y = 1000
|
||||
|
||||
clay_mat = bpy.data.materials.new("Clay")
|
||||
clay_mat.use_nodes = True
|
||||
clay_mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (0.62, 0.60, 0.58, 1)
|
||||
clay_mat.node_tree.nodes["Principled BSDF"].inputs["Roughness"].default_value = 0.45
|
||||
orig_mats = [ms.material for ms in ob.material_slots]
|
||||
|
||||
|
||||
def shoot(tag, ctr, span, yaw_deg, clay):
|
||||
if clay:
|
||||
for ms in ob.material_slots:
|
||||
ms.material = clay_mat
|
||||
else:
|
||||
for ms, m in zip(ob.material_slots, orig_mats):
|
||||
ms.material = m
|
||||
yaw = math.radians(yaw_deg)
|
||||
dist = span * 3.0
|
||||
cam.location = Vector(ctr) + Vector((math.sin(yaw) * dist, -math.cos(yaw) * dist, 0.02))
|
||||
cam.rotation_euler = (Vector(ctr) - cam.location).to_track_quat('-Z', 'Y').to_euler()
|
||||
key.rotation_euler = (math.radians(62), 0, math.radians(35 + yaw_deg))
|
||||
fill.rotation_euler = (math.radians(75), 0, math.radians(yaw_deg - 110))
|
||||
scn.render.filepath = os.path.join(OUT, f"{tag}.png")
|
||||
bpy.ops.render.render(write_still=True)
|
||||
log(f"render {tag}")
|
||||
|
||||
|
||||
CHEST = (0.0, 0.0, 0.675)
|
||||
HIP = (0.0, 0.0, 0.53)
|
||||
FULL = (0.0, 0.0, 0.50)
|
||||
for yaw in (0, 40, 90):
|
||||
shoot(f"chest_clay_{yaw}", CHEST, 0.22, yaw, True)
|
||||
shoot(f"chest_tex_{yaw}", CHEST, 0.22, yaw, False)
|
||||
shoot("hip_clay_0", HIP, 0.22, 0, True)
|
||||
shoot("full_clay_0", FULL, 0.55, 0, True)
|
||||
shoot("full_clay_40", FULL, 0.55, 40, True)
|
||||
print("REVIEW_DONE")
|
||||
Reference in New Issue
Block a user