Changeset View
Standalone View
pose_library/operators.py
| Show All 24 Lines | from bpy.types import ( | ||||
| Action, | Action, | ||||
| Context, | Context, | ||||
| Event, | Event, | ||||
| FileSelectEntry, | FileSelectEntry, | ||||
| Object, | Object, | ||||
| Operator, | Operator, | ||||
| ) | ) | ||||
| from bpy_extras import asset_utils | from bpy_extras import asset_utils | ||||
| from bpy.app.translations import pgettext_tip as tip_ | |||||
sybren: The newline here can be removed, the `bpy` imports can be grouped together. | |||||
| class PoseAssetCreator: | class PoseAssetCreator: | ||||
| @classmethod | @classmethod | ||||
| def poll(cls, context: Context) -> bool: | def poll(cls, context: Context) -> bool: | ||||
| return bool( | return bool( | ||||
| # There must be an object. | # There must be an object. | ||||
| context.object | context.object | ||||
| # It must be in pose mode with selected bones. | # It must be in pose mode with selected bones. | ||||
| ▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Lines | def _prevent_action_loss(self, object: Object) -> None: | ||||
| if not action: | if not action: | ||||
| return | return | ||||
| if action.use_fake_user or action.users > 1: | if action.use_fake_user or action.users > 1: | ||||
| # Removing one user won't GC it. | # Removing one user won't GC it. | ||||
| return | return | ||||
| action.use_fake_user = True | action.use_fake_user = True | ||||
| self.report({'WARNING'}, "Action %s marked Fake User to prevent loss" % action.name) | self.report({'WARNING'}, tip_("Action %s marked Fake User to prevent loss") % action.name) | ||||
| class POSELIB_OT_restore_previous_action(Operator): | class POSELIB_OT_restore_previous_action(Operator): | ||||
| bl_idname = "poselib.restore_previous_action" | bl_idname = "poselib.restore_previous_action" | ||||
| bl_label = "Restore Previous Action" | bl_label = "Restore Previous Action" | ||||
| bl_description = "Switch back to the previous Action, after creating a pose asset" | bl_description = "Switch back to the previous Action, after creating a pose asset" | ||||
| bl_options = {"REGISTER", "UNDO"} | bl_options = {"REGISTER", "UNDO"} | ||||
| ▲ Show 20 Lines • Show All 134 Lines • ▼ Show 20 Lines | def execute(self, context: Context) -> Set[str]: | ||||
| marker_len = len(POSELIB_OT_copy_as_asset.CLIPBOARD_ASSET_MARKER) | marker_len = len(POSELIB_OT_copy_as_asset.CLIPBOARD_ASSET_MARKER) | ||||
| filepath = Path(clipboard[marker_len:]) | filepath = Path(clipboard[marker_len:]) | ||||
| assets = functions.load_assets_from(filepath) | assets = functions.load_assets_from(filepath) | ||||
| if not assets: | if not assets: | ||||
| self.report({"ERROR"}, "Did not find any assets on clipboard") | self.report({"ERROR"}, "Did not find any assets on clipboard") | ||||
| return {"CANCELLED"} | return {"CANCELLED"} | ||||
| self.report({"INFO"}, "Pasted %d assets" % len(assets)) | self.report({"INFO"}, tip_("Pasted %d assets") % len(assets)) | ||||
| bpy.ops.asset.library_refresh() | bpy.ops.asset.library_refresh() | ||||
| asset_browser_area = asset_browser.area_from_context(context) | asset_browser_area = asset_browser.area_from_context(context) | ||||
| if not asset_browser_area: | if not asset_browser_area: | ||||
| return {"FINISHED"} | return {"FINISHED"} | ||||
| # Assign same catalog as in asset browser. | # Assign same catalog as in asset browser. | ||||
| Show All 31 Lines | def _load_and_use_pose(self, context: Context) -> Set[str]: | ||||
| asset_library_ref = context.asset_library_ref | asset_library_ref = context.asset_library_ref | ||||
| asset = context.asset_file_handle | 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: | if not asset_lib_path: | ||||
| self.report( # type: ignore | self.report( # type: ignore | ||||
| {"ERROR"}, | {"ERROR"}, | ||||
| # TODO: Add some way to get the library name from the library reference (just asset_library_ref.name?). | # TODO: Add some way to get the library name from the library reference (just asset_library_ref.name?). | ||||
| f"Selected asset {asset.name} could not be located inside the asset library", | tip_("Selected asset %s could not be located inside the asset library") % asset.name, | ||||
| ) | ) | ||||
| return {"CANCELLED"} | return {"CANCELLED"} | ||||
| if asset.id_type != 'ACTION': | if asset.id_type != 'ACTION': | ||||
| self.report( # type: ignore | self.report( # type: ignore | ||||
| {"ERROR"}, | {"ERROR"}, | ||||
| f"Selected asset {asset.name} is not an Action", | tip_("Selected asset %s is not an Action") % asset.name, | ||||
| ) | ) | ||||
| return {"CANCELLED"} | return {"CANCELLED"} | ||||
| with bpy.types.BlendData.temp_data() as temp_data: | with bpy.types.BlendData.temp_data() as temp_data: | ||||
| with temp_data.libraries.load(asset_lib_path) as (data_from, data_to): | with temp_data.libraries.load(asset_lib_path) as (data_from, data_to): | ||||
| data_to.actions = [asset.name] | data_to.actions = [asset.name] | ||||
| action: Action = data_to.actions[0] | action: Action = data_to.actions[0] | ||||
| return self.use_pose(context, action) | return self.use_pose(context, action) | ||||
| class POSELIB_OT_pose_asset_select_bones(PoseAssetUser, Operator): | class POSELIB_OT_pose_asset_select_bones(PoseAssetUser, Operator): | ||||
| bl_idname = "poselib.pose_asset_select_bones" | bl_idname = "poselib.pose_asset_select_bones" | ||||
| bl_label = "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_options = {"REGISTER", "UNDO"} | bl_options = {"REGISTER", "UNDO"} | ||||
| select: BoolProperty(name="Select", default=True) # type: ignore | select: BoolProperty(name="Select", default=True) # type: ignore | ||||
| flipped: BoolProperty(name="Flipped", default=False) # type: ignore | flipped: BoolProperty(name="Flipped", default=False) # type: ignore | ||||
| def use_pose(self, context: Context, pose_asset: Action) -> Set[str]: | def use_pose(self, context: Context, pose_asset: Action) -> Set[str]: | ||||
| arm_object: Object = context.object | 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, select=self.select, flipped=self.flipped) | ||||
| verb = "Selected" if self.select else "Deselected" | if self.select: | ||||
| self.report({"INFO"}, f"{verb} bones from {pose_asset.name}") | msg = tip_("Selected bones from %s") % pose_asset.name | ||||
| else: | |||||
| msg = tip_("Deselected bones from %s") % pose_asset.name | |||||
| self.report({"INFO"}, msg) | |||||
| return {"FINISHED"} | return {"FINISHED"} | ||||
Not Done Inline ActionsIn the old code, the conditional expression was small, and only contained what needed to be conditional. The new code duplicates almost all the code there. I don't think this is an acceptable change, if only the formatting method is supposed to be altered here. sybren: In the old code, the conditional expression was small, and only contained what needed to be… | |||||
Not Done Inline ActionsOne possible intermediate solution is to do: self.report({"INFO"}, (tip_("Selected bones from %s") if self.select else
tip_("Deselected bones from %s")).format(pose_asset.name))Would that be all right? Otherwise, here is an explanation of why verbosity is strongly favored over this sort of constructions for internationalization. Admittedly, this particular instance will almost surely work in all indo-european languages, but what about others? What if a language’s grammar uses different word orders, like: “Bones selected from <name>”, but “In <name>, bones were deselected”? Furthermore, what if “Selected” and “Deselected” were already used elsewhere in the code base but in a different context? For instance, to mean “Selected [feminine plural]” or “the <thing> that <is / was / is to be> selected” or “the <thing> has now been selected”? There are 20 usages of “Selected” in Blender, and 6 usages of “Deselected”, and I not all of them have the same nuance. This results in poor, broken translations, a frustrating time for users and a very frustrating for translators and developers trying to disambiguate between various usages (that is, as far as I know, Bastien and I right now). My point is, we have no way to know for sure what is needed if we only consider the languages we already know about. A lesser issue is that it makes translators’ work harder, because they have to know the context of the sentence, but most translators are not developers, so it’s good if they don’t have to delve into the code to know what they’re doing. I’ll still revert if you insist, but using formatting to build sentences is very bad practice for i18n. pioverfour: One possible intermediate solution is to do:
```
self.report({"INFO"}, (tip_("Selected bones… | |||||
Not Done Inline Actions
You make an excellent point, I'm glad to be corrected here :) I think it would still be nice to keep the construction of the message conditional, and take the call to self.report(...) out of the conditional, something like: if self.select: msg = tip_("Selected bones from %s") % pose_asset.name else: msg = tip_("Deselected bones from %s") % pose_asset.name self.report({"INFO"}, msg) sybren: > I’ll still revert if you insist, but using formatting to build sentences is very bad practice… | |||||
| @classmethod | @classmethod | ||||
| def description( | def description( | ||||
| cls, _context: Context, properties: 'POSELIB_OT_pose_asset_select_bones' | cls, _context: Context, properties: 'POSELIB_OT_pose_asset_select_bones' | ||||
| ) -> str: | ) -> str: | ||||
| if properties.select: | if properties.select: | ||||
| return cls.bl_description | return cls.bl_description | ||||
| return cls.bl_description.replace("Select", "Deselect") | return cls.bl_description.replace("Select", "Deselect") | ||||
| ▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | class POSELIB_OT_convert_old_poselib(Operator): | ||||
| @classmethod | @classmethod | ||||
| def poll(cls, context: Context) -> bool: | 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: | if not action: | ||||
| cls.poll_message_set("Active object has no Action") | cls.poll_message_set("Active object has no Action") | ||||
| return False | return False | ||||
| if not action.pose_markers: | if not action.pose_markers: | ||||
| cls.poll_message_set("Action %r is not a legacy pose library" % action.name) | cls.poll_message_set(tip_("Action %r is not a legacy pose library") % action.name) | ||||
| return False | return False | ||||
| return True | return True | ||||
| def execute(self, context: Context) -> Set[str]: | def execute(self, context: Context) -> Set[str]: | ||||
| from . import conversion | from . import conversion | ||||
| old_poselib = context.object.animation_data.action | old_poselib = context.object.animation_data.action | ||||
| new_actions = conversion.convert_old_poselib(old_poselib) | new_actions = conversion.convert_old_poselib(old_poselib) | ||||
| if not new_actions: | if not new_actions: | ||||
| self.report({'ERROR'}, "Unable to convert to pose assets") | self.report({'ERROR'}, "Unable to convert to pose assets") | ||||
| return {'CANCELLED'} | return {'CANCELLED'} | ||||
| self.report({'INFO'}, "Converted %d poses to pose assets" % len(new_actions)) | self.report({'INFO'}, tip_("Converted %d poses to pose assets") % len(new_actions)) | ||||
| return {'FINISHED'} | return {'FINISHED'} | ||||
| class POSELIB_OT_convert_old_object_poselib(Operator): | class POSELIB_OT_convert_old_object_poselib(Operator): | ||||
| bl_idname = "poselib.convert_old_object_poselib" | bl_idname = "poselib.convert_old_object_poselib" | ||||
| bl_label = "Convert Legacy Pose Library" | bl_label = "Convert Legacy Pose Library" | ||||
| bl_description = "Create a pose asset for each pose marker in this legacy pose library data-block" | bl_description = "Create a pose asset for each pose marker in this legacy pose library data-block" | ||||
| # Mark this one as "internal", as it converts `context.object.pose_library` | # Mark this one as "internal", as it converts `context.object.pose_library` | ||||
| # instead of its current animation Action. | # instead of its current animation Action. | ||||
| bl_options = {"REGISTER", "UNDO", "INTERNAL"} | bl_options = {"REGISTER", "UNDO", "INTERNAL"} | ||||
| @classmethod | @classmethod | ||||
| def poll(cls, context: Context) -> bool: | def poll(cls, context: Context) -> bool: | ||||
| action = context.object and context.object.pose_library | action = context.object and context.object.pose_library | ||||
| if not action: | if not action: | ||||
| cls.poll_message_set("Active object has no pose library Action") | cls.poll_message_set("Active object has no pose library Action") | ||||
| return False | return False | ||||
| if not action.pose_markers: | if not action.pose_markers: | ||||
| cls.poll_message_set("Action %r is not a legacy pose library" % action.name) | cls.poll_message_set(tip_("Action %r is not a legacy pose library") % action.name) | ||||
| return False | return False | ||||
| return True | return True | ||||
| def execute(self, context: Context) -> Set[str]: | def execute(self, context: Context) -> Set[str]: | ||||
| from . import conversion | from . import conversion | ||||
| old_poselib = context.object.pose_library | old_poselib = context.object.pose_library | ||||
| new_actions = conversion.convert_old_poselib(old_poselib) | new_actions = conversion.convert_old_poselib(old_poselib) | ||||
| if not new_actions: | if not new_actions: | ||||
| self.report({'ERROR'}, "Unable to convert to pose assets") | self.report({'ERROR'}, "Unable to convert to pose assets") | ||||
| return {'CANCELLED'} | return {'CANCELLED'} | ||||
| self.report({'INFO'}, "Converted %d poses to pose assets" % len(new_actions)) | self.report({'INFO'}, tip_("Converted %d poses to pose assets") % len(new_actions)) | ||||
| return {'FINISHED'} | return {'FINISHED'} | ||||
| classes = ( | classes = ( | ||||
| ASSET_OT_assign_action, | ASSET_OT_assign_action, | ||||
| POSELIB_OT_apply_pose_asset_for_keymap, | POSELIB_OT_apply_pose_asset_for_keymap, | ||||
| POSELIB_OT_blend_pose_asset_for_keymap, | POSELIB_OT_blend_pose_asset_for_keymap, | ||||
| POSELIB_OT_convert_old_poselib, | POSELIB_OT_convert_old_poselib, | ||||
| Show All 9 Lines | |||||
The newline here can be removed, the bpy imports can be grouped together.