Working on stuff
This commit is contained in:
parent
f708843747
commit
a47306533e
65
blender scripts/blender_join_anims.py
Normal file
65
blender scripts/blender_join_anims.py
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
import bpy
|
||||
|
||||
def append_layered_action_5_0(source_obj_name, target_obj_name):
|
||||
source_obj = bpy.data.objects.get(source_obj_name)
|
||||
target_obj = bpy.data.objects.get(target_obj_name)
|
||||
|
||||
if not (source_obj and target_obj):
|
||||
print("Ошибка: Объекты не найдены")
|
||||
return
|
||||
|
||||
src_action = source_obj.animation_data.action
|
||||
tgt_action = target_obj.animation_data.action
|
||||
|
||||
# 1. Получаем слои (обычно первый)
|
||||
src_layer = src_action.layers[0]
|
||||
tgt_layer = tgt_action.layers[0]
|
||||
|
||||
# 2. Получаем стрипы
|
||||
src_strip = src_layer.strips[0]
|
||||
tgt_strip = tgt_layer.strips[0]
|
||||
|
||||
# Смещение (опираемся на конец диапазона целевого экшена)
|
||||
offset = tgt_action.frame_range[1]
|
||||
|
||||
# 3. Итерируемся по channelbags в исходном стрипе
|
||||
for src_bag in src_strip.channelbags:
|
||||
# Ищем или создаем соответствующий bag в целевом стрипе
|
||||
# Обычно они сопоставляются по имени или типу (например, 'Keyframe Channel Bag')
|
||||
# В простейшем случае берем первый или сопоставляем по индексу
|
||||
dst_bag = None
|
||||
if len(tgt_strip.channelbags) > 0:
|
||||
# Пытаемся найти по названию (если оно есть) или берем тот же индекс
|
||||
dst_bag = tgt_strip.channelbags[0]
|
||||
|
||||
if not dst_bag:
|
||||
# Если в целевом стрипе нет сумок, это странно, но можно создать
|
||||
# (Метод создания может зависеть от конкретного подтипа стрипа в 5.0)
|
||||
continue
|
||||
|
||||
#print(f"Обработка channelbag: {src_bag.name}, кривых: {len(src_bag.fcurves)}")
|
||||
|
||||
# 4. Итерируемся по fcurves внутри сумки
|
||||
for src_fcurve in src_bag.fcurves:
|
||||
dst_fcurve = dst_bag.fcurves.find(src_fcurve.data_path, index=src_fcurve.array_index)
|
||||
|
||||
if not dst_fcurve:
|
||||
dst_fcurve = dst_bag.fcurves.new(data_path=src_fcurve.data_path, index=src_fcurve.array_index)
|
||||
|
||||
# 5. Копируем ключи с офсетом
|
||||
for keyframe in src_fcurve.keyframe_points:
|
||||
new_frame = keyframe.co[0] + offset
|
||||
new_value = keyframe.co[1]
|
||||
|
||||
new_kp = dst_fcurve.keyframe_points.insert(new_frame, new_value, options={'FAST'})
|
||||
new_kp.interpolation = keyframe.interpolation
|
||||
|
||||
# Обновляем интерполяцию
|
||||
for fc in dst_bag.fcurves:
|
||||
fc.update()
|
||||
|
||||
print(f"Анимация успешно дозаписана. Новый конец: {tgt_action.frame_range[1]}")
|
||||
|
||||
append_layered_action_5_0('Armature.001', 'Armature')
|
||||
|
||||
228
blender scripts/export_anim_001.py
Normal file
228
blender scripts/export_anim_001.py
Normal file
@ -0,0 +1,228 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
#!
|
||||
|
||||
# Имена mesh и арматуры
|
||||
mesh_name = "Joined"
|
||||
armature_name = "Armature"
|
||||
|
||||
# Находим объект mesh по имени
|
||||
mesh_obj = bpy.data.objects.get(mesh_name)
|
||||
|
||||
# Находим объект арматуры по имени
|
||||
armature_obj = bpy.data.objects.get(armature_name)
|
||||
|
||||
# Устанавливаем текущий кадр на 0
|
||||
bpy.context.scene.frame_set(0)
|
||||
|
||||
# Принудительно обновляем сцену, чтобы применить анимацию
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
# Открываем файл для записи
|
||||
with open("C:\\Work\\Media\\witcher\\2026-04-13\\output\\gg_stand_idle001.txt", "w") as file:
|
||||
# Обработка арматуры и анимации
|
||||
if armature_obj and armature_obj.type == 'ARMATURE':
|
||||
file.write("=== Armature Matrix ===\n")
|
||||
for row in armature_obj.matrix_world:
|
||||
file.write(f"{row}\n")
|
||||
file.write(f"=== Armature Bones: {len(armature_obj.data.bones)}\n")
|
||||
for bone in armature_obj.data.bones:
|
||||
# Записываем имя кости, длину и связи
|
||||
file.write(f"Bone: {bone.name}\n")
|
||||
file.write(f" HEAD_LOCAL: {bone.head_local}\n")
|
||||
file.write(f" TAIL_LOCAL: {bone.tail_local}\n")
|
||||
file.write(f" Length: {(bone.tail_local - bone.head_local).length}\n")
|
||||
for row in bone.matrix:
|
||||
file.write(f" {row}\n")
|
||||
file.write(f" Parent: {bone.parent.name if bone.parent else 'None'}\n")
|
||||
file.write(f" Children: {[child.name for child in bone.children]}\n")
|
||||
|
||||
# Обработка mesh
|
||||
if mesh_obj and mesh_obj.type == 'MESH':
|
||||
# Создаем копию mesh, чтобы не изменять оригинал
|
||||
mesh_copy = mesh_obj.copy()
|
||||
mesh_copy.data = mesh_obj.data.copy()
|
||||
bpy.context.collection.objects.link(mesh_copy)
|
||||
|
||||
# Убедимся, что объект активен
|
||||
bpy.context.view_layer.objects.active = mesh_copy
|
||||
mesh_copy.select_set(True)
|
||||
|
||||
# Применяем модификатор Armature (если он есть)
|
||||
for modifier in mesh_copy.modifiers:
|
||||
if modifier.type == 'ARMATURE':
|
||||
# Включаем модификатор, если он отключен
|
||||
if not modifier.show_viewport:
|
||||
modifier.show_viewport = True
|
||||
if not modifier.show_render:
|
||||
modifier.show_render = True
|
||||
|
||||
# Проверяем, что модификатор связан с арматурой
|
||||
if modifier.object is None:
|
||||
print(f"Модификатор Armature на объекте {mesh_copy.name} не связан с арматурой. Пропускаем.")
|
||||
continue
|
||||
|
||||
# Временно применяем модификатор, чтобы получить правильные координаты вершин
|
||||
try:
|
||||
bpy.ops.object.modifier_apply(modifier=modifier.name)
|
||||
except RuntimeError as e:
|
||||
print(f"Ошибка при применении модификатора Armature: {e}")
|
||||
continue
|
||||
|
||||
# Переходим в режим редактирования
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Получаем BMesh представление mesh
|
||||
bm = bmesh.from_edit_mesh(mesh_copy.data)
|
||||
|
||||
# Записываем список вершин
|
||||
file.write(f"===Vertices: {len(bm.verts)}\n")
|
||||
for vertex in bm.verts:
|
||||
file.write(f"Vertex {vertex.index}: {vertex.co}\n")
|
||||
|
||||
# Убедимся, что у меша есть UV слой
|
||||
uv_layer = bm.loops.layers.uv.active
|
||||
if not uv_layer:
|
||||
file.write("UV слой не найден.\n")
|
||||
|
||||
if uv_layer:
|
||||
file.write(f"===UV Coordinates:\n")
|
||||
file.write(f"Face count: {len(bm.faces)}\n")
|
||||
|
||||
for face in bm.faces:
|
||||
file.write(f"Face {face.index}\n")
|
||||
file.write(f"UV Count: {len(face.loops)}\n")
|
||||
for loop in face.loops:
|
||||
uv_coords = loop[uv_layer].uv
|
||||
file.write(f" UV {uv_coords}\n")
|
||||
|
||||
# Записываем нормали
|
||||
file.write(f"===Normals:\n")
|
||||
for vertex in bm.verts:
|
||||
file.write(f"Vertex {vertex.index}: Normal {vertex.normal}\n")
|
||||
|
||||
# Записываем треугольники (индексы вершин)
|
||||
file.write(f"===Triangles: {len(bm.faces)}\n")
|
||||
for face in bm.faces:
|
||||
if len(face.verts) == 3: # Проверяем, что это треугольник
|
||||
verts_indices = [vert.index for vert in face.verts]
|
||||
file.write(f"Triangle: {verts_indices}\n")
|
||||
|
||||
# Возвращаемся в объектный режим
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# Записываем веса вершин
|
||||
file.write("=== Vertex Weights (Max 5 bones per vertex) ===\n")
|
||||
|
||||
MAX_BONES = 5
|
||||
|
||||
for vertex in mesh_copy.data.vertices:
|
||||
# Извлекаем все группы и веса для текущей вершины
|
||||
all_weights = []
|
||||
for group_element in vertex.groups:
|
||||
all_weights.append({
|
||||
'index': group_element.group,
|
||||
'weight': group_element.weight
|
||||
})
|
||||
|
||||
# Если костей больше лимита, фильтруем и перераспределяем
|
||||
if len(all_weights) > MAX_BONES:
|
||||
# Сортируем по весу (от большего к меньшему)
|
||||
all_weights.sort(key=lambda x: x['weight'], reverse=True)
|
||||
|
||||
# Берем только топ-5
|
||||
kept_weights = all_weights[:MAX_BONES]
|
||||
|
||||
# Считаем сумму весов оставшихся костей для нормализации
|
||||
total_weight = sum(gw['weight'] for gw in kept_weights)
|
||||
|
||||
if total_weight > 0:
|
||||
for gw in kept_weights:
|
||||
gw['weight'] /= total_weight
|
||||
else:
|
||||
# На случай, если у всех веса были по 0.0 (редкий баг меша)
|
||||
kept_weights[0]['weight'] = 1.0
|
||||
|
||||
final_weights = kept_weights
|
||||
else:
|
||||
final_weights = all_weights
|
||||
|
||||
file.write(f"Vertex {vertex.index}:\n")
|
||||
file.write(f"Vertex groups: {len(final_weights)}\n")
|
||||
|
||||
for gw in final_weights:
|
||||
group_name = mesh_copy.vertex_groups[gw['index']].name
|
||||
file.write(f" Group: '{group_name}', Weight: {gw['weight']:.6f}\n")
|
||||
|
||||
# Удаляем временную копию mesh
|
||||
bpy.data.objects.remove(mesh_copy)
|
||||
else:
|
||||
file.write(f"Объект с именем '{mesh_name}' не найден или не является mesh.\n")
|
||||
|
||||
# Обработка арматуры и анимации
|
||||
if armature_obj and armature_obj.type == 'ARMATURE':
|
||||
|
||||
# Получаем все ключевые кадры для арматуры
|
||||
file.write("=== Animation Keyframes ===\n")
|
||||
if armature_obj.animation_data and armature_obj.animation_data.action:
|
||||
action = armature_obj.animation_data.action
|
||||
|
||||
# Собираем все уникальные ключевые кадры
|
||||
keyframes = set()
|
||||
|
||||
# Логика для Blender 5.0 (Strip-based / ChannelBag structure)
|
||||
if hasattr(action, "layers"):
|
||||
for layer in action.layers:
|
||||
if hasattr(layer, "strips"):
|
||||
for strip in layer.strips:
|
||||
# Проверяем наличие channelbags (согласно вашему dir(strip))
|
||||
if hasattr(strip, "channelbags"):
|
||||
for bag in strip.channelbags:
|
||||
for fcurve in bag.fcurves:
|
||||
for keyframe in fcurve.keyframe_points:
|
||||
keyframes.add(int(keyframe.co[0]))
|
||||
|
||||
# На случай, если в этой версии используется единственное число
|
||||
elif hasattr(strip, "channelbag") and strip.channelbag:
|
||||
for fcurve in strip.channelbag.fcurves:
|
||||
for keyframe in fcurve.keyframe_points:
|
||||
keyframes.add(int(keyframe.co[0]))
|
||||
|
||||
# Фоллбек для Legacy экшенов
|
||||
if not keyframes and hasattr(action, "fcurves"):
|
||||
for fcurve in action.fcurves:
|
||||
for keyframe in fcurve.keyframe_points:
|
||||
keyframes.add(int(keyframe.co[0]))
|
||||
|
||||
keyframes = sorted(keyframes)
|
||||
|
||||
# Сортируем ключевые кадры
|
||||
keyframes = sorted(keyframes)
|
||||
|
||||
# Сохраняем координаты и матрицы поворота для каждой кости на каждом ключевом кадре
|
||||
file.write("=== Bone Transforms per Keyframe ===\n")
|
||||
file.write(f"Keyframes: {len(keyframes)}\n")
|
||||
for frame in keyframes:
|
||||
# Устанавливаем текущий кадр
|
||||
bpy.context.scene.frame_set(frame)
|
||||
bpy.context.view_layer.update() # Обновляем сцену
|
||||
|
||||
file.write(f"Frame: {frame}\n")
|
||||
for bone in armature_obj.pose.bones:
|
||||
# Получаем координаты и матрицу поворота кости в мировом пространстве
|
||||
matrix = bone.matrix
|
||||
location = matrix.translation
|
||||
rotation = matrix.to_euler()
|
||||
|
||||
# Записываем данные
|
||||
file.write(f" Bone: {bone.name}\n")
|
||||
file.write(f" Location: {location}\n")
|
||||
file.write(f" Rotation: {rotation}\n")
|
||||
file.write(f" Matrix:\n")
|
||||
for row in matrix:
|
||||
file.write(f" {row}\n")
|
||||
else:
|
||||
file.write(f"Объект с именем '{armature_name}' не найден или не является арматурой.\n")
|
||||
|
||||
print("Данные сохранены в файл 'mesh_armature_and_animation_data.txt'")
|
||||
Loading…
Reference in New Issue
Block a user