UI: Custom Tooltips with Optional Images #105905
|
@ -573,6 +573,12 @@ bool BVHMetal::build_BLAS_hair(Progress &progress,
|
|||
|
||||
return true;
|
||||
}
|
||||
# else /* MAC_OS_VERSION_14_0 */
|
||||
(void)progress;
|
||||
(void)device;
|
||||
(void)queue;
|
||||
(void)geom;
|
||||
(void)(refit);
|
||||
# endif /* MAC_OS_VERSION_14_0 */
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1108,10 +1108,9 @@ void GHOST_SystemX11::processEvent(XEvent *xe)
|
|||
}
|
||||
|
||||
if (ELEM(status, XLookupChars, XLookupBoth)) {
|
||||
if (uchar(utf8_buf[0]) >= 32) { /* not an ascii control character */
|
||||
/* do nothing for now, this is valid utf8 */
|
||||
}
|
||||
else {
|
||||
/* Check for ASCII control characters.
|
||||
* Inline `iscntrl` because the users locale must not change behavior. */
|
||||
if ((utf8_buf[0] < 32 && utf8_buf[0] > 0) || (utf8_buf[0] == 127)) {
|
||||
utf8_buf[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ const bTheme U_theme_default = {
|
|||
.outline = RGBA(0x3d3d3dff),
|
||||
.inner = RGBA(0x1d1d1dff),
|
||||
.inner_sel = RGBA(0x181818ff),
|
||||
.item = RGBA(0x4772b3ff),
|
||||
.item = RGBA(0xffffff33),
|
||||
.text = RGBA(0xe6e6e6ff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.roundness = 0.2f,
|
||||
|
@ -191,7 +191,7 @@ const bTheme U_theme_default = {
|
|||
.outline = RGBA(0x2d2d2dff),
|
||||
.inner = RGBA(0xffffff00),
|
||||
.inner_sel = RGBA(0x4772b3ff),
|
||||
.item = RGBA(0x4772b3ff),
|
||||
.item = RGBA(0xffffff33),
|
||||
.text = RGBA(0xccccccff),
|
||||
.text_sel = RGBA(0xffffffff),
|
||||
.roundness = 0.2f,
|
||||
|
@ -758,8 +758,8 @@ const bTheme U_theme_default = {
|
|||
.back = RGBA(0x3d3d3dff),
|
||||
.sub_back = RGBA(0x0000001f),
|
||||
},
|
||||
.shade2 = RGBA(0x2d4366e6),
|
||||
.hilite = RGBA(0xff0000ff),
|
||||
.shade2 = RGBA(0x4d4d4de6),
|
||||
.hilite = RGBA(0x65a2ffff),
|
||||
.grid = RGBA(0x1d1d1dff),
|
||||
.vertex_size = 3,
|
||||
.outline_width = 1,
|
||||
|
@ -767,7 +767,7 @@ const bTheme U_theme_default = {
|
|||
.syntaxl = RGBA(0xe6d573ff),
|
||||
.syntaxs = RGBA(0xff734dff),
|
||||
.syntaxb = RGBA(0xe62e67ff),
|
||||
.syntaxn = RGBA(0x48c5e6ff),
|
||||
.syntaxn = RGBA(0x48d9e6ff),
|
||||
.syntaxv = RGBA(0x689d06ff),
|
||||
.syntaxc = RGBA(0x939393ff),
|
||||
.syntaxd = RGBA(0x9c73e6ff),
|
||||
|
|
|
@ -369,10 +369,11 @@ def enable(module_name, *, default_set=False, persistent=False, handle_error=Non
|
|||
mod.__time__ = os.path.getmtime(mod.__file__)
|
||||
mod.__addon_enabled__ = False
|
||||
except BaseException as ex:
|
||||
# if the addon doesn't exist, don't print full traceback
|
||||
if type(ex) is ImportError and ex.name == module_name:
|
||||
print("addon not loaded:", repr(module_name))
|
||||
print("cause:", str(ex))
|
||||
# If the add-on doesn't exist, don't print full trace-back because the back-trace is in this case
|
||||
# is verbose without any useful details. A missing path is better communicated in a short message.
|
||||
# Account for `ImportError` & `ModuleNotFoundError`.
|
||||
if isinstance(ex, ImportError) and ex.name == module_name:
|
||||
print("Add-on not loaded:", repr(module_name), "cause:", str(ex))
|
||||
else:
|
||||
handle_error(ex)
|
||||
|
||||
|
|
|
@ -345,15 +345,15 @@ def load_scripts_extensions(*, reload_scripts=False):
|
|||
bl_app_template_utils.reset(reload_scripts=reload_scripts)
|
||||
del bl_app_template_utils
|
||||
|
||||
# deal with addons separately
|
||||
_initialize = getattr(_addon_utils, "_initialize", None)
|
||||
if _initialize is not None:
|
||||
# Deal with add-ons separately.
|
||||
_initialize_once = getattr(_addon_utils, "_initialize_once", None)
|
||||
if _initialize_once is not None:
|
||||
# first time, use fast-path
|
||||
_initialize()
|
||||
del _addon_utils._initialize
|
||||
_initialize_once()
|
||||
del _addon_utils._initialize_once
|
||||
else:
|
||||
_addon_utils.reset_all(reload_scripts=reload_scripts)
|
||||
del _initialize
|
||||
del _initialize_once
|
||||
|
||||
|
||||
def script_path_user():
|
||||
|
|
|
@ -7,9 +7,29 @@ import bpy
|
|||
|
||||
language_id = "python"
|
||||
|
||||
# store our own __main__ module, not 100% needed
|
||||
# but python expects this in some places
|
||||
_BPY_MAIN_OWN = True
|
||||
|
||||
class _TempModuleOverride:
|
||||
__slots__ = (
|
||||
"module_name",
|
||||
"module",
|
||||
"module_override",
|
||||
)
|
||||
|
||||
def __init__(self, module_name, module_override):
|
||||
self.module_name = module_name
|
||||
self.module = None
|
||||
self.module_override = module_override
|
||||
|
||||
def __enter__(self):
|
||||
self.module = sys.modules.get(self.module_name)
|
||||
sys.modules[self.module_name] = self.module_override
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if self.module is None:
|
||||
# Account for removal of `module_override` (albeit unlikely).
|
||||
sys.modules.pop(self.module_name, None)
|
||||
else:
|
||||
sys.modules[self.module_name] = self.module
|
||||
|
||||
|
||||
def add_scrollback(text, text_type):
|
||||
|
@ -72,12 +92,9 @@ def get_console(console_id):
|
|||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
else:
|
||||
if _BPY_MAIN_OWN:
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
else:
|
||||
namespace = {}
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
|
||||
namespace["__builtins__"] = sys.modules["builtins"]
|
||||
namespace["bpy"] = bpy
|
||||
|
@ -94,8 +111,7 @@ def get_console(console_id):
|
|||
console.push("from mathutils import *")
|
||||
console.push("from math import *")
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
|
@ -121,25 +137,23 @@ def execute(context, is_interactive):
|
|||
|
||||
console, stdout, stderr = get_console(hash(context.region))
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
# redirect output
|
||||
from contextlib import (
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
|
||||
# not included with Python
|
||||
# Not included with Python.
|
||||
class redirect_stdin(redirect_stdout.__base__):
|
||||
_stream = "stdin"
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
with redirect_stdout(stdout), \
|
||||
redirect_stderr(stderr), \
|
||||
redirect_stdin(None):
|
||||
|
||||
with (
|
||||
redirect_stdout(stdout),
|
||||
redirect_stderr(stderr),
|
||||
# Don't allow the `stdin` to be used because it can lock Blender.
|
||||
redirect_stdin(None),
|
||||
_TempModuleOverride("__main__", console._bpy_main_mod),
|
||||
):
|
||||
# in case exception happens
|
||||
line = "" # in case of encoding error
|
||||
is_multiline = False
|
||||
|
@ -156,9 +170,6 @@ def execute(context, is_interactive):
|
|||
import traceback
|
||||
stderr.write(traceback.format_exc())
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
output = stdout.getvalue()
|
||||
output_err = stderr.getvalue()
|
||||
|
||||
|
@ -220,46 +231,48 @@ def autocomplete(context):
|
|||
if not console:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
# note: unlikely stdin would be used for autocomplete. but its possible.
|
||||
stdin_backup = sys.stdin
|
||||
sys.stdin = None
|
||||
|
||||
scrollback = ""
|
||||
scrollback_error = ""
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
# Don't allow the `stdin` to be used, can lock blender.
|
||||
# note: unlikely `stdin` would be used for auto-complete - but it's possible.
|
||||
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
# This function isn't aware of the text editor or being an operator
|
||||
# just does the autocomplete then copy its results back
|
||||
result = intellisense.expand(
|
||||
line=line,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug_python)
|
||||
# Not included with Python.
|
||||
class redirect_stdin(redirect_stdout.__base__):
|
||||
_stream = "stdin"
|
||||
|
||||
line_new = result[0]
|
||||
current_line.body, current_line.current_character, scrollback = result
|
||||
del result
|
||||
with (
|
||||
# Don't allow the `stdin` to be used because it can lock Blender.
|
||||
redirect_stdin(None),
|
||||
_TempModuleOverride("__main__", console._bpy_main_mod),
|
||||
):
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
|
||||
# update selection. setting body should really do this!
|
||||
ofs = len(line_new) - len(line)
|
||||
sc.select_start += ofs
|
||||
sc.select_end += ofs
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the api attribute access itself causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
# This function isn't aware of the text editor or being an operator
|
||||
# just does the autocomplete then copy its results back
|
||||
result = intellisense.expand(
|
||||
line=line,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug_python)
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
line_new = result[0]
|
||||
current_line.body, current_line.current_character, scrollback = result
|
||||
del result
|
||||
|
||||
# update selection. setting body should really do this!
|
||||
ofs = len(line_new) - len(line)
|
||||
sc.select_start += ofs
|
||||
sc.select_end += ofs
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the API attribute access itself causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
|
||||
# Separate autocomplete output by command prompts
|
||||
if scrollback != '':
|
||||
|
@ -275,9 +288,6 @@ def autocomplete(context):
|
|||
if scrollback_error:
|
||||
add_scrollback(scrollback_error, 'ERROR')
|
||||
|
||||
# restore the stdin
|
||||
sys.stdin = stdin_backup
|
||||
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
outline="#4d4d4dff"
|
||||
inner="#282828ff"
|
||||
inner_sel="#333333ff"
|
||||
item="#8aace6ff"
|
||||
item="#ffffff33"
|
||||
text="#dddddd"
|
||||
text_sel="#ffffff"
|
||||
show_shaded="TRUE"
|
||||
|
@ -306,7 +306,7 @@
|
|||
outline="#e6e6e6ff"
|
||||
inner="#1a1a1a00"
|
||||
inner_sel="#668cccff"
|
||||
item="#8aace6ff"
|
||||
item="#ffffff33"
|
||||
text="#1a1a1a"
|
||||
text_sel="#ffffff"
|
||||
show_shaded="FALSE"
|
||||
|
@ -918,7 +918,7 @@
|
|||
line_numbers="#d0d0d0"
|
||||
line_numbers_background="#313133"
|
||||
selected_text="#19191a"
|
||||
cursor="#ff0000"
|
||||
cursor="#71a8ff"
|
||||
syntax_builtin="#ff1961"
|
||||
syntax_symbols="#ff734d"
|
||||
syntax_special="#95d600"
|
||||
|
@ -926,7 +926,7 @@
|
|||
syntax_reserved="#c4753b"
|
||||
syntax_comment="#939393"
|
||||
syntax_string="#f6e162"
|
||||
syntax_numbers="#50dbff"
|
||||
syntax_numbers="#50faff"
|
||||
>
|
||||
<space>
|
||||
<ThemeSpaceGeneric
|
||||
|
|
|
@ -17,10 +17,13 @@ class ASSETSHELF_PT_display(Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False # No animation.
|
||||
|
||||
shelf = context.asset_shelf
|
||||
|
||||
layout.prop(shelf, "preview_size", text="Size")
|
||||
layout.prop(shelf, "show_names", text="Names")
|
||||
layout.prop(shelf, "preview_size")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
|
|
|
@ -89,6 +89,16 @@ class DATA_UL_bone_collections(UIList):
|
|||
|
||||
layout.prop(bcoll, "name", text="", emboss=False,
|
||||
icon='DOT' if has_active_bone else 'BLANK1')
|
||||
|
||||
if armature.override_library:
|
||||
icon = 'LIBRARY_DATA_OVERRIDE' if bcoll.is_local_override else 'BLANK1'
|
||||
layout.prop(
|
||||
bcoll,
|
||||
"is_local_override",
|
||||
text="",
|
||||
emboss=False,
|
||||
icon=icon)
|
||||
|
||||
layout.prop(bcoll, "is_visible", text="", emboss=False,
|
||||
icon='HIDE_OFF' if bcoll.is_visible else 'HIDE_ON')
|
||||
|
||||
|
@ -245,7 +255,17 @@ class DATA_PT_custom_props_bcoll(ArmatureButtonsPanel, PropertyPanel, Panel):
|
|||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.armature and context.armature.collections.active
|
||||
arm = context.armature
|
||||
if not arm:
|
||||
return False
|
||||
|
||||
is_lib_override = arm.id_data.override_library and arm.id_data.override_library.reference
|
||||
if is_lib_override:
|
||||
# This is due to a limitation in scripts/modules/rna_prop_ui.py; if that
|
||||
# limitation is lifted, this poll function should be adjusted.
|
||||
return False
|
||||
|
||||
return arm.collections.active
|
||||
|
||||
|
||||
classes = (
|
||||
|
|
|
@ -368,7 +368,8 @@ class _draw_tool_settings_context_mode:
|
|||
elif not tool.has_datablock:
|
||||
return False
|
||||
|
||||
paint = context.tool_settings.gpencil_paint
|
||||
tool_settings = context.tool_settings
|
||||
paint = tool_settings.gpencil_paint
|
||||
brush = paint.brush
|
||||
if brush is None:
|
||||
return False
|
||||
|
@ -376,7 +377,6 @@ class _draw_tool_settings_context_mode:
|
|||
gp_settings = brush.gpencil_settings
|
||||
|
||||
row = layout.row(align=True)
|
||||
tool_settings = context.scene.tool_settings
|
||||
settings = tool_settings.gpencil_paint
|
||||
row.template_ID_preview(settings, "brush", rows=3, cols=8, hide_buttons=True)
|
||||
|
||||
|
@ -401,7 +401,9 @@ class _draw_tool_settings_context_mode:
|
|||
def SCULPT_GPENCIL(context, layout, tool):
|
||||
if (tool is None) or (not tool.has_datablock):
|
||||
return False
|
||||
paint = context.tool_settings.gpencil_sculpt_paint
|
||||
|
||||
tool_settings = context.tool_settings
|
||||
paint = tool_settings.gpencil_sculpt_paint
|
||||
brush = paint.brush
|
||||
|
||||
from bl_ui.properties_paint_common import (
|
||||
|
@ -415,7 +417,9 @@ class _draw_tool_settings_context_mode:
|
|||
def WEIGHT_GPENCIL(context, layout, tool):
|
||||
if (tool is None) or (not tool.has_datablock):
|
||||
return False
|
||||
paint = context.tool_settings.gpencil_weight_paint
|
||||
|
||||
tool_settings = context.tool_settings
|
||||
paint = tool_settings.gpencil_weight_paint
|
||||
brush = paint.brush
|
||||
|
||||
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
|
||||
|
@ -432,11 +436,11 @@ class _draw_tool_settings_context_mode:
|
|||
if (tool is None) or (not tool.has_datablock):
|
||||
return False
|
||||
|
||||
paint = context.tool_settings.gpencil_vertex_paint
|
||||
tool_settings = context.tool_settings
|
||||
paint = tool_settings.gpencil_vertex_paint
|
||||
brush = paint.brush
|
||||
|
||||
row = layout.row(align=True)
|
||||
tool_settings = context.scene.tool_settings
|
||||
settings = tool_settings.gpencil_vertex_paint
|
||||
row.template_ID_preview(settings, "brush", rows=3, cols=8, hide_buttons=True)
|
||||
|
||||
|
@ -458,7 +462,8 @@ class _draw_tool_settings_context_mode:
|
|||
return False
|
||||
|
||||
# See: 'VIEW3D_PT_tools_brush', basically a duplicate
|
||||
settings = context.tool_settings.particle_edit
|
||||
tool_settings = context.tool_settings
|
||||
settings = tool_settings.particle_edit
|
||||
brush = settings.brush
|
||||
tool = settings.tool
|
||||
if tool == 'NONE':
|
||||
|
@ -696,7 +701,7 @@ class VIEW3D_HT_header(Header):
|
|||
|
||||
row = layout.row(align=True)
|
||||
obj = context.active_object
|
||||
# mode_string = context.mode
|
||||
mode_string = context.mode
|
||||
object_mode = 'OBJECT' if obj is None else obj.mode
|
||||
has_pose_mode = (
|
||||
(object_mode == 'POSE') or
|
||||
|
@ -959,31 +964,29 @@ class VIEW3D_HT_header(Header):
|
|||
sub.active = overlay.show_overlays
|
||||
sub.popover(panel="VIEW3D_PT_overlay", text="")
|
||||
|
||||
show_bone_overlays = (
|
||||
(object_mode == 'POSE') or
|
||||
(object_mode == 'PAINT_WEIGHT' and context.pose_object) or
|
||||
(object_mode in {'EDIT_ARMATURE', 'OBJECT'} and
|
||||
VIEW3D_PT_overlay_bones.is_using_wireframe(context))
|
||||
)
|
||||
if mode_string == 'EDIT_MESH':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_edit_mesh", text="", icon='EDITMODE_HLT')
|
||||
if mode_string == 'EDIT_CURVE':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_edit_curve", text="", icon='EDITMODE_HLT')
|
||||
elif mode_string == 'SCULPT':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_sculpt", text="", icon='SCULPTMODE_HLT')
|
||||
elif mode_string == 'SCULPT_CURVES':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_sculpt_curves", text="", icon='SCULPTMODE_HLT')
|
||||
elif mode_string == 'PAINT_WEIGHT':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_weight_paint", text="", icon='WPAINT_HLT')
|
||||
elif mode_string == 'PAINT_TEXTURE':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_texture_paint", text="", icon='TPAINT_HLT')
|
||||
elif mode_string == 'PAINT_VERTEX':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_vertex_paint", text="", icon='VPAINT_HLT')
|
||||
elif obj is not None and obj.type == 'GPENCIL':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_gpencil_options", text="", icon='OUTLINER_DATA_GREASEPENCIL')
|
||||
|
||||
if show_bone_overlays:
|
||||
sub.popover(panel="VIEW3D_PT_overlay_bones", text="", icon="POSE_HLT")
|
||||
elif context.mode == 'EDIT_MESH':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_edit_mesh", text="", icon="EDITMODE_HLT")
|
||||
if context.mode == 'EDIT_CURVE':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_edit_curve", text="", icon="EDITMODE_HLT")
|
||||
elif context.mode == 'SCULPT' and context.sculpt_object:
|
||||
sub.popover(panel="VIEW3D_PT_overlay_sculpt", text="", icon="SCULPTMODE_HLT")
|
||||
elif context.mode == 'SCULPT_CURVES' and context.object:
|
||||
sub.popover(panel="VIEW3D_PT_overlay_sculpt_curves", text="", icon="SCULPTMODE_HLT")
|
||||
elif context.mode == 'PAINT_WEIGHT':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_weight_paint", text="", icon="WPAINT_HLT")
|
||||
elif context.mode == 'PAINT_TEXTURE':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_texture_paint", text="", icon="TPAINT_HLT")
|
||||
elif context.mode == 'PAINT_VERTEX':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_vertex_paint", text="", icon="VPAINT_HLT")
|
||||
elif context.object and context.object.type == 'GPENCIL':
|
||||
sub.popover(panel="VIEW3D_PT_overlay_gpencil_options", text="", icon="OUTLINER_DATA_GREASEPENCIL")
|
||||
# Separate from `elif` chain because it may coexist with weight-paint.
|
||||
if (
|
||||
has_pose_mode or
|
||||
(object_mode in {'EDIT_ARMATURE', 'OBJECT'} and VIEW3D_PT_overlay_bones.is_using_wireframe(context))
|
||||
):
|
||||
sub.popover(panel="VIEW3D_PT_overlay_bones", text="", icon='POSE_HLT')
|
||||
|
||||
row = layout.row()
|
||||
row.active = (object_mode == 'EDIT') or (shading.type in {'WIREFRAME', 'SOLID'})
|
||||
|
@ -1023,7 +1026,7 @@ class VIEW3D_MT_editor_menus(Menu):
|
|||
edit_object = context.edit_object
|
||||
gp_edit = obj and obj.mode in {'EDIT_GPENCIL', 'PAINT_GPENCIL', 'SCULPT_GPENCIL',
|
||||
'WEIGHT_GPENCIL', 'VERTEX_GPENCIL'}
|
||||
tool_settings = context.scene.tool_settings
|
||||
tool_settings = context.tool_settings
|
||||
|
||||
layout.menu("VIEW3D_MT_view")
|
||||
|
||||
|
@ -5520,7 +5523,9 @@ class VIEW3D_MT_edit_gpencil_stroke(Menu):
|
|||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
settings = context.tool_settings.gpencil_sculpt
|
||||
|
||||
tool_settings = context.tool_settings
|
||||
settings = tool_settings.gpencil_sculpt
|
||||
|
||||
layout.operator("gpencil.stroke_subdivide", text="Subdivide").only_selected = False
|
||||
layout.menu("VIEW3D_MT_gpencil_simplify")
|
||||
|
@ -5797,18 +5802,20 @@ class VIEW3D_MT_pivot_pie(Menu):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
|
||||
tool_settings = context.tool_settings
|
||||
obj = context.active_object
|
||||
mode = context.mode
|
||||
|
||||
pie.prop_enum(context.scene.tool_settings, "transform_pivot_point", value='BOUNDING_BOX_CENTER')
|
||||
pie.prop_enum(context.scene.tool_settings, "transform_pivot_point", value='CURSOR')
|
||||
pie.prop_enum(context.scene.tool_settings, "transform_pivot_point", value='INDIVIDUAL_ORIGINS')
|
||||
pie.prop_enum(context.scene.tool_settings, "transform_pivot_point", value='MEDIAN_POINT')
|
||||
pie.prop_enum(context.scene.tool_settings, "transform_pivot_point", value='ACTIVE_ELEMENT')
|
||||
pie.prop_enum(tool_settings, "transform_pivot_point", value='BOUNDING_BOX_CENTER')
|
||||
pie.prop_enum(tool_settings, "transform_pivot_point", value='CURSOR')
|
||||
pie.prop_enum(tool_settings, "transform_pivot_point", value='INDIVIDUAL_ORIGINS')
|
||||
pie.prop_enum(tool_settings, "transform_pivot_point", value='MEDIAN_POINT')
|
||||
pie.prop_enum(tool_settings, "transform_pivot_point", value='ACTIVE_ELEMENT')
|
||||
if (obj is None) or (mode in {'OBJECT', 'POSE', 'WEIGHT_PAINT'}):
|
||||
pie.prop(context.scene.tool_settings, "use_transform_pivot_point_align")
|
||||
pie.prop(tool_settings, "use_transform_pivot_point_align")
|
||||
if mode == 'EDIT_GPENCIL':
|
||||
pie.prop(context.scene.tool_settings.gpencil_sculpt, "use_scale_thickness")
|
||||
pie.prop(tool_settings.gpencil_sculpt, "use_scale_thickness")
|
||||
|
||||
|
||||
class VIEW3D_MT_orientations_pie(Menu):
|
||||
|
@ -5853,6 +5860,7 @@ class VIEW3D_MT_proportional_editing_falloff_pie(Menu):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
|
||||
tool_settings = context.scene.tool_settings
|
||||
|
||||
pie.prop(tool_settings, "proportional_edit_falloff", expand=True)
|
||||
|
@ -7104,10 +7112,7 @@ class VIEW3D_PT_overlay_sculpt(Panel):
|
|||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return (
|
||||
context.mode == 'SCULPT' and
|
||||
context.sculpt_object
|
||||
)
|
||||
return context.mode == 'SCULPT'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
@ -7138,7 +7143,7 @@ class VIEW3D_PT_overlay_sculpt_curves(Panel):
|
|||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'SCULPT_CURVES' and (context.object)
|
||||
return context.mode == 'SCULPT_CURVES'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
@ -8428,7 +8433,7 @@ class VIEW3D_AST_sculpt_brushes(bpy.types.AssetShelf):
|
|||
|
||||
@classmethod
|
||||
def asset_poll(cls, asset):
|
||||
return asset.file_data.id_type == 'BRUSH'
|
||||
return asset.id_type == 'BRUSH'
|
||||
|
||||
|
||||
classes = (
|
||||
|
|
|
@ -11,7 +11,7 @@ class MyAssetShelf(bpy.types.AssetShelf):
|
|||
|
||||
@classmethod
|
||||
def asset_poll(cls, asset):
|
||||
return asset.file_data.id_type in {'MATERIAL', 'OBJECT'}
|
||||
return asset.id_type in {'MATERIAL', 'OBJECT'}
|
||||
|
||||
|
||||
def register():
|
||||
|
|
|
@ -54,9 +54,6 @@ void ANIM_bonecoll_free(struct BoneCollection *bcoll);
|
|||
/**
|
||||
* Recalculate the armature & bone runtime data.
|
||||
*
|
||||
* NOTE: this should only be used when the runtime structs on the Armature and Bones are still
|
||||
* empty. Any data allocated there will NOT be freed.
|
||||
*
|
||||
* TODO: move to BKE?
|
||||
*/
|
||||
void ANIM_armature_runtime_refresh(struct bArmature *armature);
|
||||
|
@ -74,6 +71,17 @@ void ANIM_armature_runtime_free(struct bArmature *armature);
|
|||
*/
|
||||
struct BoneCollection *ANIM_armature_bonecoll_new(struct bArmature *armature, const char *name);
|
||||
|
||||
/**
|
||||
* Add a bone collection to the Armature.
|
||||
*
|
||||
* NOTE: this should not typically be used. It is only used by the library overrides system to
|
||||
* apply override operations.
|
||||
*/
|
||||
struct BoneCollection *ANIM_armature_bonecoll_insert_copy_after(
|
||||
struct bArmature *armature,
|
||||
struct BoneCollection *anchor,
|
||||
const struct BoneCollection *bcoll_to_copy);
|
||||
|
||||
/**
|
||||
* Remove a bone collection from the armature.
|
||||
*/
|
||||
|
@ -83,6 +91,9 @@ void ANIM_armature_bonecoll_remove(struct bArmature *armature, struct BoneCollec
|
|||
* Set the given bone collection as the active one.
|
||||
*
|
||||
* Pass `nullptr` to clear the active bone collection.
|
||||
*
|
||||
* The bone collection MUST already be owned by this armature. If it is not,
|
||||
* this function will simply clear the active bone collection.
|
||||
*/
|
||||
void ANIM_armature_bonecoll_active_set(struct bArmature *armature, struct BoneCollection *bcoll);
|
||||
|
||||
|
@ -94,6 +105,15 @@ void ANIM_armature_bonecoll_active_set(struct bArmature *armature, struct BoneCo
|
|||
void ANIM_armature_bonecoll_active_index_set(struct bArmature *armature,
|
||||
int bone_collection_index);
|
||||
|
||||
/**
|
||||
* Determine whether the given bone collection is editable.
|
||||
*
|
||||
* Bone collections are editable when they are local, so either on a local Armature or added to a
|
||||
* linked Armature via a library override in the local file.
|
||||
*/
|
||||
bool ANIM_armature_bonecoll_is_editable(const struct bArmature *armature,
|
||||
const struct BoneCollection *bcoll);
|
||||
|
||||
/**
|
||||
* Move the bone collection by \a step places up/down.
|
||||
*
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
#include "BLI_linklist.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_string.h"
|
||||
|
@ -72,19 +73,31 @@ void ANIM_bonecoll_free(BoneCollection *bcoll)
|
|||
MEM_delete(bcoll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the mapping from the bones to this collection.
|
||||
*
|
||||
* This assumes that the bones do not have such a pointer yet, i.e. calling this
|
||||
* twice for the same bone collection will cause duplicate pointers. */
|
||||
static void add_reverse_pointers(BoneCollection *bcoll)
|
||||
{
|
||||
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
|
||||
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
|
||||
ref->bcoll = bcoll;
|
||||
BLI_addtail(&member->bone->runtime.collections, ref);
|
||||
}
|
||||
}
|
||||
|
||||
void ANIM_armature_runtime_refresh(bArmature *armature)
|
||||
{
|
||||
ANIM_armature_runtime_free(armature);
|
||||
|
||||
ANIM_armature_bonecoll_active_set(armature, armature->active_collection);
|
||||
BoneCollection *active = ANIM_armature_bonecoll_get_by_name(armature,
|
||||
armature->active_collection_name);
|
||||
ANIM_armature_bonecoll_active_set(armature, active);
|
||||
|
||||
/* Construct the bone-to-collections mapping. */
|
||||
LISTBASE_FOREACH (BoneCollection *, bcoll, &armature->collections) {
|
||||
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
|
||||
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
|
||||
ref->bcoll = bcoll;
|
||||
BLI_addtail(&member->bone->runtime.collections, ref);
|
||||
}
|
||||
add_reverse_pointers(bcoll);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,15 +121,48 @@ static void bonecoll_ensure_name_unique(bArmature *armature, BoneCollection *bco
|
|||
BoneCollection *ANIM_armature_bonecoll_new(bArmature *armature, const char *name)
|
||||
{
|
||||
BoneCollection *bcoll = ANIM_bonecoll_new(name);
|
||||
|
||||
if (!ID_IS_LINKED(&armature->id) && ID_IS_OVERRIDE_LIBRARY(&armature->id)) {
|
||||
/* Mark this bone collection as local override, so that certain operations can be allowed. */
|
||||
bcoll->flags |= BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL;
|
||||
}
|
||||
|
||||
bonecoll_ensure_name_unique(armature, bcoll);
|
||||
BLI_addtail(&armature->collections, bcoll);
|
||||
return bcoll;
|
||||
}
|
||||
|
||||
BoneCollection *ANIM_armature_bonecoll_insert_copy_after(bArmature *armature,
|
||||
BoneCollection *anchor,
|
||||
const BoneCollection *bcoll_to_copy)
|
||||
{
|
||||
BoneCollection *bcoll = static_cast<BoneCollection *>(MEM_dupallocN(bcoll_to_copy));
|
||||
|
||||
/* Remap the bone pointers to the given armature, as `bcoll_to_copy` will
|
||||
* likely be owned by another copy of the armature. */
|
||||
BLI_duplicatelist(&bcoll->bones, &bcoll->bones);
|
||||
BLI_assert_msg(armature->bonehash, "Expected armature bone hash to be there");
|
||||
LISTBASE_FOREACH (BoneCollectionMember *, member, &bcoll->bones) {
|
||||
member->bone = BKE_armature_find_bone_name(armature, member->bone->name);
|
||||
}
|
||||
|
||||
if (bcoll_to_copy->prop) {
|
||||
bcoll->prop = IDP_CopyProperty_ex(bcoll_to_copy->prop,
|
||||
0 /*do_id_user ? 0 : LIB_ID_CREATE_NO_USER_REFCOUNT*/);
|
||||
}
|
||||
|
||||
BLI_insertlinkafter(&armature->collections, anchor, bcoll);
|
||||
bonecoll_ensure_name_unique(armature, bcoll);
|
||||
add_reverse_pointers(bcoll);
|
||||
|
||||
return bcoll;
|
||||
}
|
||||
|
||||
static void armature_bonecoll_active_clear(bArmature *armature)
|
||||
{
|
||||
armature->runtime.active_collection_index = -1;
|
||||
armature->active_collection = nullptr;
|
||||
armature->runtime.active_collection = nullptr;
|
||||
armature->active_collection_name[0] = '\0';
|
||||
}
|
||||
|
||||
void ANIM_armature_bonecoll_active_set(bArmature *armature, BoneCollection *bcoll)
|
||||
|
@ -133,8 +179,9 @@ void ANIM_armature_bonecoll_active_set(bArmature *armature, BoneCollection *bcol
|
|||
return;
|
||||
}
|
||||
|
||||
STRNCPY(armature->active_collection_name, bcoll->name);
|
||||
armature->runtime.active_collection_index = index;
|
||||
armature->active_collection = bcoll;
|
||||
armature->runtime.active_collection = bcoll;
|
||||
}
|
||||
|
||||
void ANIM_armature_bonecoll_active_index_set(bArmature *armature, const int bone_collection_index)
|
||||
|
@ -152,8 +199,21 @@ void ANIM_armature_bonecoll_active_index_set(bArmature *armature, const int bone
|
|||
return;
|
||||
}
|
||||
|
||||
STRNCPY(armature->active_collection_name, bcoll->name);
|
||||
armature->runtime.active_collection_index = bone_collection_index;
|
||||
armature->active_collection = bcoll;
|
||||
armature->runtime.active_collection = bcoll;
|
||||
}
|
||||
|
||||
bool ANIM_armature_bonecoll_is_editable(const bArmature *armature, const BoneCollection *bcoll)
|
||||
{
|
||||
const bool is_override = ID_IS_OVERRIDE_LIBRARY(armature);
|
||||
if (ID_IS_LINKED(armature) && !is_override) {
|
||||
return false;
|
||||
}
|
||||
if (is_override && (bcoll->flags & BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL) == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, const int step)
|
||||
|
@ -166,7 +226,7 @@ bool ANIM_armature_bonecoll_move(bArmature *armature, BoneCollection *bcoll, con
|
|||
return false;
|
||||
}
|
||||
|
||||
if (bcoll == armature->active_collection) {
|
||||
if (bcoll == armature->runtime.active_collection) {
|
||||
armature->runtime.active_collection_index = BLI_findindex(&armature->collections, bcoll);
|
||||
}
|
||||
return true;
|
||||
|
@ -178,7 +238,15 @@ void ANIM_armature_bonecoll_name_set(bArmature *armature, BoneCollection *bcoll,
|
|||
|
||||
STRNCPY(old_name, bcoll->name);
|
||||
|
||||
STRNCPY_UTF8(bcoll->name, name);
|
||||
if (name[0] == '\0') {
|
||||
/* Refuse to have nameless collections. The name of the active collection is stored in DNA, and
|
||||
* an empty string means 'no active collection'. */
|
||||
STRNCPY(bcoll->name, bonecoll_default_name);
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(bcoll->name, name);
|
||||
}
|
||||
|
||||
bonecoll_ensure_name_unique(armature, bcoll);
|
||||
|
||||
BKE_animdata_fix_paths_rename_all(&armature->id, "collections", old_name, bcoll->name);
|
||||
|
@ -226,6 +294,13 @@ static void add_membership(BoneCollection *bcoll, Bone *bone)
|
|||
member->bone = bone;
|
||||
BLI_addtail(&bcoll->bones, member);
|
||||
}
|
||||
/* Store reverse membership on the bone. */
|
||||
static void add_reference(Bone *bone, BoneCollection *bcoll)
|
||||
{
|
||||
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
|
||||
ref->bcoll = bcoll;
|
||||
BLI_addtail(&bone->runtime.collections, ref);
|
||||
}
|
||||
|
||||
bool ANIM_armature_bonecoll_assign(BoneCollection *bcoll, Bone *bone)
|
||||
{
|
||||
|
@ -237,11 +312,7 @@ bool ANIM_armature_bonecoll_assign(BoneCollection *bcoll, Bone *bone)
|
|||
}
|
||||
|
||||
add_membership(bcoll, bone);
|
||||
|
||||
/* Store reverse membership on the bone. */
|
||||
BoneCollectionReference *ref = MEM_cnew<BoneCollectionReference>(__func__);
|
||||
ref->bcoll = bcoll;
|
||||
BLI_addtail(&bone->runtime.collections, ref);
|
||||
add_reference(bone, bcoll);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -300,6 +371,8 @@ bool ANIM_armature_bonecoll_unassign(BoneCollection *bcoll, Bone *bone)
|
|||
void ANIM_armature_bonecoll_unassign_all(Bone *bone)
|
||||
{
|
||||
LISTBASE_FOREACH_MUTABLE (BoneCollectionReference *, ref, &bone->runtime.collections) {
|
||||
/* TODO: include Armature as parameter, and check that the bone collection to unassign from is
|
||||
* actually editable. */
|
||||
ANIM_armature_bonecoll_unassign(ref->bcoll, bone);
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +467,7 @@ void ANIM_bone_set_layer_ebone(EditBone *ebone, const int layer)
|
|||
|
||||
void ANIM_armature_bonecoll_assign_active(const bArmature *armature, EditBone *ebone)
|
||||
{
|
||||
if (armature->active_collection == nullptr) {
|
||||
if (armature->runtime.active_collection == nullptr) {
|
||||
/* No active collection, do not assign to any. */
|
||||
printf("ANIM_armature_bonecoll_assign_active(%s, %s): no active collection\n",
|
||||
ebone->name,
|
||||
|
@ -402,7 +475,7 @@ void ANIM_armature_bonecoll_assign_active(const bArmature *armature, EditBone *e
|
|||
return;
|
||||
}
|
||||
|
||||
ANIM_armature_bonecoll_assign_editbone(armature->active_collection, ebone);
|
||||
ANIM_armature_bonecoll_assign_editbone(armature->runtime.active_collection, ebone);
|
||||
}
|
||||
|
||||
void ANIM_armature_bonecoll_show_from_bone(bArmature *armature, const Bone *bone)
|
||||
|
|
|
@ -55,6 +55,13 @@ class ANIM_armature_bone_collections : public testing::Test {
|
|||
BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
|
||||
BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
LISTBASE_FOREACH_BACKWARD_MUTABLE (BoneCollection *, bcoll, &arm.collections) {
|
||||
ANIM_armature_bonecoll_remove(&arm, bcoll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ANIM_armature_bone_collections, armature_owned_collections)
|
||||
|
@ -112,4 +119,93 @@ TEST_F(ANIM_armature_bone_collections, bones_assign_remove)
|
|||
"removed";
|
||||
}
|
||||
|
||||
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_pointer)
|
||||
{
|
||||
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
|
||||
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
|
||||
BoneCollection *bcoll3 = ANIM_bonecoll_new("Alien Bones");
|
||||
|
||||
ANIM_armature_bonecoll_active_set(&arm, bcoll1);
|
||||
EXPECT_EQ(0, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(bcoll1, arm.runtime.active_collection);
|
||||
EXPECT_STREQ(bcoll1->name, arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_set(&arm, nullptr);
|
||||
EXPECT_EQ(-1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(nullptr, arm.runtime.active_collection);
|
||||
EXPECT_STREQ("", arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_set(&arm, bcoll2);
|
||||
EXPECT_EQ(1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(bcoll2, arm.runtime.active_collection);
|
||||
EXPECT_STREQ(bcoll2->name, arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_set(&arm, bcoll3);
|
||||
EXPECT_EQ(-1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(nullptr, arm.runtime.active_collection);
|
||||
EXPECT_STREQ("", arm.active_collection_name);
|
||||
|
||||
ANIM_bonecoll_free(bcoll3);
|
||||
}
|
||||
|
||||
TEST_F(ANIM_armature_bone_collections, active_set_clear_by_index)
|
||||
{
|
||||
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
|
||||
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
|
||||
|
||||
ANIM_armature_bonecoll_active_index_set(&arm, 0);
|
||||
EXPECT_EQ(0, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(bcoll1, arm.runtime.active_collection);
|
||||
EXPECT_STREQ(bcoll1->name, arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_index_set(&arm, -1);
|
||||
EXPECT_EQ(-1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(nullptr, arm.runtime.active_collection);
|
||||
EXPECT_STREQ("", arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_index_set(&arm, 1);
|
||||
EXPECT_EQ(1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(bcoll2, arm.runtime.active_collection);
|
||||
EXPECT_STREQ(bcoll2->name, arm.active_collection_name);
|
||||
|
||||
ANIM_armature_bonecoll_active_index_set(&arm, 47);
|
||||
EXPECT_EQ(-1, arm.runtime.active_collection_index);
|
||||
EXPECT_EQ(nullptr, arm.runtime.active_collection);
|
||||
EXPECT_STREQ("", arm.active_collection_name);
|
||||
}
|
||||
|
||||
TEST_F(ANIM_armature_bone_collections, bcoll_is_editable)
|
||||
{
|
||||
BoneCollection *bcoll1 = ANIM_armature_bonecoll_new(&arm, "Bones 1");
|
||||
BoneCollection *bcoll2 = ANIM_armature_bonecoll_new(&arm, "Bones 2");
|
||||
|
||||
EXPECT_EQ(0, bcoll1->flags & BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL);
|
||||
EXPECT_EQ(0, bcoll2->flags & BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL);
|
||||
|
||||
EXPECT_TRUE(ANIM_armature_bonecoll_is_editable(&arm, bcoll1))
|
||||
<< "Expecting local armature to be editable";
|
||||
|
||||
/* Fake that the armature is linked from another blend file. */
|
||||
Library fake_lib;
|
||||
arm.id.lib = &fake_lib;
|
||||
EXPECT_FALSE(ANIM_armature_bonecoll_is_editable(&arm, bcoll1))
|
||||
<< "Expecting local armature to not be editable";
|
||||
|
||||
/* Fake that the armature is an override, but linked from another blend file. */
|
||||
IDOverrideLibrary fake_override;
|
||||
bArmature fake_reference;
|
||||
fake_override.reference = &fake_reference.id;
|
||||
arm.id.override_library = &fake_override;
|
||||
EXPECT_FALSE(ANIM_armature_bonecoll_is_editable(&arm, bcoll1))
|
||||
<< "Expecting linked armature override to not be editable";
|
||||
|
||||
/* Fake that the armature is a local override. */
|
||||
arm.id.lib = nullptr;
|
||||
bcoll2->flags |= BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL;
|
||||
EXPECT_FALSE(ANIM_armature_bonecoll_is_editable(&arm, bcoll1))
|
||||
<< "Expecting linked bone collection on local armature override to not be editable";
|
||||
EXPECT_TRUE(ANIM_armature_bonecoll_is_editable(&arm, bcoll2))
|
||||
<< "Expecting local bone collection on local armature override to be editable";
|
||||
}
|
||||
|
||||
} // namespace blender::animrig::tests
|
||||
|
|
|
@ -1053,7 +1053,10 @@ static FT_GlyphSlot blf_glyph_render(FontBLF *settings_font,
|
|||
}
|
||||
|
||||
if ((settings_font->flags & BLF_MONOSPACED) && (settings_font != glyph_font)) {
|
||||
blf_glyph_transform_monospace(glyph, BLI_wcwidth(char32_t(charcode)) * fixed_width);
|
||||
const int col = BLI_wcwidth(char32_t(charcode));
|
||||
if (col > 0) {
|
||||
blf_glyph_transform_monospace(glyph, col * fixed_width);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback glyph transforms, but only if required and not yet done. */
|
||||
|
|
|
@ -794,9 +794,14 @@ inline bool bNodeSocket::is_panel_collapsed() const
|
|||
return (this->flag & SOCK_PANEL_COLLAPSED) != 0;
|
||||
}
|
||||
|
||||
inline bool bNodeSocket::is_visible_or_panel_collapsed() const
|
||||
{
|
||||
return !this->is_hidden() && this->is_available();
|
||||
}
|
||||
|
||||
inline bool bNodeSocket::is_visible() const
|
||||
{
|
||||
return !this->is_hidden() && this->is_available() && !this->is_panel_collapsed();
|
||||
return this->is_visible_or_panel_collapsed() && !this->is_panel_collapsed();
|
||||
}
|
||||
|
||||
inline bNode &bNodeSocket::owner_node()
|
||||
|
|
|
@ -13,6 +13,15 @@
|
|||
|
||||
#include "BKE_context.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
namespace blender::asset_system {
|
||||
class AssetRepresentation;
|
||||
}
|
||||
using AssetRepresentationHandle = blender::asset_system::AssetRepresentation;
|
||||
#else
|
||||
typedef struct AssetRepresentationHandle AssetRepresentationHandle;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -464,12 +473,13 @@ typedef struct AssetShelfType {
|
|||
|
||||
/** Determine if an individual asset should be visible or not. May be a temporary design,
|
||||
* visibility should first and foremost be controlled by asset traits. */
|
||||
bool (*asset_poll)(const struct AssetShelfType *shelf_type, const struct AssetHandle *asset);
|
||||
bool (*asset_poll)(const struct AssetShelfType *shelf_type,
|
||||
const AssetRepresentationHandle *asset);
|
||||
|
||||
/** Asset shelves can define their own context menu via this layout definition callback. */
|
||||
void (*draw_context_menu)(const struct bContext *C,
|
||||
const struct AssetShelfType *shelf_type,
|
||||
const struct AssetHandle *asset,
|
||||
const AssetRepresentationHandle *asset,
|
||||
struct uiLayout *layout);
|
||||
|
||||
/* RNA integration */
|
||||
|
|
|
@ -350,10 +350,18 @@ static void armature_blend_read_data(BlendDataReader *reader, ID *id)
|
|||
}
|
||||
|
||||
BLO_read_list(reader, &arm->collections);
|
||||
|
||||
/* Bone collections added via an override can be edited, but ones that already exist in another
|
||||
* blend file (so on the linked Armature) should not be touched. */
|
||||
const bool reset_bcoll_override_flag = ID_IS_LINKED(&arm->id);
|
||||
LISTBASE_FOREACH (BoneCollection *, bcoll, &arm->collections) {
|
||||
direct_link_bone_collection(reader, bcoll);
|
||||
if (reset_bcoll_override_flag) {
|
||||
/* The linked Armature may have overrides in the library file already, and
|
||||
* those should *not* be editable here. */
|
||||
bcoll->flags &= ~BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL;
|
||||
}
|
||||
}
|
||||
BLO_read_data_address(reader, &arm->active_collection);
|
||||
|
||||
BLO_read_data_address(reader, &arm->act_bone);
|
||||
arm->act_edbone = nullptr;
|
||||
|
|
|
@ -2756,7 +2756,8 @@ void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock)
|
|||
|
||||
bool nodeLinkIsHidden(const bNodeLink *link)
|
||||
{
|
||||
return !(link->fromsock->is_visible() && link->tosock->is_visible());
|
||||
return !(link->fromsock->is_visible_or_panel_collapsed() &&
|
||||
link->tosock->is_visible_or_panel_collapsed());
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
|
|
@ -121,7 +121,7 @@ bool BLI_str_cursor_step_next_utf8(const char *str, size_t str_maxlen, int *pos)
|
|||
const char *str_next = str_pos;
|
||||
do {
|
||||
str_next = BLI_str_find_next_char_utf8(str_next, str_end);
|
||||
} while (str_next < str_end && str_next[0] != 0 && BLI_str_utf8_char_width(str_next) < 1);
|
||||
} while (str_next < str_end && str_next[0] != 0 && BLI_str_utf8_char_width(str_next) == 0);
|
||||
(*pos) += (str_next - str_pos);
|
||||
if ((*pos) > (int)str_maxlen) {
|
||||
(*pos) = (int)str_maxlen;
|
||||
|
|
|
@ -83,7 +83,7 @@ void SceneState::init(Object *camera_ob /*=nullptr*/)
|
|||
}
|
||||
xray_mode = shading.xray_alpha != 1.0f;
|
||||
|
||||
if (SHADING_XRAY_FLAG_ENABLED(shading)) {
|
||||
if (xray_mode) {
|
||||
/* Disable shading options that aren't supported in transparency mode. */
|
||||
shading.flag &= ~(V3D_SHADING_SHADOW | V3D_SHADING_CAVITY | V3D_SHADING_DEPTH_OF_FIELD);
|
||||
}
|
||||
|
|
|
@ -41,15 +41,32 @@ struct wmOperator;
|
|||
/* ********************************************** */
|
||||
/* Bone collections */
|
||||
|
||||
static bool bone_collection_poll(bContext *C)
|
||||
static bool bone_collection_add_poll(bContext *C)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ID_IS_OVERRIDE_LIBRARY(ob)) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Cannot edit bone collections for library overrides");
|
||||
if (ob->type != OB_ARMATURE) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Bone collections can only be added to an Armature");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ID_IS_LINKED(ob->data)) {
|
||||
CTX_wm_operator_poll_msg_set(
|
||||
C, "Cannot add bone collections to a linked Armature without an override");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Allow edits of local bone collection only (full local or local override). */
|
||||
static bool active_bone_collection_poll(bContext *C)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -57,25 +74,20 @@ static bool bone_collection_poll(bContext *C)
|
|||
CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool active_bone_collection_poll(bContext *C)
|
||||
{
|
||||
if (!bone_collection_poll(C)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bArmature *armature = static_cast<bArmature *>(ob->data);
|
||||
if (armature->active_collection == nullptr) {
|
||||
BoneCollection *bcoll = armature->runtime.active_collection;
|
||||
|
||||
if (bcoll == nullptr) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Armature has no active bone collection, select one first");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
|
||||
CTX_wm_operator_poll_msg_set(
|
||||
C, "Cannot edit bone collections that are linked from another blend file");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -103,7 +115,7 @@ void ARMATURE_OT_collection_add(wmOperatorType *ot)
|
|||
|
||||
/* api callbacks */
|
||||
ot->exec = bone_collection_add_exec;
|
||||
ot->poll = bone_collection_poll;
|
||||
ot->poll = bone_collection_add_poll;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -118,7 +130,7 @@ static int bone_collection_remove_exec(bContext *C, wmOperator * /* op */)
|
|||
|
||||
/* The poll function ensures armature->active_collection is not NULL. */
|
||||
bArmature *armature = static_cast<bArmature *>(ob->data);
|
||||
ANIM_armature_bonecoll_remove(armature, armature->active_collection);
|
||||
ANIM_armature_bonecoll_remove(armature, armature->runtime.active_collection);
|
||||
|
||||
/* notifiers for updates */
|
||||
WM_event_add_notifier(C, NC_OBJECT | ND_POSE, ob);
|
||||
|
@ -153,7 +165,8 @@ static int bone_collection_move_exec(bContext *C, wmOperator *op)
|
|||
/* Poll function makes sure this is valid. */
|
||||
bArmature *armature = static_cast<bArmature *>(ob->data);
|
||||
|
||||
const bool ok = ANIM_armature_bonecoll_move(armature, armature->active_collection, direction);
|
||||
const bool ok = ANIM_armature_bonecoll_move(
|
||||
armature, armature->runtime.active_collection, direction);
|
||||
if (!ok) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
@ -206,7 +219,7 @@ static BoneCollection *get_bonecoll_named_or_active(bContext * /*C*/,
|
|||
RNA_string_get(op->ptr, "name", bcoll_name);
|
||||
|
||||
if (bcoll_name[0] == '\0') {
|
||||
return armature->active_collection;
|
||||
return armature->runtime.active_collection;
|
||||
}
|
||||
|
||||
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(armature, bcoll_name);
|
||||
|
@ -313,6 +326,29 @@ static bool bone_collection_assign_mode_specific(bContext *C,
|
|||
}
|
||||
}
|
||||
|
||||
static bool bone_collection_assign_poll(bContext *C)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_ARMATURE) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ID_IS_LINKED(ob->data)) {
|
||||
CTX_wm_operator_poll_msg_set(
|
||||
C, "Cannot edit bone collections on linked Armatures without override");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The target bone collection can be specified by name in an operator property, but that's not
|
||||
* available here. So just allow in the poll function, and do the final check in the execute. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Assign selected pchans to the bone collection that the user selects */
|
||||
static int bone_collection_assign_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
|
@ -326,6 +362,12 @@ static int bone_collection_assign_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bArmature *armature = static_cast<bArmature *>(ob->data);
|
||||
if (!ANIM_armature_bonecoll_is_editable(armature, bcoll)) {
|
||||
WM_reportf(RPT_ERROR, "Cannot assign to linked bone collection %s", bcoll->name);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
bool made_any_changes = false;
|
||||
bool had_bones_to_assign = false;
|
||||
const bool mode_is_supported = bone_collection_assign_mode_specific(
|
||||
|
@ -364,7 +406,7 @@ void ARMATURE_OT_collection_assign(wmOperatorType *ot)
|
|||
// TODO: reinstate the menu?
|
||||
// ot->invoke = bone_collections_menu_invoke;
|
||||
ot->exec = bone_collection_assign_exec;
|
||||
ot->poll = bone_collection_poll;
|
||||
ot->poll = bone_collection_assign_poll;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -426,7 +468,7 @@ void ARMATURE_OT_collection_unassign(wmOperatorType *ot)
|
|||
|
||||
/* api callbacks */
|
||||
ot->exec = bone_collection_unassign_exec;
|
||||
ot->poll = bone_collection_poll;
|
||||
ot->poll = bone_collection_assign_poll;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -450,6 +492,23 @@ static bool editbone_is_member(const EditBone *ebone, const BoneCollection *bcol
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool armature_bone_select_poll(bContext *C)
|
||||
{
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr || ob->type != OB_ARMATURE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* For bone selection, at least the pose should be editable to actually store
|
||||
* the selection state. */
|
||||
if (ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data)) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Cannot (de)select bones on linked Armature");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bone_collection_select(bContext *C,
|
||||
Object *ob,
|
||||
BoneCollection *bcoll,
|
||||
|
@ -524,7 +583,7 @@ void ARMATURE_OT_collection_select(wmOperatorType *ot)
|
|||
|
||||
/* api callbacks */
|
||||
ot->exec = bone_collection_select_exec;
|
||||
ot->poll = bone_collection_poll;
|
||||
ot->poll = armature_bone_select_poll;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -564,7 +623,7 @@ void ARMATURE_OT_collection_deselect(wmOperatorType *ot)
|
|||
|
||||
/* api callbacks */
|
||||
ot->exec = bone_collection_deselect_exec;
|
||||
ot->poll = bone_collection_poll;
|
||||
ot->poll = armature_bone_select_poll;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
@ -612,6 +671,13 @@ static int add_or_move_to_collection_exec(bContext *C,
|
|||
}
|
||||
}
|
||||
|
||||
if (!ANIM_armature_bonecoll_is_editable(arm, target_bcoll)) {
|
||||
WM_reportf(RPT_ERROR,
|
||||
"Bone collection %s is not editable, maybe add an override on the armature?",
|
||||
target_bcoll->name);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
FOREACH_PCHAN_SELECTED_IN_OBJECT_BEGIN (obpose, pchan) {
|
||||
assign_func(target_bcoll, pchan->bone);
|
||||
}
|
||||
|
@ -636,13 +702,25 @@ static int assign_to_collection_exec(bContext *C, wmOperator *op)
|
|||
|
||||
static bool move_to_collection_poll(bContext *C)
|
||||
{
|
||||
/* TODO: add outliner support.
|
||||
if (CTX_wm_space_outliner(C) != nullptr) {
|
||||
return ED_outliner_collections_editor_poll(C);
|
||||
Object *ob = ED_object_context(C);
|
||||
if (ob == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
// TODO: add armature edit mode support.
|
||||
return ED_operator_object_active_local_editable_posemode_exclusive(C);
|
||||
|
||||
if (ob->type != OB_ARMATURE) {
|
||||
CTX_wm_operator_poll_msg_set(C, "Bone collections can only be edited on an Armature");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ID_IS_LINKED(ob->data) && !ID_IS_OVERRIDE_LIBRARY(ob->data)) {
|
||||
CTX_wm_operator_poll_msg_set(C, "This needs a local Armature or an override");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ideally this would also check the target bone collection to move/assign to.
|
||||
* However, that requires access to the operator properties, and those are not
|
||||
* available in the poll function. */
|
||||
return true;
|
||||
}
|
||||
|
||||
static const EnumPropertyItem *bone_collection_enum_itemf(bContext *C,
|
||||
|
@ -659,6 +737,12 @@ static const EnumPropertyItem *bone_collection_enum_itemf(bContext *C,
|
|||
|
||||
int bcoll_index = 0;
|
||||
LISTBASE_FOREACH_INDEX (BoneCollection *, bcoll, &arm->collections, bcoll_index) {
|
||||
if (!ANIM_armature_bonecoll_is_editable(arm, bcoll)) {
|
||||
/* Skip bone collections that cannot be assigned to because they're
|
||||
* linked and thus uneditable. If there is a way to still show these, but in a disabled
|
||||
* state, that would be preferred. */
|
||||
continue;
|
||||
}
|
||||
item_tmp.identifier = bcoll->name;
|
||||
item_tmp.name = bcoll->name;
|
||||
item_tmp.value = bcoll_index;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "BLI_array_utils.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_armature.h"
|
||||
#include "BKE_context.h"
|
||||
|
@ -70,7 +71,7 @@ static void remap_ebone_bone_collection_references(
|
|||
|
||||
struct UndoArmature {
|
||||
EditBone *act_edbone;
|
||||
BoneCollection *active_collection;
|
||||
char active_collection_name[MAX_NAME];
|
||||
ListBase /* EditBone */ ebones;
|
||||
ListBase /* BoneCollection */ bone_collections;
|
||||
size_t undo_size;
|
||||
|
@ -98,7 +99,12 @@ static void undoarm_to_editarm(UndoArmature *uarm, bArmature *arm)
|
|||
ANIM_bonecoll_listbase_free(&arm->collections, true);
|
||||
auto bcoll_map = ANIM_bonecoll_listbase_copy_no_membership(
|
||||
&arm->collections, &uarm->bone_collections, true);
|
||||
arm->active_collection = bcoll_map.lookup_default(uarm->active_collection, nullptr);
|
||||
|
||||
/* Always do a lookup-by-name and assignment. Even when the name of the active collection is
|
||||
* still the same, the order may have changed and thus the index needs to be updated. */
|
||||
BoneCollection *active_bcoll = ANIM_armature_bonecoll_get_by_name(arm,
|
||||
uarm->active_collection_name);
|
||||
ANIM_armature_bonecoll_active_set(arm, active_bcoll);
|
||||
|
||||
remap_ebone_bone_collection_references(arm->edbo, bcoll_map);
|
||||
|
||||
|
@ -123,7 +129,7 @@ static void *undoarm_from_editarm(UndoArmature *uarm, bArmature *arm)
|
|||
/* Copy bone collections. */
|
||||
auto bcoll_map = ANIM_bonecoll_listbase_copy_no_membership(
|
||||
&uarm->bone_collections, &arm->collections, false);
|
||||
uarm->active_collection = bcoll_map.lookup_default(arm->active_collection, nullptr);
|
||||
STRNCPY(uarm->active_collection_name, arm->active_collection_name);
|
||||
|
||||
/* Point the new edit bones at the new collections. */
|
||||
remap_ebone_bone_collection_references(&uarm->ebones, bcoll_map);
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "AS_asset_catalog_tree.hh"
|
||||
|
||||
struct AssetFilterSettings;
|
||||
struct AssetHandle;
|
||||
struct AssetLibraryReference;
|
||||
struct bContext;
|
||||
|
||||
|
@ -55,7 +54,7 @@ struct AssetItemTree {
|
|||
asset_system::AssetCatalogTree build_filtered_catalog_tree(
|
||||
const asset_system::AssetLibrary &library,
|
||||
const AssetLibraryReference &library_ref,
|
||||
blender::FunctionRef<bool(const AssetHandle &)> is_asset_visible_fn);
|
||||
blender::FunctionRef<bool(const asset_system::AssetRepresentation &)> is_asset_visible_fn);
|
||||
AssetItemTree build_filtered_all_catalog_tree(
|
||||
const AssetLibraryReference &library_ref,
|
||||
const bContext &C,
|
||||
|
|
|
@ -55,17 +55,17 @@ namespace blender::ed::asset {
|
|||
asset_system::AssetCatalogTree build_filtered_catalog_tree(
|
||||
const asset_system::AssetLibrary &library,
|
||||
const AssetLibraryReference &library_ref,
|
||||
const blender::FunctionRef<bool(const AssetHandle &)> is_asset_visible_fn)
|
||||
const blender::FunctionRef<bool(const asset_system::AssetRepresentation &)>
|
||||
is_asset_visible_fn)
|
||||
{
|
||||
Set<StringRef> known_paths;
|
||||
|
||||
/* Collect paths containing assets. */
|
||||
ED_assetlist_iterate(library_ref, [&](AssetHandle asset_handle) {
|
||||
if (!is_asset_visible_fn(asset_handle)) {
|
||||
ED_assetlist_iterate(library_ref, [&](asset_system::AssetRepresentation &asset) {
|
||||
if (!is_asset_visible_fn(asset)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
asset_system::AssetRepresentation &asset = *ED_asset_handle_get_representation(&asset_handle);
|
||||
const AssetMetaData &meta_data = asset.get_metadata();
|
||||
if (BLI_uuid_is_nil(meta_data.catalog_id)) {
|
||||
return true;
|
||||
|
|
|
@ -627,7 +627,7 @@ static uiBut *add_tab_button(uiBlock &block, StringRefNull name)
|
|||
return but;
|
||||
}
|
||||
|
||||
static void add_catalog_toggle_buttons(AssetShelfSettings &shelf_settings, uiLayout &layout)
|
||||
static void add_catalog_tabs(AssetShelfSettings &shelf_settings, uiLayout &layout)
|
||||
{
|
||||
uiBlock *block = uiLayoutGetBlock(&layout);
|
||||
|
||||
|
@ -685,13 +685,16 @@ static void asset_shelf_header_draw(const bContext *C, Header *header)
|
|||
PointerRNA shelf_ptr = shelf::active_shelf_ptr_from_context(C);
|
||||
AssetShelf *shelf = static_cast<AssetShelf *>(shelf_ptr.data);
|
||||
if (shelf) {
|
||||
add_catalog_toggle_buttons(shelf->settings, *layout);
|
||||
add_catalog_tabs(shelf->settings, *layout);
|
||||
}
|
||||
|
||||
uiItemSpacer(layout);
|
||||
|
||||
uiItemR(layout, &shelf_ptr, "search_filter", UI_ITEM_NONE, "", ICON_VIEWZOOM);
|
||||
uiItemPopoverPanel(layout, C, "ASSETSHELF_PT_display", "", ICON_IMGDISPLAY);
|
||||
uiLayout *sub = uiLayoutRow(layout, false);
|
||||
/* Same as file/asset browser header. */
|
||||
uiLayoutSetUnitsX(sub, 8);
|
||||
uiItemR(sub, &shelf_ptr, "search_filter", UI_ITEM_NONE, "", ICON_VIEWZOOM);
|
||||
}
|
||||
|
||||
void ED_asset_shelf_header_regiontype_register(ARegionType *region_type, const int space_type)
|
||||
|
|
|
@ -104,12 +104,13 @@ void AssetView::build_items()
|
|||
}
|
||||
|
||||
ED_assetlist_iterate(library_ref_, [&](AssetHandle asset_handle) {
|
||||
if (shelf_.type->asset_poll && !shelf_.type->asset_poll(shelf_.type, &asset_handle)) {
|
||||
const asset_system::AssetRepresentation *asset = ED_asset_handle_get_representation(
|
||||
&asset_handle);
|
||||
|
||||
if (shelf_.type->asset_poll && !shelf_.type->asset_poll(shelf_.type, asset)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const asset_system::AssetRepresentation *asset = ED_asset_handle_get_representation(
|
||||
&asset_handle);
|
||||
const AssetMetaData &asset_data = asset->get_metadata();
|
||||
|
||||
if (catalog_filter_ && !catalog_filter_->contains(asset_data.catalog_id)) {
|
||||
|
@ -213,7 +214,8 @@ void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const
|
|||
const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view());
|
||||
const AssetShelfType &shelf_type = *asset_view.shelf_.type;
|
||||
if (shelf_type.draw_context_menu) {
|
||||
shelf_type.draw_context_menu(&C, &shelf_type, &asset_, &column);
|
||||
asset_system::AssetRepresentation *asset = ED_asset_handle_get_representation(&asset_);
|
||||
shelf_type.draw_context_menu(&C, &shelf_type, asset, &column);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,8 +45,10 @@ class AssetCatalogSelectorTree : public ui::AbstractTreeView {
|
|||
: shelf_(shelf), shelf_settings_(shelf_.settings)
|
||||
{
|
||||
catalog_tree_ = build_filtered_catalog_tree(
|
||||
library, asset_system::all_library_reference(), [this](const AssetHandle asset_handle) {
|
||||
return (!shelf_.type->asset_poll || shelf_.type->asset_poll(shelf_.type, &asset_handle));
|
||||
library,
|
||||
asset_system::all_library_reference(),
|
||||
[this](const asset_system::AssetRepresentation &asset) {
|
||||
return (!shelf_.type->asset_poll || shelf_.type->asset_poll(shelf_.type, &asset));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
struct ARegion;
|
||||
struct AssetFilterSettings;
|
||||
struct AssetRepresentation;
|
||||
struct AutoComplete;
|
||||
struct EnumPropertyItem;
|
||||
struct FileSelectParams;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include "ED_undo.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace blender::ui::greasepencil {
|
||||
|
@ -74,7 +76,7 @@ class LayerNodeDropTarget : public TreeViewItemDropTarget {
|
|||
return "";
|
||||
}
|
||||
|
||||
bool on_drop(bContext * /*C*/, const DragInfo &drag_info) const override
|
||||
bool on_drop(bContext *C, const DragInfo &drag_info) const override
|
||||
{
|
||||
const wmDragGreasePencilLayer *drag_grease_pencil =
|
||||
static_cast<const wmDragGreasePencilLayer *>(drag_info.drag_data.poin);
|
||||
|
@ -102,21 +104,28 @@ class LayerNodeDropTarget : public TreeViewItemDropTarget {
|
|||
LayerGroup &drop_group = drop_tree_node_.as_group();
|
||||
drag_parent.unlink_node(&drag_layer.as_node());
|
||||
drop_group.add_layer(&drag_layer);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
case DropLocation::Before:
|
||||
case DropLocation::Before: {
|
||||
drag_parent.unlink_node(&drag_layer.as_node());
|
||||
/* Draw order is inverted, so inserting before means inserting below. */
|
||||
drop_parent_group->add_layer_after(&drag_layer, &drop_tree_node_);
|
||||
return true;
|
||||
case DropLocation::After:
|
||||
break;
|
||||
}
|
||||
case DropLocation::After: {
|
||||
drag_parent.unlink_node(&drag_layer.as_node());
|
||||
/* Draw order is inverted, so inserting after means inserting above. */
|
||||
drop_parent_group->add_layer_before(&drag_layer, &drop_tree_node_);
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include "ED_transform_snap_object_context.hh"
|
||||
#include "ED_undo.hh"
|
||||
#include "ED_view3d.hh"
|
||||
#include "ED_outliner.hh"
|
||||
|
||||
#include "WM_toolsystem.h"
|
||||
|
||||
|
@ -492,6 +493,8 @@ static bool object_transfer_mode_to_base(bContext *C, wmOperator *op, Base *base
|
|||
}
|
||||
|
||||
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
|
||||
ED_outliner_select_sync_from_object_tag(C);
|
||||
|
||||
WM_toolsystem_update_from_context_view3d(C);
|
||||
mode_transferred = true;
|
||||
}
|
||||
|
|
|
@ -407,8 +407,8 @@ static bool node_update_basis_socket(const bContext &C,
|
|||
const int &locx,
|
||||
int &locy)
|
||||
{
|
||||
if ((!input_socket || !input_socket->is_visible()) &&
|
||||
(!output_socket || !output_socket->is_visible()))
|
||||
if ((!input_socket || !input_socket->is_visible_or_panel_collapsed()) &&
|
||||
(!output_socket || !output_socket->is_visible_or_panel_collapsed()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -701,7 +701,7 @@ static void node_update_basis_from_declaration(
|
|||
}
|
||||
else {
|
||||
/* Space between items. */
|
||||
if (!is_first && item.input->is_visible()) {
|
||||
if (!is_first && item.input->is_visible_or_panel_collapsed()) {
|
||||
locy -= NODE_SOCKDY;
|
||||
}
|
||||
}
|
||||
|
@ -714,7 +714,7 @@ static void node_update_basis_from_declaration(
|
|||
}
|
||||
else {
|
||||
/* Space between items. */
|
||||
if (!is_first && item.output->is_visible()) {
|
||||
if (!is_first && item.output->is_visible_or_panel_collapsed()) {
|
||||
locy -= NODE_SOCKDY;
|
||||
}
|
||||
}
|
||||
|
@ -865,12 +865,12 @@ static void node_update_hidden(bNode &node, uiBlock &block)
|
|||
|
||||
/* Calculate minimal radius. */
|
||||
for (const bNodeSocket *socket : node.input_sockets()) {
|
||||
if (socket->is_visible()) {
|
||||
if (socket->is_visible_or_panel_collapsed()) {
|
||||
totin++;
|
||||
}
|
||||
}
|
||||
for (const bNodeSocket *socket : node.output_sockets()) {
|
||||
if (socket->is_visible()) {
|
||||
if (socket->is_visible_or_panel_collapsed()) {
|
||||
totout++;
|
||||
}
|
||||
}
|
||||
|
@ -891,7 +891,7 @@ static void node_update_hidden(bNode &node, uiBlock &block)
|
|||
float drad = rad;
|
||||
|
||||
for (bNodeSocket *socket : node.output_sockets()) {
|
||||
if (socket->is_visible()) {
|
||||
if (socket->is_visible_or_panel_collapsed()) {
|
||||
/* Round the socket location to stop it from jiggling. */
|
||||
socket->runtime->location = {
|
||||
round(node.runtime->totr.xmax - hiddenrad + sinf(rad) * hiddenrad),
|
||||
|
@ -904,7 +904,7 @@ static void node_update_hidden(bNode &node, uiBlock &block)
|
|||
rad = drad = -float(M_PI) / (1.0f + float(totin));
|
||||
|
||||
for (bNodeSocket *socket : node.input_sockets()) {
|
||||
if (socket->is_visible()) {
|
||||
if (socket->is_visible_or_panel_collapsed()) {
|
||||
/* Round the socket location to stop it from jiggling. */
|
||||
socket->runtime->location = {
|
||||
round(node.runtime->totr.xmin + hiddenrad + sinf(rad) * hiddenrad),
|
||||
|
@ -1751,7 +1751,7 @@ static void node_draw_sockets(const View2D &v2d,
|
|||
/* Socket inputs. */
|
||||
int selected_input_len = 0;
|
||||
for (const bNodeSocket *sock : node.input_sockets()) {
|
||||
if (!sock->is_visible() || sock->is_panel_collapsed()) {
|
||||
if (!sock->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
if (select_all || (sock->flag & SELECT)) {
|
||||
|
@ -1774,7 +1774,7 @@ static void node_draw_sockets(const View2D &v2d,
|
|||
int selected_output_len = 0;
|
||||
if (draw_outputs) {
|
||||
for (const bNodeSocket *sock : node.output_sockets()) {
|
||||
if (!sock->is_visible() || sock->is_panel_collapsed()) {
|
||||
if (!sock->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
if (select_all || (sock->flag & SELECT)) {
|
||||
|
|
|
@ -170,7 +170,7 @@ static void pick_input_link_by_link_intersect(const bContext &C,
|
|||
|
||||
static bool socket_is_available(bNodeTree * /*ntree*/, bNodeSocket *sock, const bool allow_used)
|
||||
{
|
||||
if (!sock->is_visible()) {
|
||||
if (!sock->is_visible_or_panel_collapsed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2264,7 +2264,7 @@ bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_
|
|||
int index;
|
||||
LISTBASE_FOREACH_INDEX (bNodeSocket *, socket, sockets, index) {
|
||||
const nodes::SocketDeclaration &socket_decl = *socket_decls[index];
|
||||
if (!socket->is_visible()) {
|
||||
if (!socket->is_visible_or_panel_collapsed()) {
|
||||
continue;
|
||||
}
|
||||
if (socket_decl.is_default_link_socket) {
|
||||
|
@ -2285,7 +2285,7 @@ bNodeSocket *get_main_socket(bNodeTree &ntree, bNode &node, eNodeSocketInOut in_
|
|||
/* Try all priorities, starting from 'highest'. */
|
||||
for (int priority = maxpriority; priority >= 0; priority--) {
|
||||
LISTBASE_FOREACH (bNodeSocket *, sock, sockets) {
|
||||
if (!!sock->is_visible() && priority == get_main_socket_priority(sock)) {
|
||||
if (!!sock->is_visible_or_panel_collapsed() && priority == get_main_socket_priority(sock)) {
|
||||
return sock;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -660,9 +660,28 @@ static void snap_transform_data(TransInfo *t, TransDataContainer *tc)
|
|||
if (t->modifiers & MOD_SNAP_INVERT) {
|
||||
invert_snap(snap_mode);
|
||||
}
|
||||
TransData *td = tc->data;
|
||||
for (int i = 0; i < tc->data_len; i++, td++) {
|
||||
transform_snap_anim_flush_data(t, td, snap_mode, td->loc);
|
||||
|
||||
float offset = 0;
|
||||
float smallest_snap_delta = FLT_MAX;
|
||||
|
||||
/* In order to move the strip in a block and not each end individually,
|
||||
* find the minimal snap offset first and then shift the whole strip by that amount. */
|
||||
for (int i = 0; i < tc->data_len; i++) {
|
||||
TransData td = tc->data[i];
|
||||
float snap_value;
|
||||
transform_snap_anim_flush_data(t, &td, snap_mode, &snap_value);
|
||||
|
||||
/* The snap_delta measures how far from the unsnapped position the value has moved. */
|
||||
const float snap_delta = *td.loc - snap_value;
|
||||
if (fabs(snap_delta) < fabs(smallest_snap_delta)) {
|
||||
offset = snap_value - td.iloc[0];
|
||||
smallest_snap_delta = snap_delta;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < tc->data_len; i++) {
|
||||
TransData td = tc->data[i];
|
||||
*td.loc = td.iloc[0] + offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ typedef struct bArmature_Runtime {
|
|||
*/
|
||||
int active_collection_index;
|
||||
uint8_t _pad0[4];
|
||||
struct BoneCollection *active_collection;
|
||||
} bArmature_Runtime;
|
||||
|
||||
typedef struct bArmature {
|
||||
|
@ -185,8 +186,12 @@ typedef struct bArmature {
|
|||
|
||||
/* BoneCollection. */
|
||||
ListBase collections;
|
||||
/* Do not directly assign, use `ANIM_armature_bonecoll_active_set` instead. */
|
||||
struct BoneCollection *active_collection;
|
||||
|
||||
/** Do not directly assign, use `ANIM_armature_bonecoll_active_set` instead.
|
||||
* This is stored as a string to make it possible for the library overrides system to understand
|
||||
* when it actually changed (compared to a BoneCollection*, which would change on every load).
|
||||
*/
|
||||
char active_collection_name[64]; /* MAX_NAME. */
|
||||
|
||||
/** For UI, to show which layers are there. */
|
||||
unsigned int layer_used DNA_DEPRECATED;
|
||||
|
@ -419,8 +424,9 @@ typedef enum eBone_BBoneHandleFlag {
|
|||
typedef enum eBoneCollection_Flag {
|
||||
BONE_COLLECTION_VISIBLE = (1 << 0),
|
||||
BONE_COLLECTION_SELECTABLE = (1 << 1), /* Intended to be implemented in the not-so-far future. */
|
||||
BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL = (1 << 2), /* Added by a local library override. */
|
||||
} eBoneCollection_Flag;
|
||||
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_SELECTABLE)
|
||||
ENUM_OPERATORS(eBoneCollection_Flag, BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL)
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
|
|
|
@ -189,6 +189,7 @@ typedef struct bNodeSocket {
|
|||
bool is_hidden() const;
|
||||
bool is_available() const;
|
||||
bool is_panel_collapsed() const;
|
||||
bool is_visible_or_panel_collapsed() const;
|
||||
bool is_visible() const;
|
||||
bool is_multi_input() const;
|
||||
bool is_input() const;
|
||||
|
|
|
@ -157,6 +157,12 @@ bool RNA_property_overridable_get(PointerRNA *ptr, PropertyRNA *prop)
|
|||
return true;
|
||||
}
|
||||
}
|
||||
else if (RNA_struct_is_a(ptr->type, &RNA_BoneCollection)) {
|
||||
BoneCollection *bcoll = static_cast<BoneCollection *>(ptr->data);
|
||||
if (bcoll->flags & BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* If this is a RNA-defined property (real or 'virtual' IDProp),
|
||||
* we want to use RNA prop flag. */
|
||||
return !(prop->flag_override & PROPOVERRIDE_NO_COMPARISON) &&
|
||||
|
|
|
@ -185,6 +185,15 @@ static void rna_Armature_edit_bone_remove(bArmature *arm,
|
|||
RNA_POINTER_INVALIDATE(ebone_ptr);
|
||||
}
|
||||
|
||||
static void rna_BoneCollections_active_set(PointerRNA *ptr,
|
||||
PointerRNA value,
|
||||
struct ReportList * /*reports*/)
|
||||
{
|
||||
bArmature *arm = (bArmature *)ptr->data;
|
||||
BoneCollection *bcoll = (BoneCollection *)value.data;
|
||||
ANIM_armature_bonecoll_active_set(arm, bcoll);
|
||||
}
|
||||
|
||||
static int rna_BoneCollections_active_index_get(PointerRNA *ptr)
|
||||
{
|
||||
bArmature *arm = (bArmature *)ptr->data;
|
||||
|
@ -210,6 +219,14 @@ static void rna_BoneCollections_active_index_range(
|
|||
*max = max_ii(0, BLI_listbase_count(&arm->collections) - 1);
|
||||
}
|
||||
|
||||
static void rna_BoneCollections_active_name_set(PointerRNA *ptr, const char *name)
|
||||
{
|
||||
bArmature *arm = (bArmature *)ptr->data;
|
||||
BoneCollection *bcoll = ANIM_armature_bonecoll_get_by_name(arm, name);
|
||||
ANIM_armature_bonecoll_active_set(arm, bcoll);
|
||||
WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, ptr->data);
|
||||
}
|
||||
|
||||
static void rna_BoneCollections_move(bArmature *arm, ReportList *reports, int from, int to)
|
||||
{
|
||||
const int count = BLI_listbase_count(&arm->collections);
|
||||
|
@ -299,6 +316,36 @@ static void rna_EditBone_collections_begin(CollectionPropertyIterator *iter, Poi
|
|||
rna_iterator_listbase_begin(iter, &bone_collection_refs, nullptr);
|
||||
}
|
||||
|
||||
/* Armature.collections library override support. */
|
||||
static bool rna_Armature_collections_override_apply(Main *bmain,
|
||||
RNAPropertyOverrideApplyContext &rnaapply_ctx)
|
||||
{
|
||||
PointerRNA *ptr_dst = &rnaapply_ctx.ptr_dst;
|
||||
PropertyRNA *prop_dst = rnaapply_ctx.prop_dst;
|
||||
PointerRNA *ptr_item_dst = &rnaapply_ctx.ptr_item_dst;
|
||||
PointerRNA *ptr_item_src = &rnaapply_ctx.ptr_item_src;
|
||||
IDOverrideLibraryPropertyOperation *opop = rnaapply_ctx.liboverride_operation;
|
||||
|
||||
if (opop->operation != LIBOVERRIDE_OP_INSERT_AFTER) {
|
||||
printf("Unsupported RNA override operation on armature collections, ignoring\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bArmature *arm_dst = (bArmature *)ptr_dst->owner_id;
|
||||
BoneCollection *bcoll_anchor = static_cast<BoneCollection *>(ptr_item_dst->data);
|
||||
BoneCollection *bcoll_src = static_cast<BoneCollection *>(ptr_item_src->data);
|
||||
BoneCollection *bcoll = ANIM_armature_bonecoll_insert_copy_after(
|
||||
arm_dst, bcoll_anchor, bcoll_src);
|
||||
|
||||
if (!ID_IS_LINKED(&arm_dst->id)) {
|
||||
/* Mark this bone collection as local override, so that certain operations can be allowed. */
|
||||
bcoll->flags |= BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL;
|
||||
}
|
||||
|
||||
RNA_property_update_main(bmain, nullptr, ptr_dst, prop_dst);
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *rna_BoneColor_path_posebone(const PointerRNA *ptr)
|
||||
{
|
||||
/* Find the bPoseChan that owns this BoneColor. */
|
||||
|
@ -1766,13 +1813,16 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
|||
|
||||
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||
RNA_def_property_pointer_sdna(prop, nullptr, "active_collection");
|
||||
RNA_def_property_pointer_sdna(prop, nullptr, "runtime.active_collection");
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE);
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_pointer_funcs(
|
||||
prop, nullptr, "rna_BoneCollections_active_set", nullptr, nullptr);
|
||||
RNA_def_property_ui_text(prop, "Active Collection", "Armature's active bone collection");
|
||||
|
||||
prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, nullptr, "runtime.active_collection_index");
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_IGNORE);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Active Collection Index",
|
||||
"The index of the Armature's active bone collection; -1 when there "
|
||||
|
@ -1782,6 +1832,17 @@ static void rna_def_armature_collections(BlenderRNA *brna, PropertyRNA *cprop)
|
|||
"rna_BoneCollections_active_index_set",
|
||||
"rna_BoneCollections_active_index_range");
|
||||
|
||||
prop = RNA_def_property(srna, "active_name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_property_string_sdna(prop, nullptr, "active_collection_name");
|
||||
/* TODO: For some reason the overrides system doesn't register a new operation when this property
|
||||
* changes. Needs further investigation to figure out why & fix it. */
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Active Collection Name",
|
||||
"The name of the Armature's active bone collection; empty when there "
|
||||
"is no active collection");
|
||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_BoneCollections_active_name_set");
|
||||
|
||||
/* Armature.collections.new(...) */
|
||||
func = RNA_def_function(srna, "new", "ANIM_armature_bonecoll_new");
|
||||
RNA_def_function_ui_description(func, "Add a new empty bone collection to the armature");
|
||||
|
@ -1907,6 +1968,10 @@ static void rna_def_armature(BlenderRNA *brna)
|
|||
RNA_def_property_collection_sdna(prop, nullptr, "collections", nullptr);
|
||||
RNA_def_property_struct_type(prop, "BoneCollection");
|
||||
RNA_def_property_ui_text(prop, "Bone Collections", "");
|
||||
RNA_def_property_override_funcs(
|
||||
prop, nullptr, nullptr, "rna_Armature_collections_override_apply");
|
||||
RNA_def_property_override_flag(
|
||||
prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY | PROPOVERRIDE_LIBRARY_INSERTION);
|
||||
rna_def_armature_collections(brna, prop);
|
||||
|
||||
/* Enum values */
|
||||
|
@ -2003,7 +2068,6 @@ static void rna_def_bonecollection(BlenderRNA *brna)
|
|||
RNA_def_property_ui_text(prop, "Name", "Unique within the Armature");
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_BoneCollection_name_set");
|
||||
// RNA_def_property_update(prop, 0, "rna_Bone_update_renamed");
|
||||
|
||||
prop = RNA_def_property(srna, "is_visible", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_VISIBLE);
|
||||
|
@ -2014,6 +2078,14 @@ static void rna_def_bonecollection(BlenderRNA *brna)
|
|||
RNA_def_property_update(prop, NC_OBJECT | ND_POSE, nullptr);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
|
||||
prop = RNA_def_property(srna, "is_local_override", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flags", BONE_COLLECTION_OVERRIDE_LIBRARY_LOCAL);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Is Local Override",
|
||||
"This collection was added via a library override in the current blend file");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
prop = RNA_def_property(srna, "bones", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "Bone");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "BLT_translation.h"
|
||||
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
|
@ -380,6 +382,14 @@ static PointerRNA rna_AssetHandle_local_id_get(PointerRNA *ptr)
|
|||
return rna_pointer_inherit_refine(ptr, &RNA_ID, id);
|
||||
}
|
||||
|
||||
static int rna_AssetRepresentation_id_type_get(PointerRNA *ptr)
|
||||
{
|
||||
using namespace blender::asset_system;
|
||||
|
||||
const AssetRepresentation *asset = static_cast<const AssetRepresentation *>(ptr->data);
|
||||
return asset->get_id_type();
|
||||
}
|
||||
|
||||
const EnumPropertyItem *rna_asset_library_reference_itemf(bContext * /*C*/,
|
||||
PointerRNA * /*ptr*/,
|
||||
PropertyRNA * /*prop*/,
|
||||
|
@ -585,12 +595,25 @@ static void rna_def_asset_handle(BlenderRNA *brna)
|
|||
static void rna_def_asset_representation(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "AssetRepresentation", nullptr);
|
||||
RNA_def_struct_ui_text(srna,
|
||||
"Asset Representation",
|
||||
"Information about an entity that makes it possible for the asset system "
|
||||
"to deal with the entity as asset");
|
||||
|
||||
prop = RNA_def_property(srna, "id_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_id_type_items);
|
||||
RNA_def_property_enum_funcs(prop, "rna_AssetRepresentation_id_type_get", nullptr, nullptr);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Data-block Type",
|
||||
/* Won't ever actually return 'NONE' currently, this is just for information for once non-ID
|
||||
* assets are supported. */
|
||||
"The type of the data-block, if the asset represents one ('NONE' otherwise)");
|
||||
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
|
||||
}
|
||||
|
||||
static void rna_def_asset_catalog_path(BlenderRNA *brna)
|
||||
|
|
|
@ -279,6 +279,12 @@ static bool rna_Attribute_is_internal_get(PointerRNA *ptr)
|
|||
return !BKE_attribute_allow_procedural_access(layer->name);
|
||||
}
|
||||
|
||||
static bool rna_Attribute_is_required_get(PointerRNA *ptr)
|
||||
{
|
||||
const CustomDataLayer *layer = (const CustomDataLayer *)ptr->data;
|
||||
return BKE_id_attribute_required(ptr->owner_id, layer->name);
|
||||
}
|
||||
|
||||
static void rna_Attribute_data_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
ID *id = ptr->owner_id;
|
||||
|
@ -1156,6 +1162,11 @@ static void rna_def_attribute(BlenderRNA *brna)
|
|||
prop, "Is Internal", "The attribute is meant for internal use by Blender");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
prop = RNA_def_property(srna, "is_required", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_funcs(prop, "rna_Attribute_is_required_get", nullptr);
|
||||
RNA_def_property_ui_text(prop, "Is Required", "Whether the attribute can be removed or renamed");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
/* types */
|
||||
rna_def_attribute_float(brna);
|
||||
rna_def_attribute_float_vector(brna);
|
||||
|
|
|
@ -1071,7 +1071,8 @@ static StructRNA *rna_Menu_refine(PointerRNA *mtr)
|
|||
|
||||
/* Asset Shelf */
|
||||
|
||||
static bool asset_shelf_asset_poll(const AssetShelfType *shelf_type, const AssetHandle *asset)
|
||||
static bool asset_shelf_asset_poll(const AssetShelfType *shelf_type,
|
||||
const AssetRepresentationHandle *asset)
|
||||
{
|
||||
extern FunctionRNA rna_AssetShelf_asset_poll_func;
|
||||
|
||||
|
@ -1080,7 +1081,7 @@ static bool asset_shelf_asset_poll(const AssetShelfType *shelf_type, const Asset
|
|||
|
||||
ParameterList list;
|
||||
RNA_parameter_list_create(&list, &ptr, func);
|
||||
RNA_parameter_set_lookup(&list, "asset_handle", &asset);
|
||||
RNA_parameter_set_lookup(&list, "asset", &asset);
|
||||
shelf_type->rna_ext.call(nullptr, &ptr, func, &list);
|
||||
|
||||
void *ret;
|
||||
|
@ -1117,7 +1118,7 @@ static bool asset_shelf_poll(const bContext *C, const AssetShelfType *shelf_type
|
|||
|
||||
static void asset_shelf_draw_context_menu(const bContext *C,
|
||||
const AssetShelfType *shelf_type,
|
||||
const AssetHandle *asset,
|
||||
const AssetRepresentationHandle *asset,
|
||||
uiLayout *layout)
|
||||
{
|
||||
extern FunctionRNA rna_AssetShelf_draw_context_menu_func;
|
||||
|
@ -1130,7 +1131,7 @@ static void asset_shelf_draw_context_menu(const bContext *C,
|
|||
ParameterList list;
|
||||
RNA_parameter_list_create(&list, &ptr, func);
|
||||
RNA_parameter_set_lookup(&list, "context", &C);
|
||||
RNA_parameter_set_lookup(&list, "asset_handle", &asset);
|
||||
RNA_parameter_set_lookup(&list, "asset", &asset);
|
||||
RNA_parameter_set_lookup(&list, "layout", &layout);
|
||||
shelf_type->rna_ext.call((bContext *)C, &ptr, func, &list);
|
||||
|
||||
|
@ -2143,7 +2144,7 @@ static void rna_def_asset_shelf(BlenderRNA *brna)
|
|||
"non-null output, the asset will be visible");
|
||||
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL);
|
||||
RNA_def_function_return(func, RNA_def_boolean(func, "visible", true, "", ""));
|
||||
parm = RNA_def_pointer(func, "asset_handle", "AssetHandle", "", "");
|
||||
parm = RNA_def_pointer(func, "asset", "AssetRepresentation", "", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
|
||||
func = RNA_def_function(srna, "draw_context_menu", nullptr);
|
||||
|
@ -2152,7 +2153,7 @@ static void rna_def_asset_shelf(BlenderRNA *brna)
|
|||
RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_REGISTER_OPTIONAL);
|
||||
parm = RNA_def_pointer(func, "context", "Context", "", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(func, "asset_handle", "AssetHandle", "", "");
|
||||
parm = RNA_def_pointer(func, "asset", "AssetRepresentation", "", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(func, "layout", "UILayout", "", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
|
|
|
@ -124,7 +124,7 @@ static void node_gather_link_searches(GatherLinkSearchOpParams ¶ms)
|
|||
|
||||
/* If the source node has a geometry socket, connect it to the new viewer node as well. */
|
||||
LISTBASE_FOREACH (bNodeSocket *, socket, ¶ms.node.outputs) {
|
||||
if (socket->type == SOCK_GEOMETRY && socket->is_visible()) {
|
||||
if (socket->type == SOCK_GEOMETRY && socket->is_visible_or_panel_collapsed()) {
|
||||
nodeAddLink(¶ms.node_tree,
|
||||
¶ms.node,
|
||||
socket,
|
||||
|
|
|
@ -766,35 +766,47 @@ PyObject *PyC_FrozenSetFromStrings(const char **strings)
|
|||
|
||||
PyObject *PyC_Err_Format_Prefix(PyObject *exception_type_prefix, const char *format, ...)
|
||||
{
|
||||
PyObject *error_value_prefix;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
error_value_prefix = PyUnicode_FromFormatV(format, args); /* can fail and be nullptr */
|
||||
va_end(args);
|
||||
PyObject *error_value_as_unicode = nullptr;
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
PyObject *error_type, *error_value, *error_traceback;
|
||||
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||
|
||||
if (PyUnicode_Check(error_value)) {
|
||||
PyErr_Format(exception_type_prefix, "%S, %S", error_value_prefix, error_value);
|
||||
error_value_as_unicode = error_value;
|
||||
Py_INCREF(error_value_as_unicode);
|
||||
}
|
||||
else {
|
||||
PyErr_Format(exception_type_prefix,
|
||||
"%S, %.200s(%S)",
|
||||
error_value_prefix,
|
||||
Py_TYPE(error_value)->tp_name,
|
||||
error_value);
|
||||
error_value_as_unicode = PyUnicode_FromFormat(
|
||||
"%.200s(%S)", Py_TYPE(error_value)->tp_name, error_value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyErr_SetObject(exception_type_prefix, error_value_prefix);
|
||||
PyErr_Restore(error_type, error_value, error_traceback);
|
||||
}
|
||||
|
||||
Py_XDECREF(error_value_prefix);
|
||||
va_list args;
|
||||
|
||||
/* dumb to always return nullptr but matches PyErr_Format */
|
||||
va_start(args, format);
|
||||
PyObject *error_value_format = PyUnicode_FromFormatV(format, args); /* Can fail and be null. */
|
||||
va_end(args);
|
||||
|
||||
if (error_value_as_unicode) {
|
||||
if (error_value_format) {
|
||||
PyObject *error_value_format_prev = error_value_format;
|
||||
error_value_format = PyUnicode_FromFormat(
|
||||
"%S, %S", error_value_format, error_value_as_unicode);
|
||||
Py_DECREF(error_value_format_prev);
|
||||
}
|
||||
else {
|
||||
/* Should never happen, hints at incorrect API use or memory corruption. */
|
||||
error_value_format = PyUnicode_FromFormat("(internal error), %S", error_value_as_unicode);
|
||||
}
|
||||
Py_DECREF(error_value_as_unicode);
|
||||
}
|
||||
|
||||
PyErr_SetObject(exception_type_prefix, error_value_format);
|
||||
Py_XDECREF(error_value_format);
|
||||
|
||||
/* Strange to always return null, matches #PyErr_Format. */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1162,15 +1174,25 @@ bool PyC_NameSpace_ImportArray(PyObject *py_dict, const char *imports[])
|
|||
void PyC_MainModule_Backup(PyObject **r_main_mod)
|
||||
{
|
||||
PyObject *modules = PyImport_GetModuleDict();
|
||||
*r_main_mod = PyDict_GetItemString(modules, "__main__");
|
||||
Py_XINCREF(*r_main_mod); /* don't free */
|
||||
PyObject *main_mod = PyDict_GetItemString(modules, "__main__");
|
||||
if (main_mod) {
|
||||
/* Ensure the backed up module is kept until it's ownership */
|
||||
/* is transferred back to `sys.modules`. */
|
||||
Py_INCREF(main_mod);
|
||||
}
|
||||
*r_main_mod = main_mod;
|
||||
}
|
||||
|
||||
void PyC_MainModule_Restore(PyObject *main_mod)
|
||||
{
|
||||
PyObject *modules = PyImport_GetModuleDict();
|
||||
PyDict_SetItemString(modules, "__main__", main_mod);
|
||||
Py_XDECREF(main_mod);
|
||||
if (main_mod) {
|
||||
PyDict_SetItemString(modules, "__main__", main_mod);
|
||||
Py_DECREF(main_mod);
|
||||
}
|
||||
else {
|
||||
PyDict_DelItemString(modules, "__main__");
|
||||
}
|
||||
}
|
||||
|
||||
bool PyC_IsInterpreterActive()
|
||||
|
|
|
@ -1665,11 +1665,11 @@ static int pyrna_py_to_prop(
|
|||
}
|
||||
}
|
||||
else {
|
||||
PyC_Err_Format_Prefix(PyExc_TypeError,
|
||||
"%.200s %.200s.%.200s doesn't support None from string types",
|
||||
error_prefix,
|
||||
RNA_struct_identifier(ptr->type),
|
||||
RNA_property_identifier(prop));
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"%.200s %.200s.%.200s doesn't support None from string types",
|
||||
error_prefix,
|
||||
RNA_struct_identifier(ptr->type),
|
||||
RNA_property_identifier(prop));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
|
||||
#include "bpy_traceback.h"
|
||||
|
||||
static const char *traceback_filepath(PyTracebackObject *tb, PyObject **coerce)
|
||||
static const char *traceback_filepath(PyTracebackObject *tb, PyObject **r_coerce)
|
||||
{
|
||||
PyCodeObject *code = PyFrame_GetCode(tb->tb_frame);
|
||||
*coerce = PyUnicode_EncodeFSDefault(code->co_filename);
|
||||
return PyBytes_AS_STRING(*coerce);
|
||||
*r_coerce = PyUnicode_EncodeFSDefault(code->co_filename);
|
||||
return PyBytes_AS_STRING(*r_coerce);
|
||||
}
|
||||
|
||||
#define MAKE_PY_IDENTIFIER_EX(varname, value) static _Py_Identifier varname{value, -1};
|
||||
|
@ -170,13 +170,8 @@ finally:
|
|||
bool python_script_error_jump(
|
||||
const char *filepath, int *r_lineno, int *r_offset, int *r_lineno_end, int *r_offset_end)
|
||||
{
|
||||
/* WARNING(@ideasman42): The normalized exception is restored (losing line number info).
|
||||
* Ideally this would leave the exception state as it found it, but that needs to be done
|
||||
* carefully with regards to reference counting, see: #97731. */
|
||||
|
||||
bool success = false;
|
||||
PyObject *exception, *value;
|
||||
PyTracebackObject *tb;
|
||||
PyObject *exception, *value, *tb;
|
||||
|
||||
*r_lineno = -1;
|
||||
*r_offset = 0;
|
||||
|
@ -185,14 +180,19 @@ bool python_script_error_jump(
|
|||
*r_offset_end = 0;
|
||||
|
||||
PyErr_Fetch(&exception, &value, (PyObject **)&tb);
|
||||
if (exception == nullptr) {
|
||||
if (exception == nullptr) { /* Equivalent of `!PyErr_Occurred()`. */
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObject *base_exception_type = nullptr;
|
||||
if (PyErr_GivenExceptionMatches(exception, PyExc_SyntaxError)) {
|
||||
base_exception_type = PyExc_SyntaxError;
|
||||
}
|
||||
|
||||
PyErr_NormalizeException(&exception, &value, &tb);
|
||||
|
||||
if (base_exception_type == PyExc_SyntaxError) {
|
||||
/* No trace-back available when `SyntaxError`.
|
||||
* Python has no API's to this. reference #parse_syntax_error() from `pythonrun.c`. */
|
||||
PyErr_NormalizeException(&exception, &value, (PyObject **)&tb);
|
||||
* Python has no API for this. reference #parse_syntax_error() from `pythonrun.c`. */
|
||||
|
||||
if (value) { /* Should always be true. */
|
||||
PyObject *message;
|
||||
|
@ -208,7 +208,7 @@ bool python_script_error_jump(
|
|||
&text_py))
|
||||
{
|
||||
const char *filepath_exc = PyUnicode_AsUTF8(filepath_exc_py);
|
||||
/* python adds a '/', prefix, so check for both */
|
||||
/* Python adds a '/', prefix, so check for both. */
|
||||
if ((BLI_path_cmp(filepath_exc, filepath) == 0) ||
|
||||
(ELEM(filepath_exc[0], '\\', '/') && BLI_path_cmp(filepath_exc + 1, filepath) == 0))
|
||||
{
|
||||
|
@ -218,28 +218,28 @@ bool python_script_error_jump(
|
|||
}
|
||||
}
|
||||
else {
|
||||
PyErr_NormalizeException(&exception, &value, (PyObject **)&tb);
|
||||
|
||||
for (tb = (PyTracebackObject *)PySys_GetObject("last_traceback");
|
||||
tb && (PyObject *)tb != Py_None;
|
||||
tb = tb->tb_next)
|
||||
BLI_assert((tb == PySys_GetObject("last_traceback")) ||
|
||||
(tb == nullptr && PySys_GetObject("last_traceback") == Py_None));
|
||||
for (PyTracebackObject *tb_iter = (PyTracebackObject *)tb;
|
||||
tb_iter && (PyObject *)tb_iter != Py_None;
|
||||
tb_iter = tb_iter->tb_next)
|
||||
{
|
||||
PyObject *coerce;
|
||||
const char *tb_filepath = traceback_filepath(tb, &coerce);
|
||||
const char *tb_filepath = traceback_filepath(tb_iter, &coerce);
|
||||
const int match = ((BLI_path_cmp(tb_filepath, filepath) == 0) ||
|
||||
(ELEM(tb_filepath[0], '\\', '/') &&
|
||||
BLI_path_cmp(tb_filepath + 1, filepath) == 0));
|
||||
Py_DECREF(coerce);
|
||||
|
||||
if (match) {
|
||||
/* Even though a match has been found, keep searching to find the inner most line. */
|
||||
success = true;
|
||||
*r_lineno = *r_lineno_end = tb->tb_lineno;
|
||||
/* used to break here, but better find the inner most line */
|
||||
*r_lineno = *r_lineno_end = tb_iter->tb_lineno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_Restore(exception, value, (PyObject *)tb); /* takes away reference! */
|
||||
PyErr_Restore(exception, value, tb);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -1934,6 +1934,7 @@ static ImBuf *seq_render_strip_stack(const SeqRenderData *context,
|
|||
ImBuf *ibuf2 = seq_render_strip(context, state, seq, timeline_frame);
|
||||
|
||||
out = seq_render_strip_stack_apply_effect(context, seq, timeline_frame, ibuf1, ibuf2);
|
||||
IMB_metadata_copy(out, ibuf2);
|
||||
|
||||
seq_cache_put(context, seq_arr[i], timeline_frame, SEQ_CACHE_STORE_COMPOSITE, out);
|
||||
|
||||
|
|
|
@ -5670,7 +5670,9 @@ void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, const int type,
|
|||
event.utf8_buf[0] = '\0';
|
||||
}
|
||||
else {
|
||||
if (event.utf8_buf[0] < 32 && event.utf8_buf[0] > 0) {
|
||||
/* Check for ASCII control characters.
|
||||
* Inline `iscntrl` because the users locale must not change behavior. */
|
||||
if ((event.utf8_buf[0] < 32 && event.utf8_buf[0] > 0) || (event.utf8_buf[0] == 127)) {
|
||||
event.utf8_buf[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase):
|
|||
ob = bpy.context.view_layer.objects.active
|
||||
action = ob.animation_data.action
|
||||
return [action.fcurves.find('rotation_euler', index=idx) for idx in range(3)]
|
||||
|
||||
|
||||
|
||||
def get_view3d_context():
|
||||
ctx = bpy.context.copy()
|
||||
|
@ -173,7 +173,6 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
super().setUp()
|
||||
bpy.ops.wm.read_homefile()
|
||||
|
||||
|
||||
def test_keyframe_insertion_basic(self):
|
||||
bpy.ops.mesh.primitive_monkey_add()
|
||||
key_count = 100
|
||||
|
@ -186,7 +185,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
self.assertEqual(key.co.x, key_index)
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_insertion_high_frame_number(self):
|
||||
|
@ -201,8 +200,8 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
key_object = bpy.context.active_object
|
||||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
self.assertEqual(key.co.x, key_index+frame_offset)
|
||||
|
||||
self.assertEqual(key.co.x, key_index + frame_offset)
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_insertion_subframes_basic(self):
|
||||
|
@ -210,14 +209,14 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
key_count = 50
|
||||
with bpy.context.temp_override(**get_view3d_context()):
|
||||
for i in range(key_count):
|
||||
bpy.context.scene.frame_set(0, subframe=i/key_count)
|
||||
bpy.context.scene.frame_set(0, subframe=i / key_count)
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
self.assertAlmostEqual(key.co.x, key_index/key_count)
|
||||
|
||||
self.assertAlmostEqual(key.co.x, key_index / key_count)
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_insertion_subframes_high_frame_number(self):
|
||||
|
@ -226,7 +225,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
frame_offset = 1000000
|
||||
with bpy.context.temp_override(**get_view3d_context()):
|
||||
for i in range(key_count):
|
||||
bpy.context.scene.frame_set(frame_offset, subframe=i/key_count)
|
||||
bpy.context.scene.frame_set(frame_offset, subframe=i / key_count)
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
|
@ -248,7 +247,8 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
1000000.8125,
|
||||
1000000.875,
|
||||
1000000.9375,
|
||||
1000001.0 # Even though range() is exclusive, the floating point limitations mean keys end up on that position.
|
||||
# Even though range() is exclusive, the floating point limitations mean keys end up on that position.
|
||||
1000001.0
|
||||
]
|
||||
keyframe_points = key_object.animation_data.action.fcurves[0].keyframe_points
|
||||
for i, value in enumerate(floating_point_steps):
|
||||
|
@ -257,9 +257,9 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
|||
|
||||
# This checks that there is a key on every possible floating point value and not more than that.
|
||||
self.assertEqual(len(floating_point_steps), len(keyframe_points))
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
|
||||
class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -285,7 +285,7 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
|||
|
||||
# Only the key on frame -1 should be left
|
||||
self.assertEqual(len(fcu.keyframe_points), 1)
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_deletion_high_frame_number(self):
|
||||
|
@ -299,16 +299,16 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
|||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i+frame_offset, value=0)
|
||||
fcu.keyframe_points.insert(frame=i + frame_offset, value=0)
|
||||
|
||||
with bpy.context.temp_override(**get_view3d_context()):
|
||||
for frame in range(key_count):
|
||||
bpy.context.scene.frame_set(frame+frame_offset)
|
||||
bpy.context.scene.frame_set(frame + frame_offset)
|
||||
bpy.ops.anim.keyframe_delete_by_name(type="Location")
|
||||
|
||||
# Only the key on frame -1 should be left
|
||||
self.assertEqual(len(fcu.keyframe_points), 1)
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_deletion_subframe_basic(self):
|
||||
|
@ -321,16 +321,16 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
|||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i/key_count, value=0)
|
||||
fcu.keyframe_points.insert(frame=i / key_count, value=0)
|
||||
|
||||
with bpy.context.temp_override(**get_view3d_context()):
|
||||
for frame in range(key_count):
|
||||
bpy.context.scene.frame_set(0, subframe=frame/key_count)
|
||||
bpy.context.scene.frame_set(0, subframe=frame / key_count)
|
||||
bpy.ops.anim.keyframe_delete_by_name(type="Location")
|
||||
|
||||
# Only the key on frame -1 should be left
|
||||
self.assertEqual(len(fcu.keyframe_points), 1)
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
def test_keyframe_deletion_subframe_high_frame_number(self):
|
||||
|
@ -344,20 +344,20 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
|||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i/key_count+frame_offset, value=0)
|
||||
fcu.keyframe_points.insert(frame=i / key_count + frame_offset, value=0)
|
||||
|
||||
with bpy.context.temp_override(**get_view3d_context()):
|
||||
for frame in range(key_count):
|
||||
bpy.context.scene.frame_set(frame_offset, subframe=frame/key_count)
|
||||
bpy.context.scene.frame_set(frame_offset, subframe=frame / key_count)
|
||||
bpy.ops.anim.keyframe_delete_by_name(type="Location")
|
||||
|
||||
# Only the key on frame -1 should be left
|
||||
# This works even though there are floating point precision issues,
|
||||
# This works even though there are floating point precision issues,
|
||||
# because the deletion has the exact same precision as the insertion.
|
||||
# Due to that, the code calls keyframe_delete_by_name for
|
||||
# Due to that, the code calls keyframe_delete_by_name for
|
||||
# every floating point step multiple times.
|
||||
self.assertEqual(len(fcu.keyframe_points), 1)
|
||||
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue