black format

This commit is contained in:
2026-01-07 16:05:47 +01:00
parent f7c125ae7b
commit 3d65bb6e4d
49 changed files with 2817 additions and 2280 deletions
+5 -5
View File
@@ -1,14 +1,14 @@
from asset_library.pose import operators
from asset_library.pose import (
operators)
if 'bpy' in locals():
if "bpy" in locals():
import importlib
importlib.reload(operators)
def register():
operators.register()
def unregister():
operators.unregister()
operators.unregister()
+1 -1
View File
@@ -39,7 +39,7 @@ def convert_old_poselib(old_poselib: Action) -> Collection[Action]:
# appropriate frame in the scene (to set up things like the background
# colour), but the old-style poselib doesn't contain such information. All
# we can do is just render on the current frame.
bpy.ops.asset.mark({'selected_ids': pose_assets})
bpy.ops.asset.mark({"selected_ids": pose_assets})
return pose_assets
+149 -92
View File
@@ -22,7 +22,13 @@ import subprocess
import uuid
import time
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, PointerProperty, StringProperty
from bpy.props import (
BoolProperty,
CollectionProperty,
EnumProperty,
PointerProperty,
StringProperty,
)
from bpy.types import (
Action,
Context,
@@ -40,11 +46,7 @@ from asset_library.action.functions import (
get_keyframes,
)
from asset_library.common.bl_utils import (
get_view3d_persp,
load_assets_from,
split_path
)
from asset_library.common.bl_utils import get_view3d_persp, load_assets_from, split_path
class POSELIB_OT_create_pose_asset(Operator):
@@ -59,25 +61,28 @@ class POSELIB_OT_create_pose_asset(Operator):
pose_name: StringProperty(name="Pose Name") # type: ignore
activate_new_action: BoolProperty(name="Activate New Action", default=True) # type: ignore
@classmethod
def poll(cls, context: Context) -> bool:
# Make sure that if there is an asset browser open, the artist can see the newly created pose asset.
asset_browse_area: Optional[bpy.types.Area] = asset_browser.area_from_context(context)
asset_browse_area: Optional[bpy.types.Area] = asset_browser.area_from_context(
context
)
if not asset_browse_area:
# No asset browser is visible, so there also aren't any expectations
# that this asset will be visible.
return True
asset_space_params = asset_browser.params(asset_browse_area)
if asset_space_params.asset_library_ref != 'LOCAL':
cls.poll_message_set("Asset Browser must be set to the Current File library")
if asset_space_params.asset_library_ref != "LOCAL":
cls.poll_message_set(
"Asset Browser must be set to the Current File library"
)
return False
return True
def execute(self, context: Context) -> Set[str]:
#pose_name = self.pose_name or context.object.name
# pose_name = self.pose_name or context.object.name
pose_name = False
if context.object.animation_data:
if context.object.animation_data.action:
@@ -85,28 +90,28 @@ class POSELIB_OT_create_pose_asset(Operator):
if pose_name:
prefix = True
asset_name = Path(bpy.data.filepath).stem.split('_')[0]
asset_name = Path(bpy.data.filepath).stem.split("_")[0]
action_asset_name = re.search(f'^{asset_name}.', pose_name)
action_asset_name = re.search(f"^{asset_name}.", pose_name)
if action_asset_name:
pose_name = pose_name.replace(action_asset_name.group(0), '')
pose_name = pose_name.replace(action_asset_name.group(0), "")
side = re.search('_\w$', pose_name)
side = re.search("_\w$", pose_name)
if side:
pose_name = pose_name.replace(side.group(0), '')
pose_name = pose_name.replace(side.group(0), "")
if 'hands' in context.object.animation_data.action.name.lower():
pose_name = f'hand_{pose_name}'
if pose_name.startswith('lips_'):
pose_name.replace('lips_', '')
split = pose_name.split('_')
pose_name = '-'.join([s for s in split if s.isupper()])
pose_name = f'{pose_name}_{split[-1]}'
if "hands" in context.object.animation_data.action.name.lower():
pose_name = f"hand_{pose_name}"
if pose_name.startswith("lips_"):
pose_name.replace("lips_", "")
split = pose_name.split("_")
pose_name = "-".join([s for s in split if s.isupper()])
pose_name = f"{pose_name}_{split[-1]}"
prefix = False
if prefix and not pose_name.startswith(asset_name):
pose_name = f'{asset_name}_{pose_name}'
pose_name = f"{asset_name}_{pose_name}"
else:
pose_name = self.pose_name or context.object.name
@@ -126,7 +131,6 @@ class POSELIB_OT_create_pose_asset(Operator):
if context.scene.camera:
data_dict.update(dict(camera=context.scene.camera.name))
for k, v in data_dict.items():
data[k] = v
###
@@ -134,7 +138,7 @@ class POSELIB_OT_create_pose_asset(Operator):
if self.activate_new_action:
self._set_active_action(context, asset)
self._activate_asset_in_browser(context, asset)
return {'FINISHED'}
return {"FINISHED"}
def _set_active_action(self, context: Context, asset: Action) -> None:
self._prevent_action_loss(context.object)
@@ -149,7 +153,9 @@ class POSELIB_OT_create_pose_asset(Operator):
This makes it possible to immediately check & edit the created pose asset.
"""
asset_browse_area: Optional[bpy.types.Area] = asset_browser.area_from_context(context)
asset_browse_area: Optional[bpy.types.Area] = asset_browser.area_from_context(
context
)
if not asset_browse_area:
return
@@ -181,7 +187,9 @@ class POSELIB_OT_create_pose_asset(Operator):
return
action.use_fake_user = True
self.report({'WARNING'}, "Action %s marked Fake User to prevent loss" % action.name)
self.report(
{"WARNING"}, "Action %s marked Fake User to prevent loss" % action.name
)
class POSELIB_OT_restore_previous_action(Operator):
@@ -215,17 +223,17 @@ class POSELIB_OT_restore_previous_action(Operator):
self._timer = wm.event_timer_add(0.001, window=context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
return {"RUNNING_MODAL"}
def modal(self, context, event):
if event.type != 'TIMER':
return {'RUNNING_MODAL'}
if event.type != "TIMER":
return {"RUNNING_MODAL"}
wm = context.window_manager
wm.event_timer_remove(self._timer)
context.object.pose.apply_pose_from_action(self.pose_action)
return {'FINISHED'}
return {"FINISHED"}
class ASSET_OT_assign_action(Operator):
@@ -257,7 +265,9 @@ class ASSET_OT_assign_action(Operator):
class POSELIB_OT_copy_as_asset(Operator):
bl_idname = "poselib.copy_as_asset"
bl_label = "Copy Pose As Asset"
bl_description = "Create a new pose asset on the clipboard, to be pasted into an Asset Browser"
bl_description = (
"Create a new pose asset on the clipboard, to be pasted into an Asset Browser"
)
bl_options = {"REGISTER"}
CLIPBOARD_ASSET_MARKER = "ASSET-BLEND="
@@ -289,7 +299,10 @@ class POSELIB_OT_copy_as_asset(Operator):
filepath,
)
asset_browser.tag_redraw(context.screen)
self.report({"INFO"}, "Pose Asset copied, use Paste As New Asset in any Asset Browser to paste")
self.report(
{"INFO"},
"Pose Asset copied, use Paste As New Asset in any Asset Browser to paste",
)
# The asset has been saved to disk, so to clean up it has to loose its asset & fake user status.
asset.asset_clear()
@@ -300,7 +313,10 @@ class POSELIB_OT_copy_as_asset(Operator):
if asset.users > 0:
# This should never happen, and indicates a bug in the code. Having a warning about it is nice,
# but it shouldn't stand in the way of actually cleaning up the meant-to-be-temporary datablock.
self.report({"WARNING"}, "Unexpected non-zero user count for the asset, please report this as a bug")
self.report(
{"WARNING"},
"Unexpected non-zero user count for the asset, please report this as a bug",
)
bpy.data.actions.remove(asset)
return {"FINISHED"}
@@ -331,8 +347,10 @@ class POSELIB_OT_paste_asset(Operator):
return False
asset_lib_ref = context.space_data.params.asset_library_ref
if asset_lib_ref != 'LOCAL':
cls.poll_message_set("Asset Browser must be set to the Current File library")
if asset_lib_ref != "LOCAL":
cls.poll_message_set(
"Asset Browser must be set to the Current File library"
)
return False
# Delay checking the clipboard as much as possible, as it's CPU-heavier than the other checks.
@@ -348,7 +366,6 @@ class POSELIB_OT_paste_asset(Operator):
return True
def execute(self, context: Context) -> Set[str]:
clipboard = context.window_manager.clipboard
marker_len = len(POSELIB_OT_copy_as_asset.CLIPBOARD_ASSET_MARKER)
@@ -379,18 +396,18 @@ class POSELIB_OT_paste_asset(Operator):
class POSELIB_OT_pose_asset_select_bones(Operator):
bl_idname = "poselib.pose_asset_select_bones"
bl_label = "Select Bones"
#bl_description = "Select those bones that are used in this pose"
# bl_description = "Select those bones that are used in this pose"
bl_description = "Click: Select used Bones\nAlt+Click: Select Flipped Bones\nCtrl+Click: Select Both sides."
bl_options = {"REGISTER", "UNDO"}
#bl_property = "selected_side"
# bl_property = "selected_side"
selected_side: EnumProperty(
name='Selected Side',
name="Selected Side",
items=(
('CURRENT', "Current", ""),
('FLIPPED', "Flipped", ""),
('BOTH', "Both", ""),
)
("CURRENT", "Current", ""),
("FLIPPED", "Flipped", ""),
("BOTH", "Both", ""),
),
)
@classmethod
@@ -402,7 +419,7 @@ class POSELIB_OT_pose_asset_select_bones(Operator):
and context.asset_file_handle
):
return False
return context.asset_file_handle.id_type == 'ACTION'
return context.asset_file_handle.id_type == "ACTION"
def execute(self, context: Context) -> Set[str]:
asset: FileSelectEntry = context.asset_file_handle
@@ -417,7 +434,9 @@ class POSELIB_OT_pose_asset_select_bones(Operator):
def _load_and_use_pose(self, context: Context) -> Set[str]:
asset_library_ref = context.asset_library_ref
asset = context.asset_file_handle
asset_lib_path = bpy.types.AssetHandle.get_full_library_path(asset, asset_library_ref)
asset_lib_path = bpy.types.AssetHandle.get_full_library_path(
asset, asset_library_ref
)
if not asset_lib_path:
self.report( # type: ignore
@@ -426,7 +445,7 @@ class POSELIB_OT_pose_asset_select_bones(Operator):
f"Selected asset {asset.name} could not be located inside the asset library",
)
return {"CANCELLED"}
if asset.id_type != 'ACTION':
if asset.id_type != "ACTION":
self.report( # type: ignore
{"ERROR"},
f"Selected asset {asset.name} is not an Action",
@@ -442,10 +461,13 @@ class POSELIB_OT_pose_asset_select_bones(Operator):
def use_pose(self, context: Context, pose_asset: Action) -> Set[str]:
arm_object: Object = context.object
#pose_usage.select_bones(arm_object, pose_asset, select=self.select, flipped=self.flipped)
pose_usage.select_bones(arm_object, pose_asset, selected_side=self.selected_side)
# pose_usage.select_bones(arm_object, pose_asset, select=self.select, flipped=self.flipped)
pose_usage.select_bones(
arm_object, pose_asset, selected_side=self.selected_side
)
return {"FINISHED"}
# This operator takes the Window Manager's `actionlib_flipped` property, and
# passes it to the `POSELIB_OT_blend_pose_asset` operator. This makes it
# possible to bind a key to the operator and still have it respect the global
@@ -464,7 +486,7 @@ class POSELIB_OT_blend_pose_asset_for_keymap(Operator):
@classmethod
def poll(cls, context: Context) -> bool:
return bpy.ops.poselib.blend_pose_asset.poll(context.copy())
"""
def invoke(self, context, event):
if event.type == 'LEFTMOUSE':
@@ -478,10 +500,14 @@ class POSELIB_OT_blend_pose_asset_for_keymap(Operator):
"""
def invoke(self, context: Context, event: Event) -> Set[str]:
return bpy.ops.poselib.blend_pose_asset(context.copy(), 'INVOKE_DEFAULT', flipped=self.flipped)
return bpy.ops.poselib.blend_pose_asset(
context.copy(), "INVOKE_DEFAULT", flipped=self.flipped
)
def execute(self, context: Context) -> Set[str]:
return bpy.ops.poselib.blend_pose_asset(context.copy(), 'EXEC_DEFAULT', flipped=self.flipped)
return bpy.ops.poselib.blend_pose_asset(
context.copy(), "EXEC_DEFAULT", flipped=self.flipped
)
# This operator takes the Window Manager's `actionlib_flipped` property, and
@@ -489,14 +515,15 @@ class POSELIB_OT_blend_pose_asset_for_keymap(Operator):
# possible to bind a key to the operator and still have it respect the global
# "Flip Pose" checkbox.
class POSELIB_OT_apply_pose_asset_for_keymap(Operator):
bl_idname = "poselib.apply_pose_asset_for_keymap"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
_rna = bpy.ops.poselib.apply_pose_asset.get_rna_type()
bl_label = _rna.name
#bl_description = _rna.description
bl_description = 'Apply Pose to Bones'
# bl_description = _rna.description
bl_description = "Apply Pose to Bones"
del _rna
flipped: BoolProperty(name="Flipped", default=False) # type: ignore
@@ -506,27 +533,42 @@ class POSELIB_OT_apply_pose_asset_for_keymap(Operator):
if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
return False
return bpy.ops.poselib.apply_pose_asset.poll(context.copy())
def execute(self, context: Context) -> Set[str]:
if self.flipped:
action = bpy.data.actions.get(context.active_file.name)
store_bones = {}
bones = [
'blendshape-eyes', 'blendshape-eye.L', 'blendshape-eye.R',
'blendshape-corner-mouth', 'blendshape-corner-mouth.L',
'blendshape-corner-down-mouth.L', 'blendshape-corner-up-mouth.L',
'blendshape-corner-mouth-add.L','blendshape-corner-mouth.R',
'blendshape-corner-down-mouth.R', 'blendshape-corner-up-mouth.R',
'blendshape-corner-mouth-add.R', 'blendshape-center-up-mouth',
'blendshape-center-down-mouth',
'hat1.R', 'hat2.R', 'hat3.R', 'hat1.L', 'hat2.L', 'hat3.L',
"blendshape-eyes",
"blendshape-eye.L",
"blendshape-eye.R",
"blendshape-corner-mouth",
"blendshape-corner-mouth.L",
"blendshape-corner-down-mouth.L",
"blendshape-corner-up-mouth.L",
"blendshape-corner-mouth-add.L",
"blendshape-corner-mouth.R",
"blendshape-corner-down-mouth.R",
"blendshape-corner-up-mouth.R",
"blendshape-corner-mouth-add.R",
"blendshape-center-up-mouth",
"blendshape-center-down-mouth",
"hat1.R",
"hat2.R",
"hat3.R",
"hat1.L",
"hat2.L",
"hat3.L",
]
attributes = [
'location', 'rotation_quaternion',
'rotation_euler', 'rotation_axis_angle', 'scale'
"location",
"rotation_quaternion",
"rotation_euler",
"rotation_axis_angle",
"scale",
]
if action:
@@ -534,7 +576,7 @@ class POSELIB_OT_apply_pose_asset_for_keymap(Operator):
bone_name, prop_name = split_path(fc.data_path)
if bone_name not in bones:
continue
if not bone_name in store_bones.keys():
store_bones[bone_name] = {}
@@ -543,30 +585,40 @@ class POSELIB_OT_apply_pose_asset_for_keymap(Operator):
if not prop_name in store_bones[bone_name].keys():
store_bones[bone_name][prop_name] = []
val = getattr(context.object.pose.bones[bone_name], prop_name)
val = getattr(
context.object.pose.bones[bone_name], prop_name
)
store_bones[bone_name][prop_name].append(fc.evaluate(context.scene.frame_current))
bpy.ops.poselib.apply_pose_asset(context.copy(), 'EXEC_DEFAULT', flipped=True)
store_bones[bone_name][prop_name].append(
fc.evaluate(context.scene.frame_current)
)
bpy.ops.poselib.apply_pose_asset(
context.copy(), "EXEC_DEFAULT", flipped=True
)
for bone, v in store_bones.items():
for attr, attr_val in v.items():
flipped_vector = 1
### TODO FAIRE ÇA PROPREMENT AVEC UNE COMPREHENSION LIST OU AUTRE
if re.search(r'\.[RL]$', bone):
if re.search(r"\.[RL]$", bone):
flipped_bone = pose_usage.flip_side_name(bone)
if attr == 'location':
if attr == "location":
flipped_vector = Vector((-1, 1, 1))
# print('-----', store_bones.get(flipped_bone)[attr])
attr_val = Vector(store_bones.get(flipped_bone)[attr]) * flipped_vector
attr_val = (
Vector(store_bones.get(flipped_bone)[attr]) * flipped_vector
)
setattr(context.object.pose.bones[bone], attr, attr_val)
return {'FINISHED'}
return {"FINISHED"}
else:
return bpy.ops.poselib.apply_pose_asset(context.copy(), 'EXEC_DEFAULT', flipped=False)
return bpy.ops.poselib.apply_pose_asset(
context.copy(), "EXEC_DEFAULT", flipped=False
)
class POSELIB_OT_convert_old_poselib(Operator):
@@ -577,12 +629,18 @@ class POSELIB_OT_convert_old_poselib(Operator):
@classmethod
def poll(cls, context: Context) -> bool:
action = context.object and context.object.animation_data and context.object.animation_data.action
action = (
context.object
and context.object.animation_data
and context.object.animation_data.action
)
if not action:
cls.poll_message_set("Active object has no Action")
return False
if not action.pose_markers:
cls.poll_message_set("Action %r is not a old-style pose library" % action.name)
cls.poll_message_set(
"Action %r is not a old-style pose library" % action.name
)
return False
return True
@@ -593,12 +651,11 @@ class POSELIB_OT_convert_old_poselib(Operator):
new_actions = conversion.convert_old_poselib(old_poselib)
if not new_actions:
self.report({'ERROR'}, "Unable to convert to pose assets")
return {'CANCELLED'}
self.report({'INFO'}, "Converted %d poses to pose assets" % len(new_actions))
return {'FINISHED'}
self.report({"ERROR"}, "Unable to convert to pose assets")
return {"CANCELLED"}
self.report({"INFO"}, "Converted %d poses to pose assets" % len(new_actions))
return {"FINISHED"}
classes = (
@@ -609,7 +666,7 @@ classes = (
POSELIB_OT_create_pose_asset,
POSELIB_OT_paste_asset,
POSELIB_OT_pose_asset_select_bones,
POSELIB_OT_restore_previous_action
POSELIB_OT_restore_previous_action,
)
register, unregister = bpy.utils.register_classes_factory(classes)
+19 -7
View File
@@ -129,7 +129,9 @@ class PoseActionCreator:
continue
try:
value = self._current_value(armature_ob, fcurve.data_path, fcurve.array_index)
value = self._current_value(
armature_ob, fcurve.data_path, fcurve.array_index
)
except UnresolvablePathError:
# A once-animated property no longer exists.
continue
@@ -197,7 +199,9 @@ class PoseActionCreator:
fcurve: Optional[FCurve] = dst_action.fcurves.find(rna_path, index=array_index)
if fcurve is None:
fcurve = dst_action.fcurves.new(rna_path, index=array_index, action_group=bone_name)
fcurve = dst_action.fcurves.new(
rna_path, index=array_index, action_group=bone_name
)
fcurve.keyframe_points.insert(self.params.src_frame_nr, value=value)
fcurve.update()
@@ -296,10 +300,13 @@ def create_pose_asset(
pose_action.asset_generate_preview()
return pose_action
#def create_pose_asset_from_context(context: Context, new_asset_name: str, selection=True) -> Optional[Action]:
def create_pose_asset_from_context(context: Context, new_asset_name: str) -> Optional[Action]:
# def create_pose_asset_from_context(context: Context, new_asset_name: str, selection=True) -> Optional[Action]:
def create_pose_asset_from_context(
context: Context, new_asset_name: str
) -> Optional[Action]:
"""Create Action asset from active object & selected bones."""
bones = context.selected_pose_bones_from_active_object
bone_names = {bone.name for bone in bones}
@@ -369,7 +376,10 @@ def copy_keyframe(dst_fcurve: FCurve, src_keyframe: Keyframe) -> Keyframe:
"""Copy a keyframe from one FCurve to the other."""
dst_keyframe = dst_fcurve.keyframe_points.insert(
src_keyframe.co.x, src_keyframe.co.y, options={'FAST'}, keyframe_type=src_keyframe.type
src_keyframe.co.x,
src_keyframe.co.y,
options={"FAST"},
keyframe_type=src_keyframe.type,
)
for propname in {
@@ -412,7 +422,9 @@ def find_keyframe(fcurve: FCurve, frame: float) -> Optional[Keyframe]:
return None
def assign_from_asset_browser(asset: Action, asset_browser_area: bpy.types.Area) -> None:
def assign_from_asset_browser(
asset: Action, asset_browser_area: bpy.types.Area
) -> None:
"""Assign some things from the asset browser to the asset.
This sets the current catalog ID, and in the future could include tags
+6 -7
View File
@@ -14,7 +14,7 @@ from bpy.types import (
)
#def select_bones(arm_object: Object, action: Action, *, select: bool, flipped: bool, both=False) -> None:
# def select_bones(arm_object: Object, action: Action, *, select: bool, flipped: bool, both=False) -> None:
def select_bones(arm_object: Object, action: Action, *, selected_side, toggle=True):
pose_bone_re = re.compile(r'pose.bones\["([^"]+)"\]')
pose = arm_object.pose
@@ -34,15 +34,14 @@ def select_bones(arm_object: Object, action: Action, *, selected_side, toggle=Tr
if bone_name in seen_bone_names:
continue
seen_bone_names.add(bone_name)
if selected_side == 'FLIPPED':
if selected_side == "FLIPPED":
bones_to_select.add(bone_name_flip)
elif selected_side == 'BOTH':
elif selected_side == "BOTH":
bones_to_select.add(bone_name_flip)
bones_to_select.add(bone_name)
elif selected_side == 'CURRENT':
elif selected_side == "CURRENT":
bones_to_select.add(bone_name)
for bone in bones_to_select:
pose_bone = pose.bones.get(bone)
@@ -174,7 +173,7 @@ def flip_side_name(to_flip: str) -> str:
return prefix + replace + suffix + number
if __name__ == '__main__':
if __name__ == "__main__":
import doctest
print(f"Test result: {doctest.testmod()}")