fix corpo manequim2

Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-14 23:21:38 -03:00
1 parent 85ec49c4f0
commit 149a12640b
2 files changed
+86 -42

No files matched your search

@@ -1,18 +1,37 @@
"""Reposiciona os bracos do 'GEO-body_male_realistic' (bundle Human Base """Reposiciona o 'GEO-body_male_realistic' (bundle Human Base Meshes da
Meshes da Blender Studio, CC0) da pose relaxada original para posicao Blender Studio, CC0) da pose relaxada original para posicao anatomica
anatomica (bracos retos ao lado do corpo), e exporta pra GLB. (bracos retos ao lado do corpo, palma virada bem mais pra frente), e
exporta pra GLB.
Rodar de dentro do Blender, sem interface: Rodar de dentro do Blender, sem interface:
blender --background human_base_meshes_bundle.blend \ blender --background human_base_meshes_bundle.blend \
--python repose_and_export.py --python repose_and_export.py
A malha nao tem armature, entao a repose e feita rotacionando os vertices A malha nao tem armature, entao tudo aqui e feito girando vertices
do braco em torno de um pivo no ombro. O angulo de rotacao e MEDIDO na diretamente. Ordem importa:
propria malha (distancia/posicao do vertice mais distante do ombro dentro
da faixa de altura do braco), nao e um valor chutado, entao continua 1. Aplica o modificador Multires (nivel 1, ~42k triangulos) ANTES de
funcionando mesmo que uma versao futura do bundle mude a pose de origem. editar qualquer vertice -- fazer isso depois faz o Blender reinterpretar
Uma janela suave (smoothstep) evita costura na axila. o deslocamento esculpido em cima de uma malha-base que mudou de posicao,
o que pode gerar bolhas/deformacoes que nao existem se a ordem for essa.
2. Supinacao (mao+punho, z entre ~0.70 e ~0.82 nesta escala): gira em
torno do proprio eixo vertical, com peso suave nas duas pontas (nao
pega nada de coxa por baixo, nao "rasga" o antebraco por cima). Parar
em exatamente 90 graus (mao IDEAL, palma 100% anterior) faz a secao
nao-circular do pulso criar uma aba/leque tosco na transicao -- sem
rig/pele pra suavizar isso direito, 45 graus e o ponto onde a palma ja
vira visivelmente pra frente sem esse artefato. Documentado aqui como
limitacao conhecida, nao erro.
3. Abducao do ombro: derruba o braco (que vinha afastado do corpo, pose
original tipo "A") ate quase vertical, com uma pequena abducao residual
(a malha nao tem rig, entao trazer pra EXATAMENTE vertical faz a mao
encostar/atravessar a coxa).
Os angulos e faixas de altura usados abaixo foram medidos na propria
malha (amostrando vertices por fatias de altura), nao chutados -- ver o
historico da sessao que criou este script se precisar re-derivar os
valores pra uma versao futura do bundle com proporcoes diferentes.
""" """
import math import math
@@ -21,9 +40,9 @@ import bpy
BODY_OBJECT = "GEO-body_male_realistic" BODY_OBJECT = "GEO-body_male_realistic"
EYE_OBJECTS = ["GEO-body_male_realistic.eye.L", "GEO-body_male_realistic.eye.R"] 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 MULTIRES_LEVEL = 1 # ~42k triangulos: mantem a definicao muscular visivel a distancia normal de camera
OUTPUT_PATH = "/tmp/mannequin-realistic.glb" OUTPUT_PATH = "/tmp/mannequin-realistic.glb"
SKIN_COLOR = (0.56, 0.64, 0.78, 1.0) # mesmo tom usado no Mannequin.tsx anterior SKIN_COLOR = (0.56, 0.64, 0.78, 1.0)
def smoothstep(edge0, edge1, x): def smoothstep(edge0, edge1, x):
@@ -31,33 +50,56 @@ def smoothstep(edge0, edge1, x):
return t * t * (3 - 2 * t) return t * t * (3 - 2 * t)
def repose_arms(obj): def apply_multires(obj):
mesh = obj.data bpy.context.view_layer.objects.active = obj
zs = [v.co.z for v in mesh.vertices] bpy.ops.object.select_all(action="DESELECT")
z_min, z_max = min(zs), max(zs) obj.select_set(True)
height = z_max - z_min for mod in obj.modifiers:
centerline_x = sum(v.co.x for v in mesh.vertices) / len(mesh.vertices) if mod.type == "MULTIRES":
mod.levels = MULTIRES_LEVEL
bpy.ops.object.modifier_apply(modifier="Multires")
def supinate_hand(mesh, centerline_x, torso_half_width):
# Mao termina de forma abrupta em z=~0.705 nesta malha (medido); a
# coxa nao passa de dx~0.19-0.20 ate z=~0.70, entao a rampa de subida
# (0.65->0.70) fica sempre acima do alcance da coxa. A rampa de
# descida (0.76->0.82) para antes do meio do antebraco.
for side in (1, -1):
core = [
v for v in mesh.vertices
if side * (v.co.x - centerline_x) > torso_half_width * 1.15
and 0.70 < v.co.z < 0.76
]
pivot_x = sum(v.co.x for v in core) / len(core)
pivot_y = sum(v.co.y for v in core) / len(core)
for v in mesh.vertices:
dx = v.co.x - centerline_x
if side * dx <= 0:
continue
weight_x = smoothstep(torso_half_width * 0.7, torso_half_width * 1.15, abs(dx))
weight_z = smoothstep(0.65, 0.70, v.co.z) * (1 - smoothstep(0.76, 0.82, v.co.z))
weight = weight_x * weight_z
if weight <= 0.001:
continue
rot = side * weight * math.radians(45.0)
cos_a, sin_a = math.cos(rot), math.sin(rot)
px, py = v.co.x - pivot_x, v.co.y - pivot_y
v.co.x = pivot_x + (px * cos_a - py * sin_a)
v.co.y = pivot_y + (px * sin_a + py * cos_a)
def drop_arms(mesh, centerline_x, torso_half_width, z_min, height):
shoulder_z = z_min + 0.84 * height shoulder_z = z_min + 0.84 * height
# A mao termina de forma bem abrupta por volta de z=0.705-0.71 nesta arm_hand_low_z = z_min + 0.417 * height # ~0.705
# escala (medido amostrando a malha por fatias de altura — ver residual = math.radians(8.0)
# diagnose2.py no historico) — abaixo disso so existe coxa. Uma janela
# mais larga aqui (ex: baseada só numa fracao generica da altura) pega
# vertice de coxa por engano e deforma a perna inteira, porque eles
# entram na rotacao rigida em torno do pivo do OMBRO.
arm_hand_low_z = z_min + 0.417 * height # ~0.705 pra este mesh especifico
torso_half_width = 0.20 # metade da largura biacromial aproximada, nesta escala
# Angulo residual: parar EXATAMENTE na vertical faz a mao encostar (ou
# atravessar) a coxa. Deixar uma pequena abducao residual dá uma folga
# natural, como nas referências de posição anatômica.
residual = math.radians(5.0)
for side in (1, -1): for side in (1, -1):
pivot_x = centerline_x + side * torso_half_width * 0.95 pivot_x = centerline_x + side * torso_half_width * 1.05
candidates = [ candidates = [
v v for v in mesh.vertices
for v in mesh.vertices
if side * (v.co.x - centerline_x) > torso_half_width if side * (v.co.x - centerline_x) > torso_half_width
and arm_hand_low_z < v.co.z < shoulder_z - 0.02 and arm_hand_low_z < v.co.z < shoulder_z - 0.02
] ]
@@ -73,10 +115,7 @@ def repose_arms(obj):
if side * dx <= 0 or v.co.z >= shoulder_z + 0.03: if side * dx <= 0 or v.co.z >= shoulder_z + 0.03:
continue continue
weight_x = smoothstep(torso_half_width * 0.7, torso_half_width * 1.15, abs(dx)) weight_x = smoothstep(torso_half_width * 0.7, torso_half_width * 1.15, abs(dx))
# janela estreita: cheia bem em cima da mao, zero já na coxa — weight_z = smoothstep(arm_hand_low_z - 0.002, arm_hand_low_z + 0.006, v.co.z)
# a transição real na malha é abrupta (não gradual), então uma
# rampa larga aqui é o que causava a contaminação da perna.
weight_z = smoothstep(arm_hand_low_z - 0.015, arm_hand_low_z + 0.005, v.co.z)
weight = weight_x * weight_z weight = weight_x * weight_z
if weight <= 0.001: if weight <= 0.001:
continue continue
@@ -86,16 +125,21 @@ def repose_arms(obj):
v.co.x = pivot_x + (px * cos_a - pz * sin_a) v.co.x = pivot_x + (px * cos_a - pz * sin_a)
v.co.z = shoulder_z + (px * sin_a + pz * cos_a) v.co.z = shoulder_z + (px * sin_a + pz * cos_a)
mesh.update()
def main(): def main():
body = bpy.data.objects[BODY_OBJECT] body = bpy.data.objects[BODY_OBJECT]
repose_arms(body) apply_multires(body)
for mod in body.modifiers: mesh = body.data
if mod.type == "MULTIRES": zs = [v.co.z for v in mesh.vertices]
mod.levels = MULTIRES_LEVEL 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)
torso_half_width = 0.20 # metade da largura biacromial aproximada, nesta escala
supinate_hand(mesh, centerline_x, torso_half_width)
drop_arms(mesh, centerline_x, torso_half_width, z_min, height)
mesh.update()
mat = bpy.data.materials.new("MannequinSkin") mat = bpy.data.materials.new("MannequinSkin")
mat.use_nodes = True mat.use_nodes = True
Binary file not shown.