143 lines
4.8 KiB
Python
143 lines
4.8 KiB
Python
import asyncio
|
|
from typing import Any
|
|
|
|
import bpy
|
|
import math
|
|
import sys
|
|
|
|
from bpy_extras import anim_utils
|
|
from contextlib import suppress
|
|
from mathutils import Vector, Matrix
|
|
|
|
|
|
def ui_refresh_properties():
|
|
# Refreshes the properties panel
|
|
for windowManager in bpy.data.window_managers:
|
|
for window in windowManager.windows:
|
|
for area in window.screen.areas:
|
|
if area.type == 'PROPERTIES':
|
|
area.tag_redraw()
|
|
|
|
|
|
def ui_refresh_view_3d():
|
|
# Refreshes the view 3D panel
|
|
for windowManager in bpy.data.window_managers:
|
|
for window in windowManager.windows:
|
|
for area in window.screen.areas:
|
|
if area.type == 'VIEW_3D':
|
|
area.tag_redraw()
|
|
|
|
|
|
def ui_refresh_all():
|
|
if not hasattr(bpy.data, "window_managers"):
|
|
return
|
|
# Refreshes all panels
|
|
for windowManager in bpy.data.window_managers:
|
|
for window in windowManager.windows:
|
|
for area in window.screen.areas:
|
|
area.tag_redraw()
|
|
|
|
|
|
def reprint(*x):
|
|
# This prints a message in the same console line continuously
|
|
sys.stdout.write("\r" + " ".join(x))
|
|
sys.stdout.flush()
|
|
|
|
|
|
def set_active(obj):
|
|
obj.select_set(True)
|
|
obj.hide_set(False)
|
|
bpy.context.view_layer.objects.active = obj
|
|
|
|
|
|
def mat3_to_vec_roll(mat):
|
|
vecmat = vec_roll_to_mat3(mat.col[1], 0)
|
|
vecmatinv = vecmat.inverted()
|
|
rollmat = vecmatinv @ mat
|
|
roll = math.atan2(rollmat[0][2], rollmat[2][2])
|
|
return roll
|
|
|
|
|
|
def vec_roll_to_mat3(vec, roll):
|
|
target = Vector((0, 0.1, 0))
|
|
nor = vec.normalized()
|
|
axis = target.cross(nor)
|
|
if axis.dot(axis) > 0.0000000001:
|
|
axis.normalize()
|
|
theta = target.angle(nor)
|
|
bMatrix = Matrix.Rotation(theta, 3, axis)
|
|
else:
|
|
updown = 1 if target.dot(nor) > 0 else -1
|
|
bMatrix = Matrix.Scale(updown, 3)
|
|
bMatrix[2][2] = 1.0
|
|
|
|
rMatrix = Matrix.Rotation(roll, 3, nor)
|
|
mat = rMatrix @ bMatrix
|
|
return mat
|
|
|
|
|
|
async def cancel_gen(agen):
|
|
"""
|
|
Stops an asynchronous generator from outside.
|
|
:param agen: The asynchronous generator
|
|
:return:
|
|
"""
|
|
task = asyncio.create_task(agen.__anext__())
|
|
task.cancel()
|
|
with suppress(Exception):
|
|
await task
|
|
await agen.aclose()
|
|
|
|
|
|
def get_fcurves_from_action(action: bpy.types.Action, slot_identifier: int = 0) -> list[Any]:
|
|
"""Returns all F-curves in the action that match the slot identifier.
|
|
:param action: The action to search in
|
|
:param slot_identifier: The slot identifier to search for. Only needed for Blender 5.0.0 and newer.
|
|
:return: A list of F-curves that match the slot identifier
|
|
"""
|
|
if hasattr(action, 'fcurves'):
|
|
fcurves = action.fcurves
|
|
else: # Blender 5.0.0 and newer
|
|
action_slot = action.slots[slot_identifier] # TODO: Add UI selection for action slot. Currently only uses the first slot.
|
|
channelbag: bpy.types.ActionChannelbag = anim_utils.action_get_channelbag_for_slot(action, action_slot)
|
|
fcurves = channelbag.fcurves if channelbag else []
|
|
return fcurves
|
|
|
|
|
|
def create_fcurve_in_action(action: bpy.types.Action, data_path: str, array_index: int = -1, slot_identifier: int = 0, action_group: str = "") -> bpy.types.FCurve:
|
|
"""Creates a new F-curve in the action with the given data path and array index.
|
|
:param action: The action to create the F-curve in
|
|
:param data_path: The data path of the F-curve
|
|
:param array_index: The array index of the F-curve
|
|
:param slot_identifier: The slot identifier to create the F-curve in. Only needed for Blender 5.0.0 and newer.
|
|
:param action_group: The action group to assign the F-curve to. Only needed for Blender 5.0.0 and newer.
|
|
:return: The created F-curve
|
|
"""
|
|
# Blender 4.5 and older (Legacy)
|
|
if hasattr(action, 'fcurves'):
|
|
# Note: In 5.0, action.fcurves is removed. hasattr check ensures backward compatibility.
|
|
fcurves = action.fcurves
|
|
fcurve = fcurves.new(data_path=data_path, index=array_index, action_group=action_group)
|
|
|
|
# Blender 5.0+ (Slotted Actions)
|
|
else:
|
|
# 1. Ensure a slot exists
|
|
if not action.slots:
|
|
action.slots.new(name="Slot_0", id_type="OBJECT")
|
|
|
|
# 2. Get the Slot Object
|
|
try:
|
|
action_slot = action.slots[slot_identifier]
|
|
except IndexError:
|
|
# Fallback if the requested identifier doesn't exist
|
|
action_slot = action.slots.new(name=f"Slot_{slot_identifier}", id_type="OBJECT")
|
|
|
|
# 3. Ensure the Channelbag exists
|
|
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, action_slot)
|
|
|
|
# 4. Ensure/Create the F-Curve
|
|
# Note: 'group_name' is the 5.0 parameter for 'action_group'
|
|
fcurve = channelbag.fcurves.ensure(data_path, index=array_index, group_name=action_group)
|
|
|
|
return fcurve
|