diff options
Diffstat (limited to 'release/scripts/startup/bl_operators')
-rw-r--r-- | release/scripts/startup/bl_operators/anim.py | 3 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/clip.py | 64 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/constraint.py | 15 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/geometry_nodes.py | 4 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/mesh.py | 4 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/node.py | 72 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/object.py | 2 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/presets.py | 4 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/screen_play_rendered_anim.py | 3 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/spreadsheet.py | 40 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/userpref.py | 42 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/uvcalc_follow_active.py | 2 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/uvcalc_lightmap.py | 2 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/view3d.py | 3 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/wm.py | 200 |
15 files changed, 353 insertions, 107 deletions
diff --git a/release/scripts/startup/bl_operators/anim.py b/release/scripts/startup/bl_operators/anim.py index ab56b24d186..81a28964389 100644 --- a/release/scripts/startup/bl_operators/anim.py +++ b/release/scripts/startup/bl_operators/anim.py @@ -355,7 +355,8 @@ class UpdateAnimatedTransformConstraint(Operator): use_convert_to_radians: BoolProperty( name="Convert to Radians", - description="Convert fcurves/drivers affecting rotations to radians (Warning: use this only once!)", + description="Convert f-curves/drivers affecting rotations to radians.\n" + "Warning: Use this only once", default=True, ) diff --git a/release/scripts/startup/bl_operators/clip.py b/release/scripts/startup/bl_operators/clip.py index 73391f94c85..45d1ea98a8a 100644 --- a/release/scripts/startup/bl_operators/clip.py +++ b/release/scripts/startup/bl_operators/clip.py @@ -18,7 +18,6 @@ # <pep8 compliant> import bpy -import os from bpy.types import Operator from bpy.props import FloatProperty from mathutils import ( @@ -207,8 +206,8 @@ class CLIP_OT_filter_tracks(Operator): @classmethod def poll(cls, context): - space = context.space_data - return (space.type == 'CLIP_EDITOR') and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): num_tracks = self._filter_values(context, self.track_threshold) @@ -222,8 +221,8 @@ class CLIP_OT_set_active_clip(Operator): @classmethod def poll(cls, context): - space = context.space_data - return space.type == 'CLIP_EDITOR' and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): clip = context.space_data.clip @@ -269,8 +268,8 @@ class CLIP_OT_track_to_empty(Operator): @classmethod def poll(cls, context): - space = context.space_data - return space.type == 'CLIP_EDITOR' and space.clip + sc = context.space_data + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): sc = context.space_data @@ -294,7 +293,7 @@ class CLIP_OT_bundles_to_mesh(Operator): @classmethod def poll(cls, context): sc = context.space_data - return (sc.type == 'CLIP_EDITOR') and sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): from bpy_extras.io_utils import unpack_list @@ -342,12 +341,8 @@ class CLIP_OT_delete_proxy(Operator): @classmethod def poll(cls, context): - if context.space_data.type != 'CLIP_EDITOR': - return False - sc = context.space_data - - return sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def invoke(self, context, event): wm = context.window_manager @@ -356,6 +351,7 @@ class CLIP_OT_delete_proxy(Operator): @staticmethod def _rmproxy(abspath): + import os import shutil if not os.path.exists(abspath): @@ -367,6 +363,7 @@ class CLIP_OT_delete_proxy(Operator): os.remove(abspath) def execute(self, context): + import os sc = context.space_data clip = sc.clip if clip.use_proxy_custom_directory: @@ -423,12 +420,8 @@ class CLIP_OT_set_viewport_background(Operator): @classmethod def poll(cls, context): - if context.space_data.type != 'CLIP_EDITOR': - return False - sc = context.space_data - - return sc.clip + return sc and (sc.type == 'CLIP_EDITOR') and sc.clip def execute(self, context): sc = context.space_data @@ -562,13 +555,11 @@ class CLIP_OT_setup_tracking_scene(Operator): @classmethod def poll(cls, context): sc = context.space_data - - if sc.type != 'CLIP_EDITOR': - return False - - clip = sc.clip - - return clip and clip.tracking.reconstruction.is_valid + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.reconstruction.is_valid: + return True + return False @staticmethod def _setupScene(context): @@ -1017,13 +1008,11 @@ class CLIP_OT_track_settings_as_default(Operator): @classmethod def poll(cls, context): sc = context.space_data - - if sc.type != 'CLIP_EDITOR': - return False - - clip = sc.clip - - return clip and clip.tracking.tracks.active + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.tracks.active: + return True + return False def execute(self, context): sc = context.space_data @@ -1067,11 +1056,12 @@ class CLIP_OT_track_settings_to_track(Operator): @classmethod def poll(cls, context): - space = context.space_data - if space.type != 'CLIP_EDITOR': - return False - clip = space.clip - return clip and clip.tracking.tracks.active + sc = context.space_data + if sc and sc.type == 'CLIP_EDITOR': + clip = sc.clip + if clip and clip.tracking.tracks.active: + return True + return False def execute(self, context): space = context.space_data diff --git a/release/scripts/startup/bl_operators/constraint.py b/release/scripts/startup/bl_operators/constraint.py index 49fc6a04112..f18f3bb3a49 100644 --- a/release/scripts/startup/bl_operators/constraint.py +++ b/release/scripts/startup/bl_operators/constraint.py @@ -33,6 +33,11 @@ class CONSTRAINT_OT_add_target(Operator): bl_label = "Add Target" bl_options = {'UNDO', 'INTERNAL'} + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): context.constraint.targets.new() return {'FINISHED'} @@ -46,6 +51,11 @@ class CONSTRAINT_OT_remove_target(Operator): index: IntProperty() + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): tgts = context.constraint.targets tgts.remove(tgts[self.index]) @@ -58,6 +68,11 @@ class CONSTRAINT_OT_normalize_target_weights(Operator): bl_label = "Normalize Weights" bl_options = {'UNDO', 'INTERNAL'} + @classmethod + def poll(cls, context): + constraint = getattr(context, "constraint", None) + return constraint + def execute(self, context): tgts = context.constraint.targets total = sum(t.weight for t in tgts) diff --git a/release/scripts/startup/bl_operators/geometry_nodes.py b/release/scripts/startup/bl_operators/geometry_nodes.py index 0c7a2a01b7a..71ef89a066b 100644 --- a/release/scripts/startup/bl_operators/geometry_nodes.py +++ b/release/scripts/startup/bl_operators/geometry_nodes.py @@ -42,8 +42,8 @@ def geometry_node_group_empty_new(): def geometry_modifier_poll(context): ob = context.object - # Test object support for geometry node modifier (No volume, curve, or hair object support yet) - if not ob or ob.type not in {'MESH', 'POINTCLOUD'}: + # Test object support for geometry node modifier (No curve, or hair object support yet) + if not ob or ob.type not in {'MESH', 'POINTCLOUD', 'VOLUME'}: return False return True diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py index 5fca3e194d7..ddbfe7845b1 100644 --- a/release/scripts/startup/bl_operators/mesh.py +++ b/release/scripts/startup/bl_operators/mesh.py @@ -219,7 +219,7 @@ class MeshSelectNext(Operator): if find_adjacent.select_next(bm, self.report): bm.select_flush_mode() - bmesh.update_edit_mesh(me, False) + bmesh.update_edit_mesh(me, loop_triangles=False) return {'FINISHED'} @@ -244,7 +244,7 @@ class MeshSelectPrev(Operator): if find_adjacent.select_prev(bm, self.report): bm.select_flush_mode() - bmesh.update_edit_mesh(me, False) + bmesh.update_edit_mesh(me, loop_triangles=False) return {'FINISHED'} diff --git a/release/scripts/startup/bl_operators/node.py b/release/scripts/startup/bl_operators/node.py index 52484f8ffc8..17e17273432 100644 --- a/release/scripts/startup/bl_operators/node.py +++ b/release/scripts/startup/bl_operators/node.py @@ -20,7 +20,6 @@ from __future__ import annotations import bpy -import nodeitems_utils from bpy.types import ( Operator, PropertyGroup, @@ -121,7 +120,7 @@ class NodeAddOperator: def poll(cls, context): space = context.space_data # needs active node editor and a tree to add nodes to - return ((space.type == 'NODE_EDITOR') and + return (space and (space.type == 'NODE_EDITOR') and space.edit_tree and not space.edit_tree.library) # Default execute simply adds a node @@ -195,6 +194,8 @@ class NODE_OT_add_search(NodeAddOperator, Operator): # Create an enum list from node items def node_enum_items(self, context): + import nodeitems_utils + enum_items = NODE_OT_add_search._enum_item_hack enum_items.clear() @@ -210,6 +211,8 @@ class NODE_OT_add_search(NodeAddOperator, Operator): # Look up the item based on index def find_node_item(self, context): + import nodeitems_utils + node_item = int(self.node_item) for index, item in enumerate(nodeitems_utils.node_items_iter(context)): if index == node_item: @@ -262,7 +265,7 @@ class NODE_OT_collapse_hide_unused_toggle(Operator): def poll(cls, context): space = context.space_data # needs active node editor and a tree - return ((space.type == 'NODE_EDITOR') and + return (space and (space.type == 'NODE_EDITOR') and (space.edit_tree and not space.edit_tree.library)) def execute(self, context): @@ -293,7 +296,7 @@ class NODE_OT_tree_path_parent(Operator): def poll(cls, context): space = context.space_data # needs active node editor and a tree - return (space.type == 'NODE_EDITOR' and len(space.path) > 1) + return (space and (space.type == 'NODE_EDITOR') and len(space.path) > 1) def execute(self, context): space = context.space_data @@ -302,7 +305,6 @@ class NODE_OT_tree_path_parent(Operator): return {'FINISHED'} - class NODE_OT_expose_input_socket(Operator): '''Expose socket''' bl_idname = "node.expose_input_socket" @@ -324,6 +326,65 @@ class NODE_OT_expose_input_socket(Operator): return {'FINISHED'} +class NODE_OT_active_preview_toggle(Operator): + '''Toggle active preview state of node''' + bl_idname = "node.active_preview_toggle" + bl_label = "Toggle Active Preview" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + space = context.space_data + if space is None: + return False + if space.type != 'NODE_EDITOR': + return False + if space.edit_tree is None: + return False + if space.edit_tree.nodes.active is None: + return False + return True + + def execute(self, context): + node_editor = context.space_data + ntree = node_editor.edit_tree + active_node = ntree.nodes.active + + if active_node.active_preview: + self.disable_preview(context, ntree, active_node) + else: + self.enable_preview(context, node_editor, ntree, active_node) + + return {'FINISHED'} + + def enable_preview(self, context, node_editor, ntree, active_node): + spreadsheets = self.find_unpinned_spreadsheets(context) + + for spreadsheet in spreadsheets: + spreadsheet.set_geometry_node_context(node_editor, active_node) + + for node in ntree.nodes: + node.active_preview = False + active_node.active_preview = True + + def disable_preview(self, context, ntree, active_node): + spreadsheets = self.find_unpinned_spreadsheets(context) + for spreadsheet in spreadsheets: + spreadsheet.context_path.clear() + + active_node.active_preview = False + + def find_unpinned_spreadsheets(self, context): + spreadsheets = [] + for window in context.window_manager.windows: + for area in window.screen.areas: + space = area.spaces.active + if space.type == 'SPREADSHEET' and not space.is_pinned: + spreadsheets.append(space) + return spreadsheets + + + classes = ( NodeSetting, @@ -333,4 +394,5 @@ classes = ( NODE_OT_collapse_hide_unused_toggle, NODE_OT_tree_path_parent, NODE_OT_expose_input_socket, + NODE_OT_active_preview_toggle, ) diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py index 5a388047ddd..d61bed71cab 100644 --- a/release/scripts/startup/bl_operators/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -133,7 +133,7 @@ class SelectCamera(Operator): scene = context.scene view_layer = context.view_layer view = context.space_data - if view.type == 'VIEW_3D' and view.use_local_camera: + if view and view.type == 'VIEW_3D' and view.use_local_camera: camera = view.camera else: camera = scene.camera diff --git a/release/scripts/startup/bl_operators/presets.py b/release/scripts/startup/bl_operators/presets.py index cedbe542287..3189f3b3376 100644 --- a/release/scripts/startup/bl_operators/presets.py +++ b/release/scripts/startup/bl_operators/presets.py @@ -114,9 +114,7 @@ class AddPresetBase: filename = self.as_filename(name) target_path = os.path.join("presets", self.preset_subdir) - target_path = bpy.utils.user_resource('SCRIPTS', - target_path, - create=True) + target_path = bpy.utils.user_resource('SCRIPTS', path=target_path, create=True) if not target_path: self.report({'WARNING'}, "Failed to create presets path") diff --git a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py index 6c29c07c62e..6d60c58cc3a 100644 --- a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py +++ b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py @@ -22,7 +22,6 @@ import bpy from bpy.types import Operator -import os from bpy.app.translations import pgettext_tip as tip_ @@ -62,6 +61,7 @@ class PlayRenderedAnim(Operator): bl_options = {'REGISTER'} def execute(self, context): + import os import subprocess from shlex import quote @@ -130,6 +130,7 @@ class PlayRenderedAnim(Operator): "-s", str(frame_start), "-e", str(frame_end), "-j", str(scene.frame_step), + "-c", str(prefs.system.memory_cache_limit), file, ] cmd.extend(opts) diff --git a/release/scripts/startup/bl_operators/spreadsheet.py b/release/scripts/startup/bl_operators/spreadsheet.py index 91fca883bb5..5cc83d4eddd 100644 --- a/release/scripts/startup/bl_operators/spreadsheet.py +++ b/release/scripts/startup/bl_operators/spreadsheet.py @@ -34,13 +34,45 @@ class SPREADSHEET_OT_toggle_pin(Operator): def execute(self, context): space = context.space_data - if space.pinned_id: - space.pinned_id = None + if space.is_pinned: + self.unpin(context) else: - space.pinned_id = context.active_object - + self.pin(context) return {'FINISHED'} + def pin(self, context): + space = context.space_data + space.is_pinned = True + + def unpin(self, context): + space = context.space_data + space.is_pinned = False + + space.context_path.clear() + + # Try to find a node with an active preview in any open editor. + if space.object_eval_state == 'EVALUATED': + node_editors = self.find_geometry_node_editors(context) + for node_editor in node_editors: + ntree = node_editor.edit_tree + for node in ntree.nodes: + if node.active_preview: + space.set_geometry_node_context(node_editor, node) + return + + def find_geometry_node_editors(self, context): + editors = [] + for window in context.window_manager.windows: + for area in window.screen.areas: + space = area.spaces.active + if space.type != 'NODE_EDITOR': + continue + if space.edit_tree is None: + continue + if space.edit_tree.type == 'GEOMETRY': + editors.append(space) + return editors + classes = ( SPREADSHEET_OT_toggle_pin, diff --git a/release/scripts/startup/bl_operators/userpref.py b/release/scripts/startup/bl_operators/userpref.py index 7547184dc04..623bf583a74 100644 --- a/release/scripts/startup/bl_operators/userpref.py +++ b/release/scripts/startup/bl_operators/userpref.py @@ -90,7 +90,7 @@ class PREFERENCES_OT_copy_prev(Operator): @classmethod def _old_version_path(cls, version): - return bpy.utils.resource_path('USER', version[0], version[1]) + return bpy.utils.resource_path('USER', major=version[0], minor=version[1]) @classmethod def previous_version(cls): @@ -99,6 +99,15 @@ class PREFERENCES_OT_copy_prev(Operator): version = bpy.app.version version_new = ((version[0] * 100) + version[1]) version_old = ((version[0] * 100) + version[1]) - 1 + + # Special case, remove when the version is > 3.0. + if version_new == 300: + version_new = 294 + version_old = 293 + else: + print("TODO: remove exception!") + # End special case. + # Ensure we only try to copy files from a point release. # The check below ensures the second numbers match. while (version_new % 100) // 10 == (version_old % 100) // 10: @@ -144,7 +153,7 @@ class PREFERENCES_OT_copy_prev(Operator): def execute(self, _context): import shutil - shutil.copytree(self._old_path(), self._new_path(), dirs_exist_ok=True) + shutil.copytree(self._old_path(), self._new_path(), dirs_exist_ok=True, symlinks=True) # reload preferences and recent-files.txt bpy.ops.wm.read_userpref() @@ -217,7 +226,11 @@ class PREFERENCES_OT_keyconfig_import(Operator): config_name = basename(self.filepath) - path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True) + path = bpy.utils.user_resource( + 'SCRIPTS', + path=os.path.join("presets", "keyconfig"), + create=True, + ) path = os.path.join(path, config_name) try: @@ -520,7 +533,11 @@ class PREFERENCES_OT_theme_install(Operator): xmlfile = self.filepath - path_themes = bpy.utils.user_resource('SCRIPTS', "presets/interface_theme", create=True) + path_themes = bpy.utils.user_resource( + 'SCRIPTS', + path=os.path.join("presets", "interface_theme"), + create=True, + ) if not path_themes: self.report({'ERROR'}, "Failed to get themes path") @@ -581,7 +598,7 @@ class PREFERENCES_OT_addon_install(Operator): name="Target Path", items=( ('DEFAULT', "Default", ""), - ('PREFS', "User Prefs", ""), + ('PREFS', "Preferences", ""), ), ) @@ -613,8 +630,8 @@ class PREFERENCES_OT_addon_install(Operator): pyfile = self.filepath if self.target == 'DEFAULT': - # don't use bpy.utils.script_paths("addons") because we may not be able to write to it. - path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) + # Don't use `bpy.utils.script_paths(path="addons")` because we may not be able to write to it. + path_addons = bpy.utils.user_resource('SCRIPTS', path="addons", create=True) else: path_addons = context.preferences.filepaths.script_directory if path_addons: @@ -873,7 +890,8 @@ class PREFERENCES_OT_app_template_install(Operator): filepath = self.filepath path_app_templates = bpy.utils.user_resource( - 'SCRIPTS', os.path.join("startup", "bl_app_templates_user"), + 'SCRIPTS', + path=os.path.join("startup", "bl_app_templates_user"), create=True, ) @@ -979,7 +997,7 @@ class PREFERENCES_OT_studiolight_install(Operator): prefs = context.preferences path_studiolights = os.path.join("studiolights", self.type.lower()) - path_studiolights = bpy.utils.user_resource('DATAFILES', path_studiolights, create=True) + path_studiolights = bpy.utils.user_resource('DATAFILES', path=path_studiolights, create=True) if not path_studiolights: self.report({'ERROR'}, "Failed to create Studio Light path") return {'CANCELLED'} @@ -1025,7 +1043,11 @@ class PREFERENCES_OT_studiolight_new(Operator): wm = context.window_manager filename = bpy.path.ensure_ext(self.filename, ".sl") - path_studiolights = bpy.utils.user_resource('DATAFILES', os.path.join("studiolights", "studio"), create=True) + path_studiolights = bpy.utils.user_resource( + 'DATAFILES', + path=os.path.join("studiolights", "studio"), + create=True, + ) if not path_studiolights: self.report({'ERROR'}, "Failed to get Studio Light path") return {'CANCELLED'} diff --git a/release/scripts/startup/bl_operators/uvcalc_follow_active.py b/release/scripts/startup/bl_operators/uvcalc_follow_active.py index 1b801f77e07..90131109e24 100644 --- a/release/scripts/startup/bl_operators/uvcalc_follow_active.py +++ b/release/scripts/startup/bl_operators/uvcalc_follow_active.py @@ -223,7 +223,7 @@ def extend(obj, EXTEND_MODE): for f_triple in walk_face(f_act): apply_uv(*f_triple) - bmesh.update_edit_mesh(me, False) + bmesh.update_edit_mesh(me, loop_triangles=False) return STATUS_OK diff --git a/release/scripts/startup/bl_operators/uvcalc_lightmap.py b/release/scripts/startup/bl_operators/uvcalc_lightmap.py index 29c17711c2a..6ba8750e9df 100644 --- a/release/scripts/startup/bl_operators/uvcalc_lightmap.py +++ b/release/scripts/startup/bl_operators/uvcalc_lightmap.py @@ -628,7 +628,7 @@ class LightMapPack(Operator): name="New Image", description=( "Assign new images for every mesh (only one if " - "shared tex space enabled)" + "Share Texture Space is enabled)" ), default=False, ) diff --git a/release/scripts/startup/bl_operators/view3d.py b/release/scripts/startup/bl_operators/view3d.py index ff5bcdb034f..0fa82e36d20 100644 --- a/release/scripts/startup/bl_operators/view3d.py +++ b/release/scripts/startup/bl_operators/view3d.py @@ -208,7 +208,8 @@ class VIEW3D_OT_transform_gizmo_set(Operator): @classmethod def poll(cls, context): - return context.area.type == 'VIEW_3D' + area = context.area + return area and (area.type == 'VIEW_3D') def execute(self, context): space_data = context.space_data diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 2f97942faa4..2cc7b828c11 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -95,6 +95,76 @@ def context_path_validate(context, data_path): return value +def context_path_to_rna_property(context, data_path): + from bl_rna_utils.data_path import property_definition_from_data_path + rna_prop = property_definition_from_data_path(context, "." + data_path) + if rna_prop is not None: + return rna_prop + return None + + +def context_path_decompose(data_path): + # Decompose a data_path into 3 components: + # base_path, prop_attr, prop_item, where: + # `"foo.bar["baz"].fiz().bob.buz[10][2]"`, returns... + # `("foo.bar["baz"].fiz().bob", "buz", "[10][2]")` + # + # This is useful as we often want the base and the property, ignoring any item access. + # Note that item access includes function calls since these aren't properties. + # + # Note that the `.` is removed from the start of the first and second values, + # this is done because `.attr` isn't convenient to use as an argument, + # also the convention is not to include this within the data paths or the operator logic for `bpy.ops.wm.*`. + from bl_rna_utils.data_path import decompose_data_path + path_split = decompose_data_path("." + data_path) + + # Find the last property that isn't a function call. + value_prev = "" + i = len(path_split) + while (i := i - 1) >= 0: + value = path_split[i] + if value.startswith("."): + if not value_prev.startswith("("): + break + value_prev = value + + if i != -1: + base_path = "".join(path_split[:i]) + prop_attr = path_split[i] + prop_item = "".join(path_split[i + 1:]) + + if base_path: + assert(base_path.startswith(".")) + base_path= base_path[1:] + if prop_attr: + assert(prop_attr.startswith(".")) + prop_attr = prop_attr[1:] + else: + # If there are no properties, everything is an item. + # Note that should not happen in practice with values which are added onto `context`, + # include since it's correct to account for this case and not doing so will create a confusing exception. + base_path = "" + prop_attr = "" + prop_item = "".join(path_split) + + return (base_path, prop_attr, prop_item) + + +def description_from_data_path(base, data_path, *, prefix, value=Ellipsis): + if context_path_validate(base, data_path) is Ellipsis: + return None + + if ( + (rna_prop := context_path_to_rna_property(base, data_path)) and + (description := rna_prop.description) + ): + description = "%s: %s" % (prefix, description) + if value != Ellipsis: + description = "%s\n%s: %s" % (description, iface_("Value"), str(value)) + return description + return None + + def operator_value_is_undo(value): if value in {None, Ellipsis}: return False @@ -120,12 +190,9 @@ def operator_value_is_undo(value): def operator_path_is_undo(context, data_path): - # note that if we have data paths that use strings this could fail - # luckily we don't do this! - # - # When we can't find the data owner assume no undo is needed. - data_path_head = data_path.rpartition(".")[0] + data_path_head, _, _ = context_path_decompose(data_path) + # When we can't find the data owner assume no undo is needed. if not data_path_head: return False @@ -168,6 +235,10 @@ class WM_OT_context_set_boolean(Operator): default=True, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -185,6 +256,10 @@ class WM_OT_context_set_int(Operator): # same as enum ) relative: rna_relative_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix="Assign", value=props.value) + execute = execute_context_assign @@ -201,6 +276,10 @@ class WM_OT_context_scale_float(Operator): default=1.0, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -235,6 +314,10 @@ class WM_OT_context_scale_int(Operator): options={'SKIP_SAVE'}, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -274,6 +357,10 @@ class WM_OT_context_set_float(Operator): # same as enum ) relative: rna_relative_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix="Assign", value=props.value) + execute = execute_context_assign @@ -290,6 +377,10 @@ class WM_OT_context_set_string(Operator): # same as enum maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -306,6 +397,10 @@ class WM_OT_context_set_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + execute = execute_context_assign @@ -322,6 +417,10 @@ class WM_OT_context_set_value(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value) + def execute(self, context): data_path = self.data_path if context_path_validate(context, data_path) is Ellipsis: @@ -339,6 +438,13 @@ class WM_OT_context_toggle(Operator): data_path: rna_path_prop module: rna_module_prop + @classmethod + def description(cls, context, props): + # Currently unsupported, it might be possible to extract this. + if props.module: + return None + return description_from_data_path(context, props.data_path, prefix=iface_("Toggle")) + def execute(self, context): data_path = self.data_path @@ -375,6 +481,11 @@ class WM_OT_context_toggle_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + value = "(%r, %r)" % (props.value_1, props.value_2) + return description_from_data_path(context, props.data_path, prefix=iface_("Toggle"), value=value) + def execute(self, context): data_path = self.data_path @@ -406,6 +517,10 @@ class WM_OT_context_cycle_int(Operator): reverse: rna_reverse_prop wrap: rna_wrap_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -442,6 +557,10 @@ class WM_OT_context_cycle_enum(Operator): reverse: rna_reverse_prop wrap: rna_wrap_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -450,22 +569,11 @@ class WM_OT_context_cycle_enum(Operator): orig_value = value - # Have to get rna enum values - rna_struct_str, rna_prop_str = data_path.rsplit('.', 1) - i = rna_prop_str.find('[') - - # just in case we get "context.foo.bar[0]" - if i != -1: - rna_prop_str = rna_prop_str[0:i] - - rna_struct = eval("context.%s.rna_type" % rna_struct_str) - - rna_prop = rna_struct.properties[rna_prop_str] - + rna_prop = context_path_to_rna_property(context, data_path) if type(rna_prop) != bpy.types.EnumProperty: raise Exception("expected an enum property") - enums = rna_struct.properties[rna_prop_str].enum_items.keys() + enums = rna_prop.enum_items.keys() orig_index = enums.index(orig_value) # Have the info we need, advance to the next item. @@ -498,6 +606,10 @@ class WM_OT_context_cycle_array(Operator): data_path: rna_path_prop reverse: rna_reverse_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Cycle")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -523,6 +635,10 @@ class WM_OT_context_menu_enum(Operator): data_path: rna_path_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Menu")) + def execute(self, context): data_path = self.data_path value = context_path_validate(context, data_path) @@ -530,15 +646,15 @@ class WM_OT_context_menu_enum(Operator): if value is Ellipsis: return {'PASS_THROUGH'} - base_path, prop_string = data_path.rsplit(".", 1) + base_path, prop_attr, _ = context_path_decompose(data_path) value_base = context_path_validate(context, base_path) - prop = value_base.bl_rna.properties[prop_string] + rna_prop = context_path_to_rna_property(context, data_path) def draw_cb(self, context): layout = self.layout - layout.prop(value_base, prop_string, expand=True) + layout.prop(value_base, prop_attr, expand=True) - context.window_manager.popup_menu(draw_func=draw_cb, title=prop.name, icon=prop.icon) + context.window_manager.popup_menu(draw_func=draw_cb, title=rna_prop.name, icon=rna_prop.icon) return {'FINISHED'} @@ -550,6 +666,10 @@ class WM_OT_context_pie_enum(Operator): data_path: rna_path_prop + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu")) + def invoke(self, context, event): wm = context.window_manager data_path = self.data_path @@ -558,15 +678,15 @@ class WM_OT_context_pie_enum(Operator): if value is Ellipsis: return {'PASS_THROUGH'} - base_path, prop_string = data_path.rsplit(".", 1) + base_path, prop_attr, _ = context_path_decompose(data_path) value_base = context_path_validate(context, base_path) - prop = value_base.bl_rna.properties[prop_string] + rna_prop = context_path_to_rna_property(context, data_path) def draw_cb(self, context): layout = self.layout - layout.prop(value_base, prop_string, expand=True) + layout.prop(value_base, prop_attr, expand=True) - wm.popup_menu_pie(draw_func=draw_cb, title=prop.name, icon=prop.icon, event=event) + wm.popup_menu_pie(draw_func=draw_cb, title=rna_prop.name, icon=rna_prop.icon, event=event) return {'FINISHED'} @@ -587,11 +707,15 @@ class WM_OT_operator_pie_enum(Operator): maxlen=1024, ) + @classmethod + def description(cls, context, props): + return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu")) + def invoke(self, context, event): wm = context.window_manager data_path = self.data_path - prop_string = self.prop_string + prop_attr = self.prop_string # same as eval("bpy.ops." + data_path) op_mod_str, ob_id_str = data_path.split(".", 1) @@ -607,7 +731,7 @@ class WM_OT_operator_pie_enum(Operator): def draw_cb(self, context): layout = self.layout pie = layout.menu_pie() - pie.operator_enum(data_path, prop_string) + pie.operator_enum(data_path, prop_attr) wm.popup_menu_pie(draw_func=draw_cb, title=op_rna.name, event=event) @@ -631,17 +755,17 @@ class WM_OT_context_set_id(Operator): value = self.value data_path = self.data_path - # match the pointer type from the target property to bpy.data.* + # Match the pointer type from the target property to `bpy.data.*` # so we lookup the correct list. - data_path_base, data_path_prop = data_path.rsplit(".", 1) - data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop] - data_prop_rna_type = data_prop_rna.fixed_type + + rna_prop = context_path_to_rna_property(context, data_path) + rna_prop_fixed_type = rna_prop.fixed_type id_iter = None for prop in bpy.data.rna_type.properties: if prop.rna_type.identifier == "CollectionProperty": - if prop.fixed_type == data_prop_rna_type: + if prop.fixed_type == rna_prop_fixed_type: id_iter = prop.identifier break @@ -976,7 +1100,7 @@ class WM_OT_path_open(Operator): return {'FINISHED'} -def _wm_doc_get_id(doc_id, do_url=True, url_prefix="", report=None): +def _wm_doc_get_id(doc_id, *, do_url=True, url_prefix="", report=None): def operator_exists_pair(a, b): # Not fast, this is only for docs. @@ -1066,7 +1190,7 @@ class WM_OT_doc_view_manual(Operator): doc_id: doc_id @staticmethod - def _find_reference(rna_id, url_mapping, verbose=True): + def _find_reference(rna_id, url_mapping, *, verbose=True): if verbose: print("online manual check for: '%s'... " % rna_id) from fnmatch import fnmatchcase @@ -1402,7 +1526,7 @@ class WM_OT_properties_edit(Operator): self.default = "" # setup defaults - prop_ui = rna_idprop_ui_prop_get(item, prop, False) # don't create + prop_ui = rna_idprop_ui_prop_get(item, prop, create=False) if prop_ui: self.min = prop_ui.get("min", -1000000000) self.max = prop_ui.get("max", 1000000000) @@ -1786,7 +1910,7 @@ class WM_OT_toolbar(Operator): return context.space_data is not None @staticmethod - def keymap_from_toolbar(context, space_type, use_fallback_keys=True, use_reset=True): + def keymap_from_toolbar(context, space_type, *, use_fallback_keys=True, use_reset=True): from bl_ui.space_toolsystem_common import ToolSelectPanelHelper from bl_keymap_utils import keymap_from_toolbar @@ -2087,7 +2211,7 @@ class WM_OT_batch_rename(Operator): actions: CollectionProperty(type=BatchRenameAction) @staticmethod - def _data_from_context(context, data_type, only_selected, check_context=False): + def _data_from_context(context, data_type, only_selected, *, check_context=False): mode = context.mode scene = context.scene |