fix corpo manequim2
Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo@martinsevieira.adv.br>
This commit is contained in:
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.
Reference in new issue
Block a user