Проблема с преобразованием данных CSV 3D-анимации в gLTFPython

Программы на Python
Ответить
Anonymous
 Проблема с преобразованием данных CSV 3D-анимации в gLTF

Сообщение Anonymous »

Я пытаюсь преобразовать этот файл CSV в файл glTF. Я устал от попыток... что бы я ни делал, результат выглядит плохо. Может ли кто-нибудь попытаться это сделать или подсказать, как получить действительный результат?

https://drive.google.com/file/d/1TTD50w ... drive_link
вот близкая попытка, но не совсем точная.
import bpy
import csv
import ast
from mathutils import Vector, Matrix

# === CONFIG ===
CSV_PATH = r"C:\Users\path\path\path\path\out_animation.csv"
OUTPUT_PATH =r"C:\Users\path\path\path\path\out_animation.glb"

FPS = 30
SCALE = 1.0
START_FRAME = 1
USE_AXIS_SWAP = False # set True if CSV is Y-up

# Smoothing
BASE_SMOOTH = 5
FINGER_SMOOTH = 9 # only fingers are smoothed more

# === SWAP _l _r ===
def swap_side(name: str) -> str:
if name.endswith("_l"):
return name[:-2] + "_r"
if name.endswith("_r"):
return name[:-2] + "_l"
return name

# === BASE PARENT MAP ===
PARENT_MAP_BASE = {
"pelvis": None,
"stomach": "pelvis",
"chest": "stomach",
"neck": "chest",
"head": "neck",
"hair": "head",

"clavicle_l": "chest",
"arm_l": "clavicle_l",
"forearm_l": "arm_l",
"forearm_twist_l": "forearm_l",
"hand_l": "forearm_l",
"weapon_l": "hand_l",

"clavicle_r": "chest",
"arm_r": "clavicle_r",
"forearm_r": "arm_r",
"forearm_twist_r": "forearm_r",
"hand_r": "forearm_r",
"weapon_r": "hand_r",

"thigh_l": "pelvis",
"calf_l": "thigh_l",
"foot_l": "calf_l",
"toe_l": "foot_l",

"thigh_r": "pelvis",
"calf_r": "thigh_r",
"foot_r": "calf_r",
"toe_r": "foot_r",

"back_l": "pelvis",
"back_r": "pelvis",

"scapular_l": "chest",
"scapular_r": "chest",
"chest_l": "chest",
"chest_r": "chest",

"zero_joint_hand_l": "hand_l",
"zero_joint_hand_r": "hand_r",
"zero_joint_pelvis_l": "pelvis",
"zero_joint_pelvis_r": "pelvis",

"biceps_twist_l": "arm_l",
"biceps_twist_r": "arm_r",
"thigh_twist_l": "thigh_l",
"thigh_twist_r": "thigh_r",

"f_big1_l": "hand_l",
"f_big2_l": "f_big1_l",
"f_big3_l": "f_big2_l",

"f_main1_l": "hand_l",
"f_main2_l": "f_main1_l",
"f_main3_l": "f_main2_l",

"f_pointer1_l": "hand_l",
"f_pointer2_l": "f_pointer1_l",
"f_pointer3_l": "f_pointer2_l",

"f_big1_r": "hand_r",
"f_big2_r": "f_big1_r",
"f_big3_r": "f_big2_r",

"f_main1_r": "hand_r",
"f_main2_r": "f_main1_r",
"f_main3_r": "f_main2_r",

"f_pointer1_r": "hand_r",
"f_pointer2_r": "f_pointer1_r",
"f_pointer3_r": "f_pointer2_r",
}

# Swap map
PARENT_MAP = {
swap_side(k): (swap_side(v) if v else None)
for k, v in PARENT_MAP_BASE.items()
}

# Hand tail should use fingers first (not weapon)
HAND_CHILD_PRIORITY_BASE = {
"hand_l": ["f_big1_l", "f_main1_l", "f_pointer1_l", "zero_joint_hand_l"],
"hand_r": ["f_big1_r", "f_main1_r", "f_pointer1_r", "zero_joint_hand_r"],
}

HAND_CHILD_PRIORITY = {
swap_side(k): [swap_side(x) for x in v]
for k, v in HAND_CHILD_PRIORITY_BASE.items()
}

# === LOAD CSV ===
with open(CSV_PATH, "r", newline="") as f:
reader = csv.reader(f)
bone_names_raw = [b.strip() for b in next(reader)]
bone_names = [swap_side(b) for b in bone_names_raw]

raw_frames = []
for row in reader:
frame = {}
for name_raw, cell in zip(bone_names_raw, row):
try:
x, y, z = ast.literal_eval(cell.strip())
vec = Vector((x, z, -y)) if USE_AXIS_SWAP else Vector((x, y, z))
frame[swap_side(name_raw)] = vec * SCALE
except:
pass
raw_frames.append(frame)

if not raw_frames:
raise RuntimeError("No frames found in CSV.")

finger_bones = [b for b in bone_names if b.startswith("f_")]

def get_window(bone):
return FINGER_SMOOTH if bone in finger_bones else BASE_SMOOTH

# === SMOOTH POSITIONS ===
frames = []
for i in range(len(raw_frames)):
smoothed = {}
for bone in bone_names:
window = get_window(bone)
half = window // 2
acc = Vector((0, 0, 0))
count = 0
for j in range(i - half, i + half + 1):
if 0 0:
smoothed[bone] = acc / count
frames.append(smoothed)

# === REMOVE OLD ARMATURE ===
if "CSV_Armature" in bpy.data.objects:
old = bpy.data.objects["CSV_Armature"]
bpy.data.objects.remove(old, do_unlink=True)

# === CREATE ARMATURE ===
arm_data = bpy.data.armatures.new("CSV_Armature")
arm_obj = bpy.data.objects.new("CSV_Armature", arm_data)
bpy.context.collection.objects.link(arm_obj)
bpy.context.view_layer.objects.active = arm_obj
arm_obj.select_set(True)

# === BUILD REST POSE FROM FRAME 0 ===
frame0 = frames[0]
children = {b: [] for b in bone_names}
for child, parent in PARENT_MAP.items():
if parent:
children.setdefault(parent, []).append(child)

bpy.ops.object.mode_set(mode="EDIT")

for name in bone_names:
if name not in frame0:
continue

eb = arm_data.edit_bones.new(name)
head = frame0[name]
eb.head = head

# Choose child for tail direction
child_list = HAND_CHILD_PRIORITY.get(name, children.get(name, []))
direction = None
for child_name in child_list:
if child_name in frame0:
direction = frame0[child_name] - head
break

if direction is None or direction.length == 0:
direction = Vector((0, 0.1, 0))

eb.tail = head + direction

for name, parent in PARENT_MAP.items():
child_bone = arm_data.edit_bones.get(name)
parent_bone = arm_data.edit_bones.get(parent) if parent else None
if child_bone and parent_bone:
child_bone.parent = parent_bone

bpy.ops.object.mode_set(mode="POSE")

# === CREATE ACTION ===
arm_obj.animation_data_create()
action = bpy.data.actions.new(name="CSV_Action")
arm_obj.animation_data.action = action

# === REST DATA ===
rest_heads = {b.name: b.head_local.copy() for b in arm_data.bones}
rest_tails = {b.name: b.tail_local.copy() for b in arm_data.bones}
rest_dirs = {}
rest_rot = {}

for b in arm_data.bones:
d = rest_tails[b.name] - rest_heads[b.name]
if d.length > 0:
d.normalize()
rest_dirs[b.name] = d
rest_rot[b.name] = b.matrix_local.to_3x3()

# === ANIMATION WITH ROTATION ===
scene = bpy.context.scene
scene.render.fps = FPS
scene.frame_start = START_FRAME
scene.frame_end = START_FRAME + len(frames) - 1

for f_idx, frame in enumerate(frames):
frame_num = START_FRAME + f_idx
scene.frame_set(frame_num)
bpy.context.view_layer.update()

for bone_name, pose_bone in arm_obj.pose.bones.items():
if bone_name not in frame:
continue

head = frame[bone_name]

# direction from first child
direction = None
for child in children.get(bone_name, []):
if child in frame:
direction = frame[child] - head
if direction.length > 0:
direction.normalize()
break

# LOCK rotation for finger bones
if bone_name in finger_bones:
rot_mat = rest_rot[bone_name]
else:
if direction:
rot_delta = rest_dirs[bone_name].rotation_difference(direction)
rot_mat = rot_delta.to_matrix() @ rest_rot[bone_name]
else:
rot_mat = rest_rot[bone_name]

pose_bone.matrix = Matrix.Translation(head) @ rot_mat.to_4x4()

pose_bone.keyframe_insert(data_path="location", frame=frame_num, group=bone_name)
pose_bone.keyframe_insert(data_path="rotation_quaternion", frame=frame_num, group=bone_name)

# === EXPORT GLB ===
bpy.ops.object.mode_set(mode="OBJECT")
for obj in bpy.context.selected_objects:
obj.select_set(False)

arm_obj.select_set(True)
bpy.context.view_layer.objects.active = arm_obj

op_props = bpy.ops.export_scene.gltf.get_rna_type().properties
kwargs = {
"filepath": OUTPUT_PATH,
"export_format": "GLB",
"use_selection": True,
"export_animations": True,
"export_force_sampling": True,
"export_skins": True,
"export_yup": True,
}
kwargs = {k: v for k, v in kwargs.items() if k in op_props}

bpy.ops.export_scene.gltf(**kwargs)

print(f"Exported: {OUTPUT_PATH}")


Подробнее здесь: https://stackoverflow.com/questions/798 ... ting-issue
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»