diff options
Diffstat (limited to 'release')
22 files changed, 352 insertions, 88 deletions
diff --git a/release/scripts/addons b/release/scripts/addons -Subproject bcd08a9506d33bdd7358201031b04d041ef22d9 +Subproject 63492d3d0334e1827f611f8fe5a931f3ccbddfc diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py index 83bed69d8d2..387691f9f05 100644 --- a/release/scripts/modules/addon_utils.py +++ b/release/scripts/modules/addon_utils.py @@ -349,6 +349,10 @@ def enable(module_name, *, default_set=False, persistent=False, handle_error=Non # 1) try import try: mod = __import__(module_name) + if mod.__file__ is None: + # This can happen when the addon has been removed but there are + # residual `.pyc` files left behind. + raise ImportError(name=module_name) mod.__time__ = os.path.getmtime(mod.__file__) mod.__addon_enabled__ = False except Exception as ex: diff --git a/release/scripts/modules/animsys_refactor.py b/release/scripts/modules/animsys_refactor.py index 97e8a8dd144..fd4952e2a53 100644 --- a/release/scripts/modules/animsys_refactor.py +++ b/release/scripts/modules/animsys_refactor.py @@ -32,12 +32,6 @@ import bpy IS_TESTING = False -def drepr(string): - # is there a less crappy way to do this in python?, re.escape also escapes - # single quotes strings so can't use it. - return '"%s"' % repr(string)[1:-1].replace("\"", "\\\"").replace("\\'", "'") - - def classes_recursive(base_type, clss=None): if clss is None: clss = [base_type] @@ -66,7 +60,7 @@ class DataPathBuilder: if type(key) is int: str_value = '[%d]' % key elif type(key) is str: - str_value = '[%s]' % drepr(key) + str_value = '["%s"]' % bpy.utils.escape_identifier(key) else: raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key))) return DataPathBuilder(self.data_path + (str_value, )) diff --git a/release/scripts/modules/bl_rna_utils/__init__.py b/release/scripts/modules/bl_rna_utils/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/release/scripts/modules/bl_rna_utils/__init__.py diff --git a/release/scripts/modules/bl_rna_utils/data_path.py b/release/scripts/modules/bl_rna_utils/data_path.py new file mode 100644 index 00000000000..42942b7a295 --- /dev/null +++ b/release/scripts/modules/bl_rna_utils/data_path.py @@ -0,0 +1,91 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +__all__ = ( + "property_definition_from_data_path", + "decompose_data_path", +) + +class _TokenizeDataPath: + """ + Class to split up tokens of a data-path. + + Note that almost all access generates new objects with additional paths, + with the exception of iteration which is the intended way to access the resulting data.""" + __slots__ = ( + "data_path", + ) + + def __init__(self, attrs): + self.data_path = attrs + + def __getattr__(self, attr): + return _TokenizeDataPath(self.data_path + ((".%s" % attr),)) + + def __getitem__(self, key): + return _TokenizeDataPath(self.data_path + (("[%r]" % (key,)),)) + + def __call__(self, *args, **kw): + value_str = ", ".join([ + val for val in ( + ", ".join(repr(value) for value in args), + ", ".join(["%s=%r" % (key, value) for key, value in kw.items()]), + ) if val]) + return _TokenizeDataPath(self.data_path + ('(%s)' % value_str, )) + + def __iter__(self): + return iter(self.data_path) + + +def decompose_data_path(data_path): + """ + Return the components of a data path split into a list. + """ + ns = {"base": _TokenizeDataPath(())} + return list(eval("base" + data_path, ns, ns)) + + +def property_definition_from_data_path(base, data_path): + """ + Return an RNA property definition from an object and a data path. + + In Blender this is often used with ``context`` as the base and a + path that it references, for example ``.space_data.lock_camera``. + """ + data = decompose_data_path(data_path) + while data and (not data[-1].startswith(".")): + data.pop() + + if (not data) or (not data[-1].startswith(".")) or (len(data) < 2): + return None + + data_path_head = "".join(data[:-1]) + data_path_tail = data[-1] + + value_head = eval("base" + data_path_head) + value_head_rna = getattr(value_head, "bl_rna", None) + if value_head_rna is None: + return None + + value_tail = value_head.bl_rna.properties.get(data_path_tail[1:]) + if not value_tail: + return None + + return value_tail diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 2fb0e9a0bea..68e273f2244 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1694,6 +1694,7 @@ def km_image(params): ("image.view_all", {"type": 'HOME', "value": 'PRESS', "shift": True}, {"properties": [("fit_view", True)]}), ("image.view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None), + ("image.view_cursor_center", {"type": 'C', "value": 'PRESS', "shift": True}, None), ("image.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS'}, None), ("image.view_pan", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "shift": True}, None), ("image.view_pan", {"type": 'TRACKPADPAN', "value": 'ANY'}, None), diff --git a/release/scripts/startup/bl_operators/anim.py b/release/scripts/startup/bl_operators/anim.py index 8ec9bececc5..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, ) @@ -430,22 +431,9 @@ class UpdateAnimatedTransformConstraint(Operator): return {'FINISHED'} -class ANIM_OT_show_group_colors_deprecated(Operator): - """This option moved to Preferences > Animation""" - - bl_idname = "anim.show_group_colors_deprecated" - bl_label = "Show Group Colors" - bl_options = {'REGISTER'} - - @classmethod - def poll(cls, _context): - return False - - classes = ( ANIM_OT_keying_set_export, NLA_OT_bake, ClearUselessActions, UpdateAnimatedTransformConstraint, - ANIM_OT_show_group_colors_deprecated, ) diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 2f97942faa4..35826cea860 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 diff --git a/release/scripts/startup/bl_ui/properties_collection.py b/release/scripts/startup/bl_ui/properties_collection.py index 186314f9591..c5c35121135 100644 --- a/release/scripts/startup/bl_ui/properties_collection.py +++ b/release/scripts/startup/bl_ui/properties_collection.py @@ -77,6 +77,7 @@ class COLLECTION_PT_instancing(CollectionButtonsPanel, Panel): class COLLECTION_PT_lineart_collection(CollectionButtonsPanel, Panel): bl_label = "Line Art" + bl_order = 10 def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/bl_ui/properties_data_armature.py b/release/scripts/startup/bl_ui/properties_data_armature.py index 4cdcab45926..87572fcd438 100644 --- a/release/scripts/startup/bl_ui/properties_data_armature.py +++ b/release/scripts/startup/bl_ui/properties_data_armature.py @@ -86,12 +86,19 @@ class DATA_PT_display(ArmatureButtonsPanel, Panel): col = layout.column(heading="Show") col.prop(arm, "show_names", text="Names") - col.prop(arm, "show_axes", text="Axes") col.prop(arm, "show_bone_custom_shapes", text="Shapes") col.prop(arm, "show_group_colors", text="Group Colors") + if ob: col.prop(ob, "show_in_front", text="In Front") + col = layout.column(align=False, heading="Axes") + row = col.row(align=True) + row.prop(arm, "show_axes", text="") + sub = row.row(align=True) + sub.active = arm.show_axes + sub.prop(arm, "axes_position", text="Position") + class DATA_MT_bone_group_context_menu(Menu): bl_label = "Bone Group Specials" diff --git a/release/scripts/startup/bl_ui/properties_material.py b/release/scripts/startup/bl_ui/properties_material.py index d85078d4ec2..aca7ba3c5ad 100644 --- a/release/scripts/startup/bl_ui/properties_material.py +++ b/release/scripts/startup/bl_ui/properties_material.py @@ -277,6 +277,7 @@ class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel): class MATERIAL_PT_lineart(MaterialButtonsPanel, Panel): bl_label = "Line Art" bl_options = {'DEFAULT_CLOSED'} + bl_order = 10 @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py index b74100aa570..033e6196323 100644 --- a/release/scripts/startup/bl_ui/properties_object.py +++ b/release/scripts/startup/bl_ui/properties_object.py @@ -311,6 +311,7 @@ class OBJECT_PT_instancing_size(ObjectButtonsPanel, Panel): class OBJECT_PT_lineart(ObjectButtonsPanel, Panel): bl_label = "Line Art" bl_options = {'DEFAULT_CLOSED'} + bl_order = 10 @classmethod def poll(cls, context): diff --git a/release/scripts/startup/bl_ui/properties_physics_common.py b/release/scripts/startup/bl_ui/properties_physics_common.py index 82f43790d72..4ddb4953fbd 100644 --- a/release/scripts/startup/bl_ui/properties_physics_common.py +++ b/release/scripts/startup/bl_ui/properties_physics_common.py @@ -74,10 +74,6 @@ class PHYSICS_PT_add(PhysicButtonsPanel, Panel): def draw(self, context): layout = self.layout - row = layout.row(align=True) - row.alignment = 'LEFT' - row.label(text="Enable physics for:") - flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=True) obj = context.object diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index 84f83559da6..e7893b8c448 100644 --- a/release/scripts/startup/bl_ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -348,8 +348,6 @@ class DOPESHEET_MT_view(Menu): col.active = context.space_data.mode != 'SHAPEKEY' col.prop(st, "show_sliders") - if bpy.app.version < (2, 93): - layout.operator("anim.show_group_colors_deprecated", icon='CHECKBOX_HLT') layout.prop(st, "show_interpolation") layout.prop(st, "show_extremes") layout.prop(st, "use_auto_merge_keyframes") diff --git a/release/scripts/startup/bl_ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py index 9ba3bd8f8cc..f8521592dd9 100644 --- a/release/scripts/startup/bl_ui/space_graph.py +++ b/release/scripts/startup/bl_ui/space_graph.py @@ -18,7 +18,6 @@ # <pep8 compliant> -import bpy from bpy.types import Header, Menu, Panel from bl_ui.space_dopesheet import ( DopesheetFilterPopoverBase, @@ -120,10 +119,6 @@ class GRAPH_MT_view(Menu): layout.prop(st, "use_realtime_update") layout.prop(st, "show_cursor") layout.prop(st, "show_sliders") - - if bpy.app.version < (2, 93): - layout.operator("anim.show_group_colors_deprecated", icon='CHECKBOX_HLT') - layout.prop(st, "use_auto_merge_keyframes") if st.mode != 'DRIVERS': diff --git a/release/scripts/startup/bl_ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index a85dd70e8f4..d8cfa9dcc82 100644 --- a/release/scripts/startup/bl_ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -481,7 +481,7 @@ class NODE_MT_context_menu(Menu): class NODE_PT_active_node_generic(Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' - bl_category = "Item" + bl_category = "Node" bl_label = "Node" @classmethod @@ -499,7 +499,7 @@ class NODE_PT_active_node_generic(Panel): class NODE_PT_active_node_color(Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' - bl_category = "Item" + bl_category = "Node" bl_label = "Color" bl_options = {'DEFAULT_CLOSED'} bl_parent_id = 'NODE_PT_active_node_generic' @@ -529,7 +529,7 @@ class NODE_PT_active_node_color(Panel): class NODE_PT_active_node_properties(Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' - bl_category = "Item" + bl_category = "Node" bl_label = "Properties" bl_options = {'DEFAULT_CLOSED'} @@ -570,7 +570,7 @@ class NODE_PT_active_node_properties(Panel): class NODE_PT_texture_mapping(Panel): bl_space_type = 'NODE_EDITOR' bl_region_type = 'UI' - bl_category = "Item" + bl_category = "Node" bl_label = "Texture Mapping" bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py index 7219922c379..adab0b0c88a 100644 --- a/release/scripts/startup/bl_ui/space_topbar.py +++ b/release/scripts/startup/bl_ui/space_topbar.py @@ -469,6 +469,8 @@ class TOPBAR_MT_file_import(Menu): if bpy.app.build_options.alembic: self.layout.operator("wm.alembic_import", text="Alembic (.abc)") + self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil") + class TOPBAR_MT_file_export(Menu): bl_idname = "TOPBAR_MT_file_export" @@ -485,6 +487,13 @@ class TOPBAR_MT_file_export(Menu): self.layout.operator( "wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)") + # Pugixml lib dependency + if bpy.app.build_options.pugixml: + self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG") + # Haru lib dependency + if bpy.app.build_options.haru: + self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF") + class TOPBAR_MT_file_external_data(Menu): bl_label = "External Data" diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 96695ff1be5..f44cf23fb58 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2244,6 +2244,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_switch_object_operator"}, "T80402"), ({"property": "use_sculpt_tools_tilt"}, "T82877"), ({"property": "use_asset_browser"}, ("project/profile/124/", "Milestone 1")), + ({"property": "use_override_templates"}, ("T73318", "Milestone 4")), ), ) diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 72baa5331bf..831fc06eda5 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -622,9 +622,15 @@ class VIEW3D_PT_tools_brush_texture(Panel, View3DPaintPanel): @classmethod def poll(cls, context): - settings = cls.paint_settings(context) - return (settings and settings.brush and - (context.sculpt_object or context.image_paint_object or context.vertex_paint_object)) + if ( + (settings := cls.paint_settings(context)) and + (brush := settings.brush) + ): + if context.sculpt_object or context.vertex_paint_object: + return True + elif context.image_paint_object: + return (brush.image_tool == 'DRAW') + return False def draw(self, context): layout = self.layout diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index febb31af188..7e887caf3f2 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -368,6 +368,7 @@ compositor_node_categories = [ NodeItem("CompositorNodePixelate"), NodeItem("CompositorNodeSunBeams"), NodeItem("CompositorNodeDenoise"), + NodeItem("CompositorNodeAntiAliasing"), ]), CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[ NodeItem("CompositorNodeNormal"), @@ -484,6 +485,7 @@ geometry_node_categories = [ GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[ NodeItem("GeometryNodeAttributeRandomize"), NodeItem("GeometryNodeAttributeMath"), + NodeItem("GeometryNodeAttributeClamp"), NodeItem("GeometryNodeAttributeCompare"), NodeItem("GeometryNodeAttributeConvert"), NodeItem("GeometryNodeAttributeFill"), @@ -495,6 +497,7 @@ geometry_node_categories = [ NodeItem("GeometryNodeAttributeCombineXYZ"), NodeItem("GeometryNodeAttributeSeparateXYZ"), NodeItem("GeometryNodeAttributeRemove"), + NodeItem("GeometryNodeAttributeMapRange"), ]), GeometryNodeCategory("GEO_COLOR", "Color", items=[ NodeItem("ShaderNodeValToRGB"), @@ -520,7 +523,16 @@ geometry_node_categories = [ NodeItem("GeometryNodeEdgeSplit"), NodeItem("GeometryNodeSubdivisionSurface"), NodeItem("GeometryNodeSubdivide"), - + ]), + GeometryNodeCategory("GEO_PRIMITIVES", "Mesh Primitives", items=[ + NodeItem("GeometryNodeMeshCircle"), + NodeItem("GeometryNodeMeshCone"), + NodeItem("GeometryNodeMeshCube"), + NodeItem("GeometryNodeMeshCylinder"), + NodeItem("GeometryNodeMeshGrid"), + NodeItem("GeometryNodeMeshIcoSphere"), + NodeItem("GeometryNodeMeshLine"), + NodeItem("GeometryNodeMeshUVSphere"), ]), GeometryNodeCategory("GEO_POINT", "Point", items=[ NodeItem("GeometryNodePointDistribute"), @@ -531,20 +543,6 @@ geometry_node_categories = [ NodeItem("GeometryNodeRotatePoints"), NodeItem("GeometryNodeAlignRotationToVector"), ]), - GeometryNodeCategory("GEO_VOLUME", "Volume", items=[ - NodeItem("GeometryNodePointsToVolume"), - NodeItem("GeometryNodeVolumeToMesh"), - ]), - GeometryNodeCategory("GEO_PRIMITIVES", "Mesh Primitives", items=[ - NodeItem("GeometryNodeMeshCube"), - NodeItem("GeometryNodeMeshCircle"), - NodeItem("GeometryNodeMeshUVSphere"), - NodeItem("GeometryNodeMeshIcoSphere"), - NodeItem("GeometryNodeMeshCylinder"), - NodeItem("GeometryNodeMeshCone"), - NodeItem("GeometryNodeMeshLine"), - NodeItem("GeometryNodeMeshPlane"), - ]), GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[ NodeItem("ShaderNodeMapRange"), NodeItem("ShaderNodeClamp"), @@ -558,6 +556,10 @@ geometry_node_categories = [ NodeItem("ShaderNodeVectorMath"), NodeItem("ShaderNodeVectorRotate"), ]), + GeometryNodeCategory("GEO_VOLUME", "Volume", items=[ + NodeItem("GeometryNodePointsToVolume"), + NodeItem("GeometryNodeVolumeToMesh"), + ]), GeometryNodeCategory("GEO_GROUP", "Group", items=node_group_items), GeometryNodeCategory("GEO_LAYOUT", "Layout", items=[ NodeItem("NodeFrame"), diff --git a/release/scripts/templates_osl/basic_shader.osl b/release/scripts/templates_osl/basic_shader.osl new file mode 100644 index 00000000000..3c5240a1bbd --- /dev/null +++ b/release/scripts/templates_osl/basic_shader.osl @@ -0,0 +1,10 @@ +shader basic_shader( + float in_float = 1.0, + color in_color = color(1.0, 1.0, 1.0), + output float out_float = 0.0, + output color out_color = color(0.0, 0.0, 0.0) + ) +{ + out_float = in_float * 2.0; + out_color = in_color * 2.0; +} diff --git a/release/scripts/templates_py/image_processing.py b/release/scripts/templates_py/image_processing.py new file mode 100644 index 00000000000..2392faf440c --- /dev/null +++ b/release/scripts/templates_py/image_processing.py @@ -0,0 +1,35 @@ +# This sample shows the an efficient way of doing image processing +# over Blender's images using Python. + +import bpy +import numpy as np + + +input_image_name = "Image" +output_image_name = "NewImage" + +# Retrieve input image. +input_image = bpy.data.images[input_image_name] +w, h = input_image.size + +# Allocate a numpy array to manipulate pixel data. +pixel_data = np.zeros((w, h, 4), 'f') + +# Fast copy of pixel data from bpy.data to numpy array. +input_image.pixels.foreach_get(pixel_data.ravel()) + +# Do whatever image processing you want using numpy here: +# Example 1: Inverse red green and blue channels. +pixel_data[:,:,:3] = 1.0 - pixel_data[:,:,:3] +# Example 2: Change gamma on the red channel. +pixel_data[:,:,0] = np.power(pixel_data[:,:,0], 1.5) + +# Create output image. +if output_image_name in bpy.data.images: + output_image = bpy.data.images[output_image_name] +else: + output_image = bpy.data.images.new(output_image_name, width=w, height=h) + +# Copy of pixel data from numpy array back to the output image. +output_image.pixels.foreach_set(pixel_data.ravel()) +output_image.update() |