"""Reposiciona o '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, palma virada bem mais pra frente), 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 tudo aqui e feito girando vertices diretamente. Ordem importa: 1. Aplica o modificador Multires (nivel 1, ~42k triangulos) ANTES de editar qualquer vertice -- fazer isso depois faz o Blender reinterpretar 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 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 OUTPUT_PATH = "/tmp/mannequin-realistic.glb" SKIN_COLOR = (0.56, 0.64, 0.78, 1.0) def smoothstep(edge0, edge1, x): t = max(0.0, min(1.0, (x - edge0) / (edge1 - edge0))) return t * t * (3 - 2 * t) def apply_multires(obj): bpy.context.view_layer.objects.active = obj bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) for mod in obj.modifiers: 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 arm_hand_low_z = z_min + 0.417 * height # ~0.705 residual = math.radians(8.0) for side in (1, -1): pivot_x = centerline_x + side * torso_half_width * 1.05 candidates = [ v for v in mesh.vertices if side * (v.co.x - centerline_x) > torso_half_width and arm_hand_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, ) measured_angle = math.atan2(far.co.x - pivot_x, -(far.co.z - shoulder_z)) angle = measured_angle - side * residual 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_hand_low_z - 0.002, arm_hand_low_z + 0.006, 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) def main(): body = bpy.data.objects[BODY_OBJECT] apply_multires(body) mesh = body.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) 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.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()