novo corpo
This commit is contained in:
1 parent
270cdbb965
commit
12342f442d
5 files changed
+230
-125
No files matched your search
@@ -0,0 +1,113 @@
|
||||
"""Reposiciona os bracos do 'GEO-body_male_realistic' (bundle Human Base
|
||||
Meshes da Blender Studio, CC0) da pose relaxada original para posicao
|
||||
anatomica (bracos retos ao lado do corpo), e exporta pra GLB.
|
||||
|
||||
Rodar de dentro do Blender, sem interface:
|
||||
|
||||
blender --background human_base_meshes_bundle.blend \
|
||||
--python repose_and_export.py
|
||||
|
||||
A malha nao tem armature, entao a repose e feita rotacionando os vertices
|
||||
do braco em torno de um pivo no ombro. O angulo de rotacao e MEDIDO na
|
||||
propria malha (distancia/posicao do vertice mais distante do ombro dentro
|
||||
da faixa de altura do braco), nao e um valor chutado, entao continua
|
||||
funcionando mesmo que uma versao futura do bundle mude a pose de origem.
|
||||
Uma janela suave (smoothstep) evita costura na axila.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import bpy
|
||||
|
||||
BODY_OBJECT = "GEO-body_male_realistic"
|
||||
EYE_OBJECTS = ["GEO-body_male_realistic.eye.L", "GEO-body_male_realistic.eye.R"]
|
||||
MULTIRES_LEVEL = 1 # ~42k triangulos: mantem a definicao muscular visivel a distancia normal de camera, bem mais leve que o nivel 3 (~677k) do arquivo original
|
||||
OUTPUT_PATH = "/tmp/mannequin-realistic.glb"
|
||||
SKIN_COLOR = (0.56, 0.64, 0.78, 1.0) # mesmo tom usado no Mannequin.tsx anterior
|
||||
|
||||
|
||||
def smoothstep(edge0, edge1, x):
|
||||
t = max(0.0, min(1.0, (x - edge0) / (edge1 - edge0)))
|
||||
return t * t * (3 - 2 * t)
|
||||
|
||||
|
||||
def repose_arms(obj):
|
||||
mesh = obj.data
|
||||
zs = [v.co.z for v in mesh.vertices]
|
||||
z_min, z_max = min(zs), max(zs)
|
||||
height = z_max - z_min
|
||||
centerline_x = sum(v.co.x for v in mesh.vertices) / len(mesh.vertices)
|
||||
|
||||
shoulder_z = z_min + 0.84 * height
|
||||
# comfortably above o alargamento do pe/tornozelo, abaixo de onde a mao
|
||||
# fica nesta pose relaxada — deixa uma folga clara entre perna e braço
|
||||
arm_low_z = z_min + 0.30 * height
|
||||
torso_half_width = 0.20 # metade da largura biacromial aproximada, nesta escala
|
||||
|
||||
for side in (1, -1):
|
||||
pivot_x = centerline_x + side * torso_half_width * 0.85
|
||||
|
||||
candidates = [
|
||||
v
|
||||
for v in mesh.vertices
|
||||
if side * (v.co.x - centerline_x) > torso_half_width
|
||||
and arm_low_z < v.co.z < shoulder_z - 0.02
|
||||
]
|
||||
far = max(
|
||||
candidates,
|
||||
key=lambda v: (v.co.x - pivot_x) ** 2 + (v.co.z - shoulder_z) ** 2,
|
||||
)
|
||||
angle = math.atan2(far.co.x - pivot_x, -(far.co.z - shoulder_z))
|
||||
|
||||
for v in mesh.vertices:
|
||||
dx = v.co.x - centerline_x
|
||||
if side * dx <= 0 or v.co.z >= shoulder_z + 0.03:
|
||||
continue
|
||||
weight_x = smoothstep(torso_half_width * 0.7, torso_half_width * 1.15, abs(dx))
|
||||
weight_z = smoothstep(arm_low_z - 0.08, arm_low_z + 0.07, v.co.z)
|
||||
weight = weight_x * weight_z
|
||||
if weight <= 0.001:
|
||||
continue
|
||||
rot = -weight * angle
|
||||
cos_a, sin_a = math.cos(rot), math.sin(rot)
|
||||
px, pz = v.co.x - pivot_x, v.co.z - shoulder_z
|
||||
v.co.x = pivot_x + (px * cos_a - pz * sin_a)
|
||||
v.co.z = shoulder_z + (px * sin_a + pz * cos_a)
|
||||
|
||||
mesh.update()
|
||||
|
||||
|
||||
def main():
|
||||
body = bpy.data.objects[BODY_OBJECT]
|
||||
repose_arms(body)
|
||||
|
||||
for mod in body.modifiers:
|
||||
if mod.type == "MULTIRES":
|
||||
mod.levels = MULTIRES_LEVEL
|
||||
|
||||
mat = bpy.data.materials.new("MannequinSkin")
|
||||
mat.use_nodes = True
|
||||
mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = SKIN_COLOR
|
||||
mat.node_tree.nodes["Principled BSDF"].inputs["Roughness"].default_value = 0.6
|
||||
|
||||
targets = [body] + [bpy.data.objects[n] for n in EYE_OBJECTS if n in bpy.data.objects]
|
||||
for obj in targets:
|
||||
obj.data.materials.clear()
|
||||
obj.data.materials.append(mat)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for obj in targets:
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = body
|
||||
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=OUTPUT_PATH,
|
||||
export_format="GLB",
|
||||
use_selection=True,
|
||||
export_apply=True,
|
||||
export_yup=True,
|
||||
)
|
||||
print(f"Exportado para {OUTPUT_PATH}")
|
||||
|
||||
|
||||
main()
|
||||
Reference in new issue
Block a user