WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 286 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
116 changed files with 3788 additions and 1569 deletions

View File

@ -107,10 +107,6 @@ def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
kmi_hack_properties = kmi_hack.properties
kmi_hack.active = False
kmi_hack_brush_select = keymap.keymap_items.new("paint.brush_select", 'NONE', 'PRESS')
kmi_hack_brush_select_properties = kmi_hack_brush_select.properties
kmi_hack_brush_select.active = False
if use_release_confirm or use_tap_reset:
kmi_toolbar = wm.keyconfigs.find_item_from_operator(
idname="wm.toolbar",
@ -169,47 +165,6 @@ def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
include={'KEYBOARD'},
)[1]
if kmi_found is None:
if item.data_block:
# PAINT_OT_brush_select
mode = context.active_object.mode
# See: BKE_paint_get_tool_prop_id_from_paintmode
if space_type == 'IMAGE_EDITOR':
if context.space_data.mode == 'PAINT':
attr = "image_tool"
else:
attr = None
elif space_type == 'VIEW_3D':
attr = {
'SCULPT': "sculpt_tool",
'VERTEX_PAINT': "vertex_tool",
'WEIGHT_PAINT': "weight_tool",
'TEXTURE_PAINT': "image_tool",
'PAINT_GPENCIL': "gpencil_tool",
'PAINT_GREASE_PENCIL': "gpencil_tool",
'VERTEX_GPENCIL': "gpencil_vertex_tool",
'SCULPT_GPENCIL': "gpencil_sculpt_tool",
'WEIGHT_GPENCIL': "gpencil_weight_tool",
'SCULPT_CURVES': "curves_sculpt_tool",
}.get(mode, None)
else:
attr = None
if attr is not None:
setattr(kmi_hack_brush_select_properties, attr, item.data_block)
kmi_found = wm.keyconfigs.find_item_from_operator(
idname="paint.brush_select",
context='INVOKE_REGION_WIN',
properties=kmi_hack_brush_select_properties,
include={'KEYBOARD'},
)[1]
elif mode in {'EDIT', 'PARTICLE_EDIT', 'SCULPT_GPENCIL'}:
# Doesn't use brushes
pass
else:
print("Unsupported mode:", mode)
del mode, attr
else:
kmi_found = None
@ -402,7 +357,6 @@ def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
if use_hack_properties:
keymap.keymap_items.remove(kmi_hack)
keymap.keymap_items.remove(kmi_hack_brush_select)
# Keep last so we can try add a key without any modifiers
# in the case this toolbar was activated with modifiers.

View File

@ -5594,27 +5594,37 @@ def km_sculpt(params):
op_menu_pie("VIEW3D_MT_sculpt_automasking_pie", {"type": 'A', "alt": True, "value": 'PRESS'}),
op_menu_pie("VIEW3D_MT_sculpt_face_sets_edit_pie", {"type": 'W', "value": 'PRESS', "alt": True}),
*_template_items_context_panel("VIEW3D_PT_sculpt_context_menu", params.context_menu_event),
# Tools
("paint.brush_select", {"type": 'V', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'DRAW')]}),
("paint.brush_select", {"type": 'S', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'SMOOTH')]}),
("paint.brush_select", {"type": 'P', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'PINCH')]}),
("paint.brush_select", {"type": 'I', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'INFLATE')]}),
("paint.brush_select", {"type": 'G', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'GRAB')]}),
("paint.brush_select", {"type": 'T', "value": 'PRESS', "shift": True},
{"properties": [("sculpt_tool", 'SCRAPE')]}),
("paint.brush_select", {"type": 'C', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'CLAY_STRIPS')]}),
("paint.brush_select", {"type": 'C', "value": 'PRESS', "shift": True},
{"properties": [("sculpt_tool", 'CREASE')]}),
("paint.brush_select", {"type": 'K', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'SNAKE_HOOK')]}),
("paint.brush_select", {"type": 'M', "value": 'PRESS'},
{"properties": [("sculpt_tool", 'MASK'), ("toggle", True), ("create_missing", True)]}),
# Brushes
("brush.asset_select", {"type": 'V', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Draw")]}),
("brush.asset_select", {"type": 'S', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Smooth")]}),
("brush.asset_select", {"type": 'P', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Pinch/Magnify")]}),
("brush.asset_select", {"type": 'I', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Inflate/Deflate")]}),
("brush.asset_select", {"type": 'G', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Grab")]}),
("brush.asset_select", {"type": 'T', "value": 'PRESS', "shift": True},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Scrape/Fill")]}),
("brush.asset_select", {"type": 'C', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Clay Strips")]}),
("brush.asset_select", {"type": 'C', "value": 'PRESS', "shift": True},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Crease")]}),
("brush.asset_select", {"type": 'K', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Snake Hook")]}),
("brush.asset_select", {"type": 'M', "value": 'PRESS'},
{"properties": [("asset_library_type", 'ESSENTIALS'),
("relative_asset_identifier", "brushes/essentials_brushes.blend/Brush/Mask")]}),
])
# Lasso Masking.

View File

@ -151,19 +151,27 @@ class BrushSelectPanel(BrushPanel):
brush = settings.brush
row = layout.row()
large_preview = True
if large_preview:
row.column().template_ID_preview(settings, "brush", new="brush.add", rows=3, cols=8, hide_buttons=False)
else:
row.column().template_ID(settings, "brush", new="brush.add")
# TODO: hide buttons since they are confusing with menu entries.
# But some of this functionality may still be needed.
row.column().template_ID_preview(settings, "brush", rows=3, cols=8, hide_buttons=True)
if brush is None:
return
col = row.column()
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
if brush is not None:
col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="")
if not brush.is_asset_library_data and not brush.asset_data:
# Legacy custom icon, mostly replaced by asset preview.
layout.use_property_split = True
layout.use_property_decorate = False
if brush.use_custom_icon:
layout.prop(brush, "icon_filepath", text="")
col = layout.column(heading="Custom Icon", align=True)
row = col.row()
row.prop(brush, "use_custom_icon", text="")
sub = row.row()
sub.active = brush.use_custom_icon
sub.prop(brush, "icon_filepath", text="")
class ColorPalettePanel(BrushPanel):
@ -985,6 +993,9 @@ def brush_settings_advanced(layout, context, brush, popover=False):
use_frontface = False
if mode == 'SCULPT':
layout.prop(brush, "sculpt_tool")
layout.separator()
capabilities = brush.sculpt_capabilities
use_accumulate = capabilities.has_accumulate
use_frontface = True
@ -1075,6 +1086,9 @@ def brush_settings_advanced(layout, context, brush, popover=False):
# 3D and 2D Texture Paint.
elif mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
layout.prop(brush, "image_tool")
layout.separator()
capabilities = brush.image_paint_capabilities
use_accumulate = capabilities.has_accumulate
@ -1102,6 +1116,9 @@ def brush_settings_advanced(layout, context, brush, popover=False):
# Vertex Paint #
elif mode == 'PAINT_VERTEX':
layout.prop(brush, "vertex_tool")
layout.separator()
layout.prop(brush, "use_alpha")
if brush.vertex_tool != 'SMEAR':
use_accumulate = True
@ -1109,10 +1126,17 @@ def brush_settings_advanced(layout, context, brush, popover=False):
# Weight Paint
elif mode == 'PAINT_WEIGHT':
layout.prop(brush, "weight_tool")
layout.separator()
if brush.weight_tool != 'SMEAR':
use_accumulate = True
use_frontface = True
# Sculpt Curves
elif mode == 'SCULPT_CURVES':
layout.prop(brush, "curves_sculpt_tool")
# Draw shared settings.
if use_accumulate:
layout.prop(brush, "use_accumulate")
@ -1120,6 +1144,21 @@ def brush_settings_advanced(layout, context, brush, popover=False):
if use_frontface:
layout.prop(brush, "use_frontface", text="Front Faces Only")
# Brush modes
header, panel = layout.panel("modes", default_closed=True)
header.label(text="Modes")
if panel:
panel.use_property_split = True
panel.use_property_decorate = False
col = panel.column(align=True)
col.prop(brush, "use_paint_sculpt", text="Sculpt")
col.prop(brush, "use_paint_uv_sculpt", text="UV Sculpt")
col.prop(brush, "use_paint_vertex", text="Vertex Paint")
col.prop(brush, "use_paint_weight", text="Weight Paint")
col.prop(brush, "use_paint_image", text="Texture Paint")
col.prop(brush, "use_paint_sculpt_curves", text="Sculpt Curves")
def draw_color_settings(context, layout, brush, color_type=False):
"""Draw color wheel and gradient settings."""

View File

@ -3,6 +3,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import (
AssetShelf,
Header,
Menu,
Panel,
@ -30,6 +31,9 @@ from bl_ui.properties_grease_pencil_common import (
from bl_ui.space_toolsystem_common import (
ToolActivePanelHelper,
)
from bl_ui.space_view3d import (
BrushAssetShelf,
)
from bpy.app.translations import (
contexts as i18n_contexts,
@ -1688,6 +1692,15 @@ class IMAGE_PT_annotation(AnnotationDataPanel, Panel):
# Grease Pencil drawing tools.
class ImageAssetShelf(BrushAssetShelf):
bl_space_type = "IMAGE_EDITOR"
class IMAGE_AST_brush_sculpt(ImageAssetShelf, AssetShelf):
mode = 'TEXTURE_PAINT'
mode_prop = "use_paint_image"
classes = (
IMAGE_MT_view,
IMAGE_MT_view_zoom,
@ -1757,6 +1770,7 @@ classes = (
IMAGE_PT_overlay_uv_edit_geometry,
IMAGE_PT_overlay_texture_paint,
IMAGE_PT_overlay_image,
IMAGE_AST_brush_sculpt,
)

View File

@ -1396,16 +1396,23 @@ class _defs_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.sculpt.",
type=bpy.types.Brush,
attr="sculpt_tool",
# TODO(@ideasman42): we may want to enable this,
# it causes awkward grouping with 2x column button layout.
use_separators=False,
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.sculpt.brush
if brush:
tool = brush.sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
@ToolDef.from_fn
def hide_border():
@ -1694,13 +1701,23 @@ class _defs_vertex_paint:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.paint_vertex.",
type=bpy.types.Brush,
attr="vertex_tool",
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.vertex_paint.brush
if brush:
tool = brush.vertex_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_texture_paint:
@ -1715,14 +1732,24 @@ class _defs_texture_paint:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.paint_texture.",
type=bpy.types.Brush,
attr="image_tool",
cursor='PAINT_CROSS',
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.image_paint.brush
if brush:
tool = brush.image_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
cursor='PAINT_CROSS',
data_block=tool
)
)
]
class _defs_weight_paint:
@ -1742,13 +1769,23 @@ class _defs_weight_paint:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.paint_weight.",
type=bpy.types.Brush,
attr="weight_tool",
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.weight_paint.brush
if brush:
tool = brush.weight_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
@ToolDef.from_fn
def sample_weight():
@ -1821,36 +1858,25 @@ class _defs_weight_paint:
class _defs_paint_grease_pencil:
# FIXME: Replace brush tools with code below once they are all implemented:
#
# @staticmethod
# def generate_from_brushes(context):
# return generate_from_enum_ex(
# context,
# idname_prefix="builtin_brush.",
# icon_prefix="brush.gpencil_draw.",
# type=bpy.types.Brush,
# attr="gpencil_tool",
# cursor='DOT',
# )
@ToolDef.from_fn
def draw():
return dict(
idname="builtin_brush.Draw",
label="Draw",
icon="brush.gpencil_draw.draw",
data_block='DRAW',
)
@ToolDef.from_fn
def erase():
return dict(
idname="builtin_brush.Erase",
label="Erase",
icon="brush.gpencil_draw.erase",
data_block='ERASE',
)
@staticmethod
def generate_from_brushes(context):
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_paint.brush
if brush:
tool = brush.gpencil_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
@ToolDef.from_fn
def cutter():
@ -2288,17 +2314,23 @@ class _defs_gpencil_paint:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.gpencil_draw.",
type=bpy.types.Brush,
attr="gpencil_tool",
cursor='DOT',
tooldef_keywords=dict(
operator="gpencil.draw",
),
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_paint.brush
if brush:
tool = brush.gpencil_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
@ToolDef.from_fn
def cutter():
@ -2654,16 +2686,24 @@ class _defs_gpencil_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.gpencil.sculpt_",
type=bpy.types.Brush,
attr="gpencil_sculpt_tool",
tooldef_keywords=dict(
operator="gpencil.sculpt_paint",
),
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_sculpt_paint.brush
if brush:
tool = brush.gpencil_sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_grease_pencil_sculpt:
@ -2684,33 +2724,46 @@ class _defs_grease_pencil_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.gpencil.sculpt_",
type=bpy.types.Brush,
# Uses GPv2 tool settings
attr="gpencil_sculpt_tool",
tooldef_keywords=dict(
operator="grease_pencil.sculpt_paint",
),
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_sculpt_paint.brush
if brush:
tool = brush.gpencil_sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_gpencil_weight:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.gpencil.sculpt_",
type=bpy.types.Brush,
attr="gpencil_weight_tool",
tooldef_keywords=dict(
operator="gpencil.weight_paint",
),
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_weight_paint.brush
if brush:
tool = brush.gpencil_weight_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_grease_pencil_weight:
@ -2734,17 +2787,23 @@ class _defs_curves_sculpt:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="ops.curves.sculpt_",
type=bpy.types.Brush,
attr="curves_sculpt_tool",
icon_map={
# Use the generic icon for selection painting.
"ops.curves.sculpt_selection_paint": "ops.generic.select_paint",
},
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.curves_sculpt.brush
if brush:
tool = brush.curves_sculpt_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_gpencil_vertex:
@ -2766,18 +2825,23 @@ class _defs_gpencil_vertex:
@staticmethod
def generate_from_brushes(context):
return generate_from_enum_ex(
context,
idname_prefix="builtin_brush.",
icon_prefix="brush.paint_vertex.",
type=bpy.types.Brush,
attr="gpencil_vertex_tool",
cursor='DOT',
tooldef_keywords=dict(
operator="gpencil.vertex_paint",
),
)
# Though `data_block` is conceptually unnecessary with a single brush tool,
# it's still used because many areas assume that brush tools have it set #bToolRef.
tool = None
if context:
brush = context.tool_settings.gpencil_vertex_paint.brush
if brush:
tool = brush.gpencil_vertex_tool
return [
ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
data_block=tool
)
)
]
class _defs_node_select:
@ -3466,8 +3530,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
'PAINT_GREASE_PENCIL': [
_defs_view3d_generic.cursor,
None,
_defs_paint_grease_pencil.draw,
_defs_paint_grease_pencil.erase,
_defs_paint_grease_pencil.generate_from_brushes,
_defs_paint_grease_pencil.cutter,
_defs_paint_grease_pencil.tint,
None,

View File

@ -235,9 +235,18 @@ class _draw_tool_settings_context_mode:
return False
paint = context.tool_settings.sculpt
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
brush = paint.brush
brush_name = brush.name if brush else None
preview_icon_id = brush.preview.icon_id if brush and brush.preview else 0
fallback_icon = 'BRUSH_DATA' if not preview_icon_id else 'NONE'
layout.template_asset_shelf_popover(
BrushAssetShelf.get_shelf_name_from_mode(context.object.mode),
name=brush_name,
icon=fallback_icon,
icon_value=preview_icon_id,
)
if brush is None:
return False
@ -3481,23 +3490,6 @@ class VIEW3D_MT_make_links(Menu):
layout.operator("object.datalayout_transfer")
class VIEW3D_MT_brush_paint_modes(Menu):
bl_label = "Enabled Modes"
def draw(self, context):
layout = self.layout
settings = UnifiedPaintPanel.paint_settings(context)
brush = settings.brush
layout.prop(brush, "use_paint_sculpt", text="Sculpt")
layout.prop(brush, "use_paint_uv_sculpt", text="UV Sculpt")
layout.prop(brush, "use_paint_vertex", text="Vertex Paint")
layout.prop(brush, "use_paint_weight", text="Weight Paint")
layout.prop(brush, "use_paint_image", text="Texture Paint")
layout.prop(brush, "use_paint_sculpt_curves", text="Sculpt Curves")
class VIEW3D_MT_paint_vertex(Menu):
bl_label = "Paint"
@ -9012,24 +9004,104 @@ class VIEW3D_PT_viewport_debug(Panel):
layout.prop(overlay, "use_debug_freeze_view_culling")
class VIEW3D_AST_sculpt_brushes(bpy.types.AssetShelf):
# Experimental: Asset shelf for sculpt brushes, only shows up if both the
# "Asset Shelf" and the "Extended Asset Browser" experimental features are
# enabled.
bl_space_type = 'VIEW_3D'
class BrushAssetShelf:
bl_options = {'DEFAULT_VISIBLE', 'NO_ASSET_DRAG', 'STORE_ENABLED_CATALOGS_IN_PREFERENCES'}
bl_activate_operator = "BRUSH_OT_asset_select"
bl_default_preview_size = 48
@classmethod
def poll(cls, context):
prefs = context.preferences
if not prefs.experimental.use_extended_asset_browser:
return False
return context.mode == 'SCULPT'
return context.object and context.object.mode == cls.mode
@classmethod
def asset_poll(cls, asset):
return asset.id_type == 'BRUSH'
if asset.id_type != 'BRUSH':
return False
return asset.metadata.get(cls.mode_prop, False)
@classmethod
def get_active_asset(cls):
paint_settings = UnifiedPaintPanel.paint_settings(bpy.context)
return paint_settings.brush_asset_reference if paint_settings else None
@classmethod
def draw_context_menu(self, context, asset, layout):
# Currently this menu adds operators that deal with the affected brush and don't take the
# asset into account. Luckily that is okay for now, since right clicking in the grid view
# also activates the item.
layout.menu_contents("VIEW3D_MT_brush_context_menu")
# Not nice, but needed unfortunately.
@staticmethod
def get_shelf_name_from_mode(obmode):
mode_map = {
'SCULPT': "VIEW3D_AST_brush_sculpt",
'SCULPT_CURVES': "VIEW3D_AST_brush_sculpt_curves",
'VERTEX_PAINT': "VIEW3D_AST_brush_vertex_paint",
'WEIGHT_PAINT': "VIEW3D_AST_brush_weight_paint",
'TEXTURE_PAINT': "VIEW3D_AST_brush_texture_paint",
'PAINT_GPENCIL': "VIEW3D_AST_brush_gpencil_paint",
'PAINT_GREASE_PENCIL': "VIEW3D_AST_brush_grease_pencil_paint",
'SCULPT_GPENCIL': "VIEW3D_AST_brush_gpencil_sculpt",
'VERTEX_GPENCIL': "VIEW3D_AST_brush_gpencil_vertex",
'WEIGHT_GPENCIL': "VIEW3D_AST_brush_gpencil_weight",
}
return mode_map[obmode]
class View3DAssetShelf(BrushAssetShelf):
bl_space_type = "VIEW_3D"
class VIEW3D_AST_brush_sculpt(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT'
mode_prop = "use_paint_sculpt"
class VIEW3D_AST_brush_sculpt_curves(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT_CURVES'
mode_prop = "use_paint_sculpt_curves"
class VIEW3D_AST_brush_vertex_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'VERTEX_PAINT'
mode_prop = "use_paint_vertex"
class VIEW3D_AST_brush_weight_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'WEIGHT_PAINT'
mode_prop = "use_paint_weight"
class VIEW3D_AST_brush_texture_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'TEXTURE_PAINT'
mode_prop = "use_paint_image"
class VIEW3D_AST_brush_gpencil_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'PAINT_GPENCIL'
mode_prop = "use_paint_grease_pencil"
class VIEW3D_AST_brush_grease_pencil_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'PAINT_GREASE_PENCIL'
mode_prop = "use_paint_grease_pencil"
class VIEW3D_AST_brush_gpencil_sculpt(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT_GPENCIL'
mode_prop = "use_sculpt_grease_pencil"
class VIEW3D_AST_brush_gpencil_vertex(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'VERTEX_GPENCIL'
mode_prop = "use_vertex_grease_pencil"
class VIEW3D_AST_brush_gpencil_weight(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'WEIGHT_GPENCIL'
mode_prop = "use_weight_grease_pencil"
classes = (
@ -9110,7 +9182,6 @@ classes = (
VIEW3D_MT_object_cleanup,
VIEW3D_MT_make_single_user,
VIEW3D_MT_make_links,
VIEW3D_MT_brush_paint_modes,
VIEW3D_MT_paint_vertex,
VIEW3D_MT_hook,
VIEW3D_MT_vertex_group,
@ -9295,7 +9366,16 @@ classes = (
VIEW3D_PT_curves_sculpt_parameter_falloff,
VIEW3D_PT_curves_sculpt_grow_shrink_scaling,
VIEW3D_PT_viewport_debug,
VIEW3D_AST_sculpt_brushes,
VIEW3D_AST_brush_sculpt,
VIEW3D_AST_brush_sculpt_curves,
VIEW3D_AST_brush_vertex_paint,
VIEW3D_AST_brush_weight_paint,
VIEW3D_AST_brush_texture_paint,
VIEW3D_AST_brush_gpencil_paint,
VIEW3D_AST_brush_grease_pencil_paint,
VIEW3D_AST_brush_gpencil_sculpt,
VIEW3D_AST_brush_gpencil_vertex,
VIEW3D_AST_brush_gpencil_weight,
)

View File

@ -43,25 +43,21 @@ class VIEW3D_MT_brush_context_menu(Menu):
# skip if no active brush
if not brush:
layout.label(text="No Brushes currently available", icon='INFO')
layout.label(text="No brush selected", icon='INFO')
return
# brush paint modes
layout.menu("VIEW3D_MT_brush_paint_modes")
if brush.is_asset_library_data:
layout.operator("brush.asset_save_as", text="Duplicate Asset...", icon='DUPLICATE')
layout.operator("brush.asset_delete", text="Delete Asset")
# brush tool
layout.separator()
if context.image_paint_object:
layout.prop_menu_enum(brush, "image_tool")
elif context.vertex_paint_object:
layout.prop_menu_enum(brush, "vertex_tool")
elif context.weight_paint_object:
layout.prop_menu_enum(brush, "weight_tool")
elif context.sculpt_object:
layout.prop_menu_enum(brush, "sculpt_tool")
layout.operator("brush.reset")
elif context.tool_settings.curves_sculpt:
layout.prop_menu_enum(brush, "curves_sculpt_tool")
layout.operator("brush.asset_edit_metadata", text="Edit Metadata")
layout.operator("brush.asset_update", text="Update Asset")
layout.operator("brush.asset_revert", text="Revert to Asset")
else:
layout.operator("brush.asset_save_as", text="Save As Asset...", icon='FILE_TICK')
layout.operator("brush.asset_delete", text="Delete")
class VIEW3D_MT_brush_gpencil_context_menu(Menu):

View File

@ -114,6 +114,9 @@ class AssetLibrary {
*/
static void foreach_loaded(FunctionRef<void(AssetLibrary &)> fn, bool include_all_library);
static std::string resolve_asset_weak_reference_to_full_path(
const AssetWeakReference &asset_reference);
void load_catalogs();
AssetCatalogService &catalog_service() const;
@ -172,8 +175,6 @@ class AssetLibrary {
*/
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
std::string resolve_asset_weak_reference_to_full_path(const AssetWeakReference &asset_reference);
eAssetLibraryType library_type() const;
StringRefNull name() const;
StringRefNull root_path() const;

View File

@ -70,6 +70,74 @@ TEST_F(AssetRepresentationTest, weak_reference__custom_library)
}
}
TEST_F(AssetRepresentationTest, weak_reference__compare)
{
{
AssetWeakReference a;
AssetWeakReference b;
EXPECT_EQ(a, b);
/* Arbitrary individual member changes to test how it affects the comparison. */
b.asset_library_identifier = "My lib";
EXPECT_NE(a, b);
a.asset_library_identifier = "My lib";
EXPECT_EQ(a, b);
a.asset_library_type = ASSET_LIBRARY_ESSENTIALS;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_LOCAL;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_ESSENTIALS;
EXPECT_EQ(a, b);
a.relative_asset_identifier = "Foo";
EXPECT_NE(a, b);
b.relative_asset_identifier = "Bar";
EXPECT_NE(a, b);
a.relative_asset_identifier = "Bar";
EXPECT_EQ(a, b);
/* Make the destructor work. */
a.asset_library_identifier = b.asset_library_identifier = nullptr;
a.relative_asset_identifier = b.relative_asset_identifier = nullptr;
}
{
AssetWeakReference a;
a.asset_library_type = ASSET_LIBRARY_LOCAL;
a.asset_library_identifier = "My custom lib";
a.relative_asset_identifier = "path/to/an/asset";
AssetWeakReference b;
EXPECT_NE(a, b);
b.asset_library_type = ASSET_LIBRARY_LOCAL;
b.asset_library_identifier = "My custom lib";
b.relative_asset_identifier = "path/to/an/asset";
EXPECT_EQ(a, b);
/* Make the destructor work. */
a.asset_library_identifier = b.asset_library_identifier = nullptr;
a.relative_asset_identifier = b.relative_asset_identifier = nullptr;
}
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference weak_ref = asset.make_weak_reference();
AssetWeakReference other;
other.asset_library_type = ASSET_LIBRARY_CUSTOM;
other.asset_library_identifier = "My custom lib";
other.relative_asset_identifier = "path/to/an/asset";
EXPECT_EQ(weak_ref, other);
/* Make the destructor work. */
other.asset_library_identifier = nullptr;
other.relative_asset_identifier = nullptr;
}
}
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__current_file)
{
AssetLibraryService *service = AssetLibraryService::get();

View File

@ -11,6 +11,7 @@
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
#include "DNA_ID_enums.h"
#include "DNA_asset_types.h"
struct AssetLibraryReference;
@ -20,6 +21,7 @@ struct BlendDataReader;
struct BlendWriter;
struct ID;
struct IDProperty;
struct Main;
struct PreviewImage;
using PreSaveFn = void (*)(void *asset_ptr, AssetMetaData *asset_data);
@ -78,3 +80,13 @@ void BKE_asset_metadata_read(BlendDataReader *reader, AssetMetaData *asset_data)
void BKE_asset_weak_reference_write(BlendWriter *writer, const AssetWeakReference *weak_ref);
void BKE_asset_weak_reference_read(BlendDataReader *reader, AssetWeakReference *weak_ref);
void BKE_asset_catalog_path_list_free(ListBase &catalog_path_list);
ListBase BKE_asset_catalog_path_list_duplicate(const ListBase &catalog_path_list);
void BKE_asset_catalog_path_list_blend_write(BlendWriter *writer,
const ListBase &catalog_path_list);
void BKE_asset_catalog_path_list_blend_read_data(BlendDataReader *reader,
ListBase &catalog_path_list);
bool BKE_asset_catalog_path_list_has_path(const ListBase &catalog_path_list,
const char *catalog_path);
void BKE_asset_catalog_path_list_add_path(ListBase &catalog_path_list, const char *catalog_path);

View File

@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
/**
* Editing of datablocks from asset libraries, separate from the current open
* blend file.
*
* Each asset blend file is loaded into a separate main database, including the
* asset datablocks and their dependencies. These datablocks are all tagged with
* LIB_TAG_ASSET_MAIN. These can not be linked with other datablocks in the
* current blend file.
*
* For editable assets in user asset libraries, each asset is stored in its own
* blend file. This way the blend file can be easily saved, reloaded and deleted.
*
* This mechanism is currently only used for brush assets.
*/
#include <optional>
#include <string>
#include "BLI_string_ref.hh"
#include "AS_asset_catalog.hh"
#include "DNA_ID_enums.h"
struct bUserAssetLibrary;
struct AssetMetaData;
struct AssetWeakReference;
struct ID;
struct Main;
struct ReportList;
namespace blender::bke {
/** Get datablock from weak reference, loading the blend file as needed. */
ID *asset_edit_id_from_weak_reference(Main &global_main,
ID_Type id_type,
const AssetWeakReference &weak_ref);
/** Get main database that a given asset datablock corresponds to. */
Main *asset_edit_main(const ID &id);
/** Asset editing operations. */
bool asset_edit_id_is_editable(const ID &id);
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID &id,
StringRef name,
const bUserAssetLibrary &user_library,
ReportList &reports);
bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports);
bool asset_edit_id_revert(Main &global_main, const ID &id, ReportList &reports);
bool asset_edit_id_delete(Main &global_main, const ID &id, ReportList &reports);
/** Clean up on exit. */
void asset_edit_main_free_all();
} // namespace blender::bke

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 22
#define BLENDER_FILE_SUBVERSION 24
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -19,6 +19,11 @@ struct ReportList;
struct UserDef;
struct WorkspaceConfigFileData;
/**
* The suffix used for blendfiles managed by the asset system.
*/
#define BLENDER_ASSET_FILE_SUFFIX ".asset.blend"
/**
* Check whether given path ends with a blend file compatible extension
* (`.blend`, `.ble` or `.blend.gz`).

View File

@ -185,16 +185,5 @@ void BKE_brush_scale_size(int *r_brush_size,
*/
bool BKE_brush_has_cube_tip(const Brush *brush, PaintMode paint_mode);
/* Accessors */
#define BKE_brush_tool_get(brush, p) \
(CHECK_TYPE_ANY(brush, Brush *, const Brush *), \
*(const char *)POINTER_OFFSET(brush, (p)->runtime.tool_offset))
#define BKE_brush_tool_set(brush, p, tool) \
{ \
CHECK_TYPE_ANY(brush, Brush *); \
*(char *)POINTER_OFFSET(brush, (p)->runtime.tool_offset) = tool; \
} \
((void)0)
/* debugging only */
void BKE_brush_debug_print_state(Brush *br);

View File

@ -699,12 +699,6 @@ void BKE_library_make_local(
void BKE_id_tag_set_atomic(ID *id, int tag);
void BKE_id_tag_clear_atomic(ID *id, int tag);
/**
* Check that given ID pointer actually is in G_MAIN.
* Main intended use is for debug asserts in places we cannot easily get rid of #G_Main.
*/
bool BKE_id_is_in_global_main(ID *id);
bool BKE_id_can_be_asset(const ID *id);
/**

View File

@ -143,6 +143,14 @@ struct Main {
* could try to use more refined detection on load. */
bool has_forward_compatibility_issues;
/**
* The currently opened .blend file was created as an asset library storage.
*
* This is used to warn the user when they try to save it from Blender UI, since this will likely
* break the automatic management from the asset library system.
*/
bool is_asset_repository;
/** Commit timestamp from `buildinfo`. */
uint64_t build_commit_timestamp;
/** Commit Hash from `buildinfo`. */
@ -183,6 +191,11 @@ struct Main {
*/
bool is_global_main;
/**
* True if main used to store weakly referenced assets.
*/
bool is_asset_weak_reference_main;
BlendThumbnail *blen_thumb;
Library *curlib;
@ -301,6 +314,17 @@ void BKE_main_merge(Main *bmain_dst, Main **r_bmain_src, MainMergeReport &report
*/
bool BKE_main_is_empty(Main *bmain);
/**
* Check whether the bmain has issues, e.g. for reporting in the status bar.
*/
bool BKE_main_has_issues(const Main *bmain);
/**
* Check whether user confirmation should be required when overwriting this `bmain` into its source
* blendfile.
*/
bool BKE_main_needs_overwrite_confirm(const Main *bmain);
void BKE_main_lock(Main *bmain);
void BKE_main_unlock(Main *bmain);
@ -489,6 +513,17 @@ ListBase *which_libbase(Main *bmain, short type);
*/
int set_listbasepointers(Main *main, ListBase *lb[]);
/**
* Return main database this ID is a member of.
*
* Use this in operator and draw code instead of assuming the main
* in the context owns datablocks. Some datablock can be part of
* main datablocks from asset libraries instead.
*
* Optionally can verify membership of global_main, but this is expensive.
*/
Main *BKE_main_from_id(Main *global_main, const ID *id, bool verify = false);
#define MAIN_VERSION_FILE_ATLEAST(main, ver, subver) \
((main)->versionfile > (ver) || \
((main)->versionfile == (ver) && (main)->subversionfile >= (subver)))

View File

@ -8,6 +8,8 @@
* \ingroup bke
*/
#include <optional>
#include "BLI_array.hh"
#include "BLI_bit_vector.hh"
#include "BLI_math_matrix_types.hh"
@ -22,6 +24,7 @@
#include "BKE_pbvh.hh"
struct AssetWeakReference;
struct BMFace;
struct BMLog;
struct BMesh;
@ -172,7 +175,7 @@ PaintCurve *BKE_paint_curve_add(Main *bmain, const char *name);
/**
* Call when entering each respective paint mode.
*/
bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint);
bool BKE_paint_ensure(Main *bmain, ToolSettings *ts, Paint **r_paint);
void BKE_paint_init(Main *bmain, Scene *sce, PaintMode mode, const uchar col[3]);
void BKE_paint_free(Paint *p);
/**
@ -182,24 +185,41 @@ void BKE_paint_free(Paint *p);
*/
void BKE_paint_copy(const Paint *src, Paint *tar, int flag);
void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint);
void BKE_paint_cavity_curve_preset(Paint *p, int preset);
eObjectMode BKE_paint_object_mode_from_paintmode(PaintMode mode);
bool BKE_paint_ensure_from_paintmode(Scene *sce, PaintMode mode);
bool BKE_paint_ensure_from_paintmode(Main *bmain, Scene *sce, PaintMode mode);
Paint *BKE_paint_get_active_from_paintmode(Scene *sce, PaintMode mode);
const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(PaintMode mode);
const char *BKE_paint_get_tool_enum_translation_context_from_paintmode(PaintMode mode);
const char *BKE_paint_get_tool_prop_id_from_paintmode(PaintMode mode);
uint BKE_paint_get_brush_tool_offset_from_paintmode(PaintMode mode);
Paint *BKE_paint_get_active(Scene *sce, ViewLayer *view_layer);
Paint *BKE_paint_get_active_from_context(const bContext *C);
PaintMode BKE_paintmode_get_active_from_context(const bContext *C);
PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref);
/* Paint brush retrieval and assignment. */
Brush *BKE_paint_brush(Paint *paint);
const Brush *BKE_paint_brush_for_read(const Paint *p);
void BKE_paint_brush_set(Paint *paint, Brush *br);
const Brush *BKE_paint_brush_for_read(const Paint *paint);
bool BKE_paint_brush_set(Paint *paint, Brush *brush);
bool BKE_paint_brush_set_default(Main *bmain, Paint *paint);
bool BKE_paint_brush_set_essentials(Main *bmain, Paint *paint, const char *name);
void BKE_paint_brush_set_default_references(ToolSettings *ts);
void BKE_paint_brush_validate(Main *bmain, Paint *paint);
/**
* Set the active brush of given paint struct, and store the weak asset reference to it.
* \note Takes ownership of the given `weak_asset_reference`.
*/
bool BKE_paint_brush_asset_set(Paint *paint,
Brush *brush,
const AssetWeakReference &weak_asset_reference);
/* Paint palette. */
Palette *BKE_paint_palette(Paint *paint);
void BKE_paint_palette_set(Paint *p, Palette *palette);
void BKE_paint_curve_clamp_endpoint_add_index(PaintCurve *pc, int add_index);
@ -252,19 +272,6 @@ void paint_update_brush_rake_rotation(UnifiedPaintSettings *ups, Brush *brush, f
void BKE_paint_stroke_get_average(const Scene *scene, const Object *ob, float stroke[3]);
/* Tool slot API. */
void BKE_paint_toolslots_init_from_main(Main *bmain);
void BKE_paint_toolslots_len_ensure(Paint *paint, int len);
void BKE_paint_toolslots_brush_update_ex(Paint *paint, Brush *brush);
void BKE_paint_toolslots_brush_update(Paint *paint);
/**
* Run this to ensure brush types are set for each slot on entering modes
* (for new scenes for example).
*/
void BKE_paint_toolslots_brush_validate(Main *bmain, Paint *paint);
Brush *BKE_paint_toolslots_brush_get(Paint *paint, int slot_index);
/* .blend I/O */
void BKE_paint_blend_write(BlendWriter *writer, Paint *paint);
@ -726,7 +733,7 @@ void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph,
Main *bmain,
Object *ob,
MultiresModifierData *mmd);
void BKE_sculpt_toolsettings_data_ensure(Scene *scene);
void BKE_sculpt_toolsettings_data_ensure(Main *bmain, Scene *scene);
PBVH *BKE_sculpt_object_pbvh_ensure(Depsgraph *depsgraph, Object *ob);

View File

@ -18,6 +18,7 @@ extern "C" {
struct UserDef;
struct bUserExtensionRepo;
struct bUserAssetLibrary;
struct bUserAssetShelfSettings;
/* -------------------------------------------------------------------- */
/** \name Assert Libraries
@ -122,6 +123,27 @@ int BKE_preferences_extension_repo_get_index(const UserDef *userdef,
/** \} */
/* -------------------------------------------------------------------- */
/** \name #bUserAssetShelvesSettings
* \{ */
bUserAssetShelfSettings *BKE_preferences_asset_shelf_settings_get(const UserDef *userdef,
const char *shelf_idname);
bool BKE_preferences_asset_shelf_settings_is_catalog_path_enabled(const UserDef *userdef,
const char *shelf_idname,
const char *catalog_path);
/**
* Enable a catalog path for a asset shelf identified by \a shelf_idname. Will create the shelf
* settings in the Preferences if necessary.
* \return Return true if the catalog was newly enabled. The Preferences should be tagged as dirty
* then.
*/
bool BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(UserDef *userdef,
const char *shelf_idname,
const char *catalog_path);
/** \} */
#ifdef __cplusplus
}
#endif

View File

@ -233,6 +233,12 @@ struct ARegionType {
/* return context data */
bContextDataCallback context;
/**
* Called on every frame in which the region's poll succeeds, regardless of visibility, before
* drawing, visibility evaluation and initialization. Allows the region to override visibility.
*/
void (*on_poll_success)(const bContext *C, ARegion *region);
/**
* Called whenever the user changes the region's size. Not called when the size is changed
* through other means, like to adjust for a scaled down window.
@ -516,6 +522,8 @@ enum AssetShelfTypeFlag {
/** Do not trigger asset dragging on drag events. Drag events can be overridden with custom
* keymap items then. */
ASSET_SHELF_TYPE_FLAG_NO_ASSET_DRAG = (1 << 0),
ASSET_SHELF_TYPE_FLAG_DEFAULT_VISIBLE = (1 << 1),
ASSET_SHELF_TYPE_FLAG_STORE_CATALOGS_IN_PREFS = (1 << 2),
ASSET_SHELF_TYPE_FLAG_MAX
};
@ -526,8 +534,13 @@ struct AssetShelfType {
int space_type;
/** Operator to call when activating a grid view item. */
std::string activate_operator;
AssetShelfTypeFlag flag;
short default_preview_size;
/** Determine if asset shelves of this type should be available in current context or not. */
bool (*poll)(const bContext *C, const AssetShelfType *shelf_type);
@ -542,6 +555,8 @@ struct AssetShelfType {
const blender::asset_system::AssetRepresentation *asset,
uiLayout *layout);
const AssetWeakReference *(*get_active_asset)(const AssetShelfType *shelf_type);
/* RNA integration */
ExtensionRNA rna_ext;
};

View File

@ -66,6 +66,7 @@ set(SRC
intern/armature_update.cc
intern/asset.cc
intern/asset_weak_reference.cc
intern/asset_edit.cc
intern/attribute.cc
intern/attribute_access.cc
intern/attribute_math.cc
@ -254,7 +255,6 @@ set(SRC
intern/packedFile.cc
intern/paint.cc
intern/paint_canvas.cc
intern/paint_toolslots.cc
intern/particle.cc
intern/particle_child.cc
intern/particle_distribute.cc
@ -335,6 +335,7 @@ set(SRC
BKE_appdir.hh
BKE_armature.hh
BKE_asset.hh
BKE_asset_edit.hh
BKE_attribute.h
BKE_attribute.hh
BKE_attribute_math.hh

View File

@ -0,0 +1,497 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <memory>
#include <utility>
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_space_types.h"
#include "AS_asset_identifier.hh"
#include "AS_asset_library.hh"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_blendfile.hh"
#include "BKE_blendfile_link_append.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_preferences.h"
#include "BKE_report.hh"
#include "BLO_read_write.hh"
#include "BLO_readfile.hh"
#include "BLO_writefile.hh"
#include "DNA_asset_types.h"
#include "MEM_guardedalloc.h"
namespace blender::bke {
/**
* Asset library blend file, with editable contents.
*/
struct AssetEditBlend {
std::string filepath;
Main *main;
bool is_editable = false;
AssetEditBlend(const std::string &filepath);
~AssetEditBlend();
AssetEditBlend(const AssetEditBlend &) = delete;
AssetEditBlend(AssetEditBlend &&other);
AssetEditBlend &operator=(AssetEditBlend &&other);
ID *ensure_id(ID_Type id_type, const char *asset_name);
void reload(Main &global_main);
void clear_users(Main &global_main);
};
AssetEditBlend::AssetEditBlend(const std::string &filepath)
: filepath(std::move(filepath)), main(BKE_main_new())
{
this->main->is_asset_weak_reference_main = true;
BLI_assert(!BLI_path_is_rel(filepath.c_str()));
/* Fairly simple check based on filepath only.
* - Ends with `.asset.bend` extensions.
* - Is located in user asset library.
*
* TODO?
* - Check file contents.
* - Check file is writable.
*/
this->is_editable = StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX) &&
BKE_preferences_asset_library_containing_path(&U, filepath.c_str());
}
AssetEditBlend::~AssetEditBlend()
{
if (main) {
BKE_main_free(main);
}
}
AssetEditBlend::AssetEditBlend(AssetEditBlend &&other)
: filepath(std::exchange(other.filepath, "")), main(std::exchange(other.main, nullptr))
{
}
AssetEditBlend &AssetEditBlend::operator=(AssetEditBlend &&other)
{
if (this == &other) {
return *this;
}
this->filepath = std::exchange(other.filepath, "");
this->main = std::exchange(other.main, nullptr);
return *this;
}
ID *AssetEditBlend::ensure_id(const ID_Type id_type, const char *asset_name)
{
/* Check if we have the asset already. */
ID *local_asset = BKE_libblock_find_name(this->main, id_type, asset_name);
if (local_asset) {
BLI_assert(ID_IS_ASSET(local_asset));
return local_asset;
}
/* Load asset from asset library. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = this->main;
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(&lapp_params);
BKE_blendfile_link_append_context_flag_set(lapp_context, BLO_LIBLINK_FORCE_INDIRECT, true);
BKE_blendfile_link_append_context_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, filepath.c_str(), nullptr);
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, asset_name, id_type, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
BKE_blendfile_link(lapp_context, nullptr);
BKE_blendfile_append(lapp_context, nullptr);
local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(this->main, LIB_TAG_ASSET_MAIN, true);
/* Verify that the name matches. It must for referencing the same asset again to work. */
BLI_assert(local_asset == nullptr || STREQ(local_asset->name + 2, asset_name));
return local_asset;
}
static std::string asset_root_path_for_save(const bUserAssetLibrary &user_library,
const ID_Type id_type)
{
BLI_assert(user_library.dirpath[0] != '\0');
char libpath[FILE_MAX];
BLI_strncpy(libpath, user_library.dirpath, sizeof(libpath));
BLI_path_slash_native(libpath);
BLI_path_normalize(libpath);
/* Capitalize folder name. Ideally this would already available in
* the type info to work correctly with multiple words. */
const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_idcode(id_type);
std::string name = id_type_info->name_plural;
name[0] = BLI_toupper_ascii(name[0]);
return std::string(libpath) + SEP + "Saved" + SEP + name;
}
static std::string asset_blendfile_path_for_save(const bUserAssetLibrary &user_library,
const StringRef base_name,
const ID_Type id_type,
ReportList &reports)
{
std::string root_path = asset_root_path_for_save(user_library, id_type);
BLI_assert(!root_path.empty());
if (!BLI_dir_create_recursive(root_path.c_str())) {
BKE_report(&reports, RPT_ERROR, "Failed to create asset library directory to save brush");
return "";
}
char base_name_filesafe[FILE_MAXFILE];
BLI_strncpy(base_name_filesafe,
base_name.data(),
std::min(sizeof(base_name_filesafe), size_t(base_name.size())));
BLI_path_make_safe_filename(base_name_filesafe);
const std::string filepath = root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
if (!BLI_is_file(filepath.c_str())) {
return filepath;
}
for (int i = 1;; i++) {
const std::string filepath = root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
BLENDER_ASSET_FILE_SUFFIX;
if (!BLI_is_file((filepath.c_str()))) {
return filepath;
}
}
return "";
}
static bool asset_write_in_library(Main *bmain,
const ID &id_const,
const StringRef name,
const StringRefNull filepath,
std::string &final_full_file_path,
ReportList &reports)
{
/* TODO: Comment seems to be resolved by separate #Main storage?
* XXX
* FIXME
*
* This code is _pure evil_. It does in-place manipulation on IDs in global Main database,
* temporarilly remove them and add them back...
*
* Use it as-is for now (in a similar way as python API or copy-to-buffer works). Nut the whole
* 'BKE_blendfile_write_partial' code needs to be completely refactored.
*
* Ideas:
* - Have `BKE_blendfile_write_partial_begin` return a new temp Main.
* - Replace `BKE_blendfile_write_partial_tag_ID` by API to add IDs to this temp Main.
* + This should _duplicate_ the ID, not remove the original one from the source Main!
* - Have API to automatically also duplicate dependencies into temp Main.
* + Have options to e.g. make all duplicated IDs 'local' (i.e. remove their library data).
* - `BKE_blendfile_write_partial` then simply write the given temp main.
* - `BKE_blendfile_write_partial_end` frees the temp Main.
*/
ID &id = const_cast<ID &>(id_const);
const short prev_flag = id.flag;
const int prev_tag = id.tag;
const int prev_us = id.us;
const std::string prev_name = id.name + 2;
/* TODO: Remove library overrides stuff now that they are not used for brush assets. */
IDOverrideLibrary *prev_liboverride = id.override_library;
const int write_flags = 0; /* Could use #G_FILE_COMPRESS ? */
const eBLO_WritePathRemap remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
BKE_blendfile_write_partial_begin(bmain);
id.flag |= LIB_FAKEUSER;
id.tag &= ~LIB_TAG_RUNTIME;
id.us = 1;
BLI_strncpy(id.name + 2, name.data(), std::min(sizeof(id.name) - 2, size_t(name.size())));
id.override_library = nullptr;
BKE_blendfile_write_partial_tag_ID(&id, true);
/* TODO: check overwriting existing file. */
/* TODO: ensure filepath contains only valid characters for file system. */
const bool sucess = BKE_blendfile_write_partial(
bmain, filepath.c_str(), write_flags, remap_mode, &reports);
if (sucess) {
final_full_file_path = std::string(filepath) + SEP + "Brush" + SEP + name;
}
BKE_blendfile_write_partial_end(bmain);
BKE_blendfile_write_partial_tag_ID(&id, false);
id.flag = prev_flag;
id.tag = prev_tag;
id.us = prev_us;
BLI_strncpy(id.name + 2, prev_name.c_str(), sizeof(id.name) - 2);
id.override_library = prev_liboverride;
return sucess;
}
void AssetEditBlend::reload(Main &global_main)
{
Main *old_main = this->main;
this->main = BKE_main_new();
this->main->is_asset_weak_reference_main = true;
/* Fill fresh main database with same datablock as before. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = this->main;
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(&lapp_params);
BKE_blendfile_link_append_context_flag_set(lapp_context, BLO_LIBLINK_FORCE_INDIRECT, true);
BKE_blendfile_link_append_context_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, this->filepath.c_str(), nullptr);
/* Requests all existing datablocks to be appended again. */
ID *old_id;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID_Type old_id_code = GS(old_id->name);
if (BKE_idtype_idcode_is_linkable(old_id_code)) {
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, old_id->name + 2, old_id_code, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
}
}
FOREACH_MAIN_ID_END;
BKE_blendfile_link(lapp_context, nullptr);
BKE_blendfile_append(lapp_context, nullptr);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(this->main, LIB_TAG_ASSET_MAIN, true);
/* Remap old to new. */
bke::id::IDRemapper mappings;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID *new_id = BKE_libblock_find_name(this->main, GS(old_id->name), old_id->name + 2);
mappings.add(old_id, new_id);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
/* Free old database. */
BKE_main_free(old_main);
}
void AssetEditBlend::clear_users(Main &global_main)
{
/* Remap old to null pointer. */
bke::id::IDRemapper mappings;
ID *old_id;
FOREACH_MAIN_ID_BEGIN (this->main, old_id) {
mappings.add(old_id, nullptr);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
}
/**
* Public API
*/
static Vector<AssetEditBlend> &asset_edit_blend_get_all()
{
static Vector<AssetEditBlend> mains;
return mains;
}
static AssetEditBlend *asset_edit_blend_from_id(const ID &id)
{
BLI_assert(id.tag & LIB_TAG_ASSET_MAIN);
for (AssetEditBlend &asset_blend : asset_edit_blend_get_all()) {
/* TODO: Look into make this whole thing more efficient. */
ListBase *lb = which_libbase(asset_blend.main, GS(id.name));
LISTBASE_FOREACH (ID *, other_id, lb) {
if (&id == other_id) {
return &asset_blend;
}
}
}
BLI_assert_unreachable();
return nullptr;
}
Main *asset_edit_main(const ID &id)
{
const AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
return (asset_blend) ? asset_blend->main : nullptr;
}
static AssetEditBlend &asset_edit_blend_file_ensure(const StringRef filepath)
{
for (AssetEditBlend &asset_blend : asset_edit_blend_get_all()) {
if (asset_blend.filepath == filepath) {
return asset_blend;
}
}
asset_edit_blend_get_all().append_as(filepath);
return asset_edit_blend_get_all().last();
}
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID &id,
const StringRef name,
const bUserAssetLibrary &user_library,
ReportList &reports)
{
const std::string filepath = asset_blendfile_path_for_save(
user_library, name, GS(id.name), reports);
/* Save to asset library. */
Main *asset_main = BKE_main_from_id(&global_main, &id);
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(
asset_main, id, name, filepath, final_full_asset_filepath, reports);
if (!success) {
BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
return std::nullopt;
}
BKE_reportf(&reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
return final_full_asset_filepath;
}
bool asset_edit_id_save(Main & /*global_main*/, const ID &id, ReportList &reports)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(asset_blend->main,
id,
id.name + 2,
asset_blend->filepath.c_str(),
final_full_asset_filepath,
reports);
if (!success) {
BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
return false;
}
return true;
}
bool asset_edit_id_revert(Main &global_main, const ID &id, ReportList & /*reports*/)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
/* Reload entire main, including texture dependencies. This relies on there
* being only a single brush asset per blend file. */
asset_blend->reload(global_main);
return true;
}
bool asset_edit_id_delete(Main &global_main, const ID &id, ReportList &reports)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
if (BLI_delete(asset_blend->filepath.c_str(), false, false) != 0) {
BKE_report(&reports, RPT_ERROR, "Failed to delete asset library file");
return false;
}
asset_blend->clear_users(global_main);
int index = 0;
for (AssetEditBlend &asset_blend_iter : asset_edit_blend_get_all()) {
if (&asset_blend_iter == asset_blend) {
asset_edit_blend_get_all().remove(index);
break;
}
index++;
}
return true;
}
ID *asset_edit_id_from_weak_reference(Main &global_main,
const ID_Type id_type,
const AssetWeakReference &weak_ref)
{
char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
char *asset_lib_path, *asset_group, *asset_name;
AS_asset_full_path_explode_from_weak_ref(
&weak_ref, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
return nullptr;
}
BLI_assert(asset_name != nullptr);
/* Find asset in current blend file. */
if (asset_lib_path == nullptr) {
ID *local_asset = BKE_libblock_find_name(&global_main, id_type, asset_name);
BLI_assert(local_asset == nullptr || ID_IS_ASSET(local_asset));
return local_asset;
}
/* If weak reference resolves to a null library path, assume we are in local asset case. */
AssetEditBlend &asset_blend = asset_edit_blend_file_ensure(asset_lib_path);
return asset_blend.ensure_id(id_type, asset_name);
}
bool asset_edit_id_is_editable(const ID &id)
{
if (!(id.tag & LIB_TAG_ASSET_MAIN)) {
return false;
}
const AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
return (asset_blend) ? asset_blend->is_editable : false;
}
void asset_edit_main_free_all()
{
asset_edit_blend_get_all().clear_and_shrink();
}
} // namespace blender::bke

View File

@ -7,15 +7,26 @@
*/
#include <memory>
#include <utility>
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_space_types.h"
#include "AS_asset_identifier.hh"
#include "AS_asset_library.hh"
#include "BKE_asset.hh"
#include "BKE_blendfile_link_append.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BLO_read_write.hh"
#include "BLO_readfile.hh"
#include "DNA_asset_types.h"
@ -73,6 +84,20 @@ AssetWeakReference &AssetWeakReference::operator=(AssetWeakReference &&other)
return *this;
}
bool operator==(const AssetWeakReference &a, const AssetWeakReference &b)
{
if (a.asset_library_type != b.asset_library_type) {
return false;
}
if (StringRef(a.asset_library_identifier) != StringRef(b.asset_library_identifier)) {
return false;
}
if (StringRef(a.relative_asset_identifier) != StringRef(b.relative_asset_identifier)) {
return false;
}
return true;
}
AssetWeakReference AssetWeakReference::make_reference(
const asset_system::AssetLibrary &library,
const asset_system::AssetIdentifier &asset_identifier)
@ -104,3 +129,58 @@ void BKE_asset_weak_reference_read(BlendDataReader *reader, AssetWeakReference *
BLO_read_string(reader, &weak_ref->asset_library_identifier);
BLO_read_string(reader, &weak_ref->relative_asset_identifier);
}
void BKE_asset_catalog_path_list_free(ListBase &catalog_path_list)
{
LISTBASE_FOREACH_MUTABLE (AssetCatalogPathLink *, catalog_path, &catalog_path_list) {
MEM_delete(catalog_path->path);
BLI_freelinkN(&catalog_path_list, catalog_path);
}
BLI_assert(BLI_listbase_is_empty(&catalog_path_list));
}
ListBase BKE_asset_catalog_path_list_duplicate(const ListBase &catalog_path_list)
{
ListBase duplicated_list = {nullptr};
LISTBASE_FOREACH (AssetCatalogPathLink *, catalog_path, &catalog_path_list) {
AssetCatalogPathLink *copied_path = MEM_cnew<AssetCatalogPathLink>(__func__);
copied_path->path = BLI_strdup(catalog_path->path);
BLI_addtail(&duplicated_list, copied_path);
}
return duplicated_list;
}
void BKE_asset_catalog_path_list_blend_write(BlendWriter *writer,
const ListBase &catalog_path_list)
{
LISTBASE_FOREACH (const AssetCatalogPathLink *, catalog_path, &catalog_path_list) {
BLO_write_struct(writer, AssetCatalogPathLink, catalog_path);
BLO_write_string(writer, catalog_path->path);
}
}
void BKE_asset_catalog_path_list_blend_read_data(BlendDataReader *reader,
ListBase &catalog_path_list)
{
BLO_read_struct_list(reader, AssetCatalogPathLink, &catalog_path_list);
LISTBASE_FOREACH (AssetCatalogPathLink *, catalog_path, &catalog_path_list) {
BLO_read_data_address(reader, &catalog_path->path);
}
}
bool BKE_asset_catalog_path_list_has_path(const ListBase &catalog_path_list,
const char *catalog_path)
{
return BLI_findstring_ptr(
&catalog_path_list, catalog_path, offsetof(AssetCatalogPathLink, path)) != nullptr;
}
void BKE_asset_catalog_path_list_add_path(ListBase &catalog_path_list, const char *catalog_path)
{
AssetCatalogPathLink *new_path = MEM_cnew<AssetCatalogPathLink>(__func__);
new_path->path = BLI_strdup(catalog_path);
BLI_addtail(&catalog_path_list, new_path);
}

View File

@ -22,6 +22,8 @@
#include "IMB_moviecache.hh"
#include "BKE_addon.h"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_blender.hh" /* own include */
#include "BKE_blender_user_menu.hh" /* own include */
#include "BKE_blender_version.h" /* own include */
@ -32,6 +34,7 @@
#include "BKE_idprop.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_preferences.h"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BKE_studiolight.h"
@ -59,6 +62,11 @@ void BKE_blender_free()
BKE_blender_globals_clear();
/* Free separate data-bases after #BKE_blender_globals_clear in case any data-blocks in the
* global main use pointers to asset main data-blocks when they're freed. That generally
* shouldn't happen but it's better to be safe. */
blender::bke::asset_edit_main_free_all();
if (G.log.file != nullptr) {
fclose(static_cast<FILE *>(G.log.file));
}
@ -340,6 +348,12 @@ void BKE_blender_userdef_data_free(UserDef *userdef, bool clear_fonts)
BLI_freelistN(&userdef->script_directories);
BLI_freelistN(&userdef->asset_libraries);
BLI_freelistN(&userdef->extension_repos);
LISTBASE_FOREACH_MUTABLE (bUserAssetShelfSettings *, settings, &userdef->asset_shelves_settings)
{
BKE_asset_catalog_path_list_free(settings->enabled_catalog_paths);
MEM_freeN(settings);
}
BLI_listbase_clear(&userdef->asset_shelves_settings);
BLI_freelistN(&userdef->uistyles);
BLI_freelistN(&userdef->uifonts);

View File

@ -675,6 +675,9 @@ static void setup_app_data(bContext *C,
* (but may have a null `curscreen`). */
else if (ELEM(nullptr, bfd->curscreen, bfd->curscene)) {
BKE_report(reports->reports, RPT_WARNING, "Library file, loading empty scene");
if (blender::StringRefNull(bfd->main->filepath).endswith(BLENDER_ASSET_FILE_SUFFIX)) {
bfd->main->is_asset_repository = true;
}
mode = LOAD_UI_OFF;
}
else if (G.fileflags & G_FILE_NO_UI) {
@ -1259,6 +1262,15 @@ UserDef *BKE_blendfile_userdef_from_defaults()
BKE_preferences_extension_repo_add_default(userdef);
BKE_preferences_extension_repo_add_default_user(userdef);
{
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/Cloth");
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/General");
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/Painting");
}
return userdef;
}

View File

@ -7,23 +7,30 @@
*/
#include <optional>
#include <memory>
#include "MEM_guardedalloc.h"
#include "DNA_brush_types.h"
#include "DNA_defaults.h"
#include "DNA_material_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_rand.h"
#include "BLO_readfile.hh"
#include "BLT_translation.hh"
#include "BKE_asset.hh"
#include "BKE_bpath.hh"
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_gpencil_legacy.h"
#include "BKE_idprop.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
@ -34,6 +41,8 @@
#include "BKE_preview_image.hh"
#include "BKE_texture.h"
#include "AS_asset_library.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
@ -378,38 +387,44 @@ static void brush_blend_read_after_liblink(BlendLibReader * /*reader*/, ID *id)
}
}
static int brush_undo_preserve_cb(LibraryIDLinkCallbackData *cb_data)
static void brush_asset_metadata_ensure(void *asset_ptr, AssetMetaData *asset_data)
{
BlendLibReader *reader = (BlendLibReader *)cb_data->user_data;
ID *self_id = cb_data->self_id;
ID *id_old = *cb_data->id_pointer;
/* Old data has not been remapped to new values of the pointers, if we want to keep the old
* pointer here we need its new address. */
ID *id_old_new = id_old != nullptr ? BLO_read_get_new_id_address(
reader, self_id, ID_IS_LINKED(self_id), id_old) :
nullptr;
BLI_assert(id_old_new == nullptr || ELEM(id_old, id_old_new, id_old_new->orig_id));
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_plus_no_lib(id_old_new);
id_us_min(id_old);
using namespace blender;
using namespace blender::bke;
Brush *brush = reinterpret_cast<Brush *>(asset_ptr);
BLI_assert(GS(brush->id.name) == ID_BR);
/* Most names copied from brush RNA (not all are available there though). */
constexpr std::array mode_map{
std::pair{"use_paint_sculpt", OB_MODE_SCULPT},
std::pair{"use_paint_vertex", OB_MODE_VERTEX_PAINT},
std::pair{"use_paint_weight", OB_MODE_WEIGHT_PAINT},
std::pair{"use_paint_image", OB_MODE_TEXTURE_PAINT},
/* Sculpt UVs in the image editor while in edit mode. */
std::pair{"use_paint_uv_sculpt", OB_MODE_EDIT},
std::pair{"use_paint_grease_pencil", OB_MODE_PAINT_GPENCIL_LEGACY},
/* Note: Not defined in brush RNA, own name. */
std::pair{"use_sculpt_grease_pencil", OB_MODE_SCULPT_GPENCIL_LEGACY},
std::pair{"use_vertex_grease_pencil", OB_MODE_VERTEX_GPENCIL_LEGACY},
std::pair{"use_weight_grease_pencil", OB_MODE_WEIGHT_GPENCIL_LEGACY},
std::pair{"use_paint_sculpt_curves", OB_MODE_SCULPT_CURVES},
};
for (const auto &mode_mapping : mode_map) {
/* Only add bools for supported modes. */
if (!(brush->ob_mode & mode_mapping.second)) {
continue;
}
auto mode_property = idprop::create_bool(mode_mapping.first, true);
BKE_asset_metadata_idprop_ensure(asset_data, mode_property.release());
}
*cb_data->id_pointer = id_old_new;
return IDWALK_RET_NOP;
}
static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old)
{
/* Whole Brush is preserved across undo-steps. */
BKE_lib_id_swap(nullptr, id_new, id_old, false, 0);
/* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid.
* NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */
BKE_library_foreach_ID_link(nullptr, id_new, brush_undo_preserve_cb, reader, IDWALK_NOP);
/* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be
* fairly delicate. */
std::swap(id_new->properties, id_old->properties);
}
static AssetTypeInfo AssetType_BR = {
/*pre_save_fn*/ brush_asset_metadata_ensure,
/*on_mark_asset_fn*/ brush_asset_metadata_ensure,
};
IDTypeInfo IDType_ID_BR = {
/*id_code*/ ID_BR,
@ -421,8 +436,8 @@ IDTypeInfo IDType_ID_BR = {
/*name*/ "Brush",
/*name_plural*/ N_("brushes"),
/*translation_context*/ BLT_I18NCONTEXT_ID_BRUSH,
/*flags*/ IDTYPE_FLAGS_NO_ANIMDATA,
/*asset_type_info*/ nullptr,
/*flags*/ IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_NO_MEMFILE_UNDO,
/*asset_type_info*/ &AssetType_BR,
/*init_data*/ brush_init_data,
/*copy_data*/ brush_copy_data,
@ -437,7 +452,7 @@ IDTypeInfo IDType_ID_BR = {
/*blend_read_data*/ brush_blend_read_data,
/*blend_read_after_liblink*/ brush_blend_read_after_liblink,
/*blend_read_undo_preserve*/ brush_undo_preserve,
/*blend_read_undo_preserve*/ nullptr,
/*lib_override_apply_post*/ nullptr,
};

View File

@ -1325,6 +1325,9 @@ void *BKE_libblock_alloc_in_lib(Main *bmain,
if ((flag & LIB_ID_CREATE_NO_DEG_TAG) == 0) {
DEG_id_type_tag(bmain, type);
}
if (bmain->is_asset_weak_reference_main) {
id->tag |= LIB_TAG_ASSET_MAIN;
}
}
else {
BLI_strncpy(id->name + 2, name, sizeof(id->name) - 2);
@ -1539,6 +1542,10 @@ void BKE_libblock_copy_in_lib(Main *bmain,
DEG_id_type_tag(bmain, GS(new_id->name));
}
if (bmain && bmain->is_asset_weak_reference_main) {
new_id->tag |= LIB_TAG_ASSET_MAIN;
}
*r_newid = new_id;
}
@ -2194,13 +2201,6 @@ void BKE_id_tag_clear_atomic(ID *id, int tag)
atomic_fetch_and_and_int32(&id->tag, ~tag);
}
bool BKE_id_is_in_global_main(ID *id)
{
/* We do not want to fail when id is nullptr here, even though this is a bit strange behavior...
*/
return (id == nullptr || BLI_findindex(which_libbase(G_MAIN, GS(id->name)), id) != -1);
}
bool BKE_id_can_be_asset(const ID *id)
{
return !ID_IS_LINKED(id) && !ID_IS_OVERRIDE_LIBRARY(id) &&

View File

@ -24,6 +24,7 @@
#include "DNA_ID.h"
#include "BKE_asset_edit.hh"
#include "BKE_bpath.hh"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
@ -445,6 +446,16 @@ bool BKE_main_is_empty(Main *bmain)
return result;
}
bool BKE_main_has_issues(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_repository;
}
bool BKE_main_needs_overwrite_confirm(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_repository;
}
void BKE_main_lock(Main *bmain)
{
BLI_spin_lock((SpinLock *)bmain->lock);
@ -955,3 +966,34 @@ int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/])
return (INDEX_ID_MAX - 1);
}
Main *BKE_main_from_id(Main *global_main, const ID *id, const bool verify)
{
if (id == nullptr || (id->tag & LIB_TAG_NO_MAIN)) {
return nullptr;
}
if (id->tag & LIB_TAG_ASSET_MAIN) {
return blender::bke::asset_edit_main(*id);
}
if (verify) {
/* This is rather expensive, so don't do by default and assume valid input. */
if (BLI_findindex(which_libbase(global_main, GS(id->name)), id) == -1) {
return nullptr;
}
}
else {
/* Debug assert, especially for places that pass in G_MAIN. */
#ifndef NDEBUG
if (id->flag & LIB_EMBEDDED_DATA) {
const ID *id_owner = BKE_id_owner_get(const_cast<ID *>(id));
BLI_assert(BLI_findindex(which_libbase(global_main, GS(id_owner->name)), id_owner) != -1);
}
else {
BLI_assert(BLI_findindex(which_libbase(global_main, GS(id->name)), id) != -1);
}
#endif
}
return global_main;
}

View File

@ -13,6 +13,7 @@
#include "DNA_object_enums.h"
#include "MEM_guardedalloc.h"
#include "DNA_asset_types.h"
#include "DNA_brush_types.h"
#include "DNA_defaults.h"
#include "DNA_gpencil_legacy_types.h"
@ -33,12 +34,15 @@
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BLT_translation.hh"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_ccg.h"
@ -323,7 +327,7 @@ void BKE_paint_reset_overlay_invalid(ePaintOverlayControlFlags flag)
overlay_flags &= ~(flag);
}
bool BKE_paint_ensure_from_paintmode(Scene *sce, PaintMode mode)
bool BKE_paint_ensure_from_paintmode(Main *bmain, Scene *sce, PaintMode mode)
{
ToolSettings *ts = sce->toolsettings;
Paint **paint_ptr = nullptr;
@ -368,7 +372,7 @@ bool BKE_paint_ensure_from_paintmode(Scene *sce, PaintMode mode)
break;
}
if (paint_ptr) {
BKE_paint_ensure(ts, paint_ptr);
BKE_paint_ensure(bmain, ts, paint_ptr);
return true;
}
return false;
@ -441,61 +445,6 @@ const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(const PaintMode m
return nullptr;
}
const char *BKE_paint_get_tool_prop_id_from_paintmode(const PaintMode mode)
{
switch (mode) {
case PaintMode::Sculpt:
return "sculpt_tool";
case PaintMode::Vertex:
return "vertex_tool";
case PaintMode::Weight:
return "weight_tool";
case PaintMode::Texture2D:
case PaintMode::Texture3D:
return "image_tool";
case PaintMode::GPencil:
return "gpencil_tool";
case PaintMode::VertexGPencil:
return "gpencil_vertex_tool";
case PaintMode::SculptGPencil:
return "gpencil_sculpt_tool";
case PaintMode::WeightGPencil:
return "gpencil_weight_tool";
case PaintMode::SculptCurves:
return "curves_sculpt_tool";
case PaintMode::SculptGreasePencil:
return "gpencil_sculpt_tool";
case PaintMode::Invalid:
break;
}
/* Invalid paint mode. */
return nullptr;
}
const char *BKE_paint_get_tool_enum_translation_context_from_paintmode(const PaintMode mode)
{
switch (mode) {
case PaintMode::Sculpt:
case PaintMode::GPencil:
case PaintMode::Texture2D:
case PaintMode::Texture3D:
return BLT_I18NCONTEXT_ID_BRUSH;
case PaintMode::Vertex:
case PaintMode::Weight:
case PaintMode::VertexGPencil:
case PaintMode::SculptGPencil:
case PaintMode::WeightGPencil:
case PaintMode::SculptCurves:
case PaintMode::SculptGreasePencil:
case PaintMode::Invalid:
break;
}
/* Invalid paint mode. */
return BLT_I18NCONTEXT_DEFAULT;
}
Paint *BKE_paint_get_active(Scene *sce, ViewLayer *view_layer)
{
if (sce && view_layer) {
@ -660,68 +609,229 @@ PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref)
return PaintMode::Invalid;
}
Brush *BKE_paint_brush(Paint *p)
static bool paint_brush_set_from_asset_reference(Main *bmain, Paint *paint)
{
return (Brush *)BKE_paint_brush_for_read((const Paint *)p);
/* Attempt to restore a valid active brush from brush asset information. */
if (paint->brush != nullptr) {
return false;
}
if (paint->brush_asset_reference == nullptr) {
return false;
}
Brush *brush = reinterpret_cast<Brush *>(blender::bke::asset_edit_id_from_weak_reference(
*bmain, ID_BR, *paint->brush_asset_reference));
BLI_assert(brush == nullptr || (brush->id.tag & LIB_TAG_ASSET_MAIN));
/* Ensure we have a brush with appropriate mode to assign.
* Could happen if contents of asset blend was manually changed. */
if (brush == nullptr || (paint->runtime.ob_mode & brush->ob_mode) == 0) {
MEM_delete(paint->brush_asset_reference);
paint->brush_asset_reference = nullptr;
return false;
}
paint->brush = brush;
return true;
}
const Brush *BKE_paint_brush_for_read(const Paint *p)
Brush *BKE_paint_brush(Paint *paint)
{
return p ? p->brush : nullptr;
return (Brush *)BKE_paint_brush_for_read((const Paint *)paint);
}
void BKE_paint_brush_set(Paint *p, Brush *br)
const Brush *BKE_paint_brush_for_read(const Paint *paint)
{
if (p) {
id_us_min((ID *)p->brush);
id_us_plus((ID *)br);
p->brush = br;
return paint ? paint->brush : nullptr;
}
BKE_paint_toolslots_brush_update(p);
bool BKE_paint_brush_set(Paint *paint, Brush *brush)
{
if (paint == nullptr || paint->brush == brush) {
return false;
}
if (brush && (paint->runtime.ob_mode & brush->ob_mode) == 0) {
return false;
}
paint->brush = brush;
return true;
}
static void paint_brush_asset_update(Paint &paint,
Brush *brush,
const AssetWeakReference &brush_asset_reference)
{
BLI_assert(&brush_asset_reference != paint.brush_asset_reference);
MEM_delete(paint.brush_asset_reference);
paint.brush_asset_reference = nullptr;
if (brush == nullptr || brush != paint.brush || !(brush->id.tag & LIB_TAG_ASSET_MAIN)) {
return;
}
paint.brush_asset_reference = MEM_new<AssetWeakReference>(__func__, brush_asset_reference);
}
bool BKE_paint_brush_asset_set(Paint *paint,
Brush *brush,
const AssetWeakReference &weak_asset_reference)
{
/* Should not happen for users if brush assets are properly filtered by mode, but still protect
* against it in case of invalid API usage. */
if (brush && paint->runtime.ob_mode != brush->ob_mode) {
return false;
}
BKE_paint_brush_set(paint, brush);
paint_brush_asset_update(*paint, brush, weak_asset_reference);
return true;
}
static void paint_brush_set_essentials_reference(Paint *paint, const char *name)
{
/* Set brush asset reference to a named brush in the essentials asset library. */
MEM_delete(paint->brush_asset_reference);
AssetWeakReference *weak_ref = MEM_new<AssetWeakReference>(__func__);
weak_ref->asset_library_type = eAssetLibraryType::ASSET_LIBRARY_ESSENTIALS;
weak_ref->relative_asset_identifier = BLI_sprintfN("brushes/essentials_brushes.blend/Brush/%s",
name);
paint->brush_asset_reference = weak_ref;
paint->brush = nullptr;
}
static void paint_brush_set_default_reference(Paint *paint)
{
const char *name = nullptr;
switch (paint->runtime.ob_mode) {
case OB_MODE_SCULPT:
name = "Draw";
break;
case OB_MODE_VERTEX_PAINT:
name = "Paint Vertex";
break;
case OB_MODE_WEIGHT_PAINT:
name = "Paint Weight";
break;
case OB_MODE_TEXTURE_PAINT:
name = "Paint Texture";
break;
case OB_MODE_SCULPT_CURVES:
name = "Comb Curves";
break;
case OB_MODE_EDIT:
/* TODO: UV sculpt. */
break;
case OB_MODE_PAINT_GPENCIL_LEGACY:
name = "Pencil";
break;
case OB_MODE_VERTEX_GPENCIL_LEGACY:
name = "Paint Point Color";
break;
case OB_MODE_SCULPT_GPENCIL_LEGACY:
name = "Smooth Stroke";
break;
case OB_MODE_WEIGHT_GPENCIL_LEGACY:
name = "Paint Point Weight";
break;
default:
BLI_assert_unreachable();
return;
}
if (name) {
paint_brush_set_essentials_reference(paint, name);
}
}
void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint)
void BKE_paint_brush_set_default_references(ToolSettings *ts)
{
if (ts->sculpt) {
paint_brush_set_default_reference(&ts->sculpt->paint);
}
if (ts->curves_sculpt) {
paint_brush_set_default_reference(&ts->curves_sculpt->paint);
}
if (ts->wpaint) {
paint_brush_set_default_reference(&ts->wpaint->paint);
}
if (ts->vpaint) {
paint_brush_set_default_reference(&ts->vpaint->paint);
}
if (ts->gp_paint) {
paint_brush_set_default_reference(&ts->gp_paint->paint);
}
if (ts->gp_vertexpaint) {
paint_brush_set_default_reference(&ts->gp_vertexpaint->paint);
}
if (ts->gp_sculptpaint) {
paint_brush_set_default_reference(&ts->gp_sculptpaint->paint);
}
if (ts->gp_weightpaint) {
paint_brush_set_default_reference(&ts->gp_weightpaint->paint);
}
paint_brush_set_default_reference(&ts->imapaint.paint);
}
bool BKE_paint_brush_set_default(Main *bmain, Paint *paint)
{
paint_brush_set_default_reference(paint);
return paint_brush_set_from_asset_reference(bmain, paint);
}
bool BKE_paint_brush_set_essentials(Main *bmain, Paint *paint, const char *name)
{
paint_brush_set_essentials_reference(paint, name);
return paint_brush_set_from_asset_reference(bmain, paint);
}
void BKE_paint_brush_validate(Main *bmain, Paint *paint)
{
/* Clear brush with invalid mode. Unclear if this can still happen,
* but kept from old paint toolslots code. */
Brush *brush = BKE_paint_brush(paint);
if (brush && (paint->runtime.ob_mode & brush->ob_mode) == 0) {
BKE_paint_brush_set(paint, nullptr);
BKE_paint_brush_set_default(bmain, paint);
}
}
static void paint_runtime_init(const ToolSettings *ts, Paint *paint)
{
if (paint == &ts->imapaint.paint) {
paint->runtime.tool_offset = offsetof(Brush, imagepaint_tool);
paint->runtime.ob_mode = OB_MODE_TEXTURE_PAINT;
}
else if (ts->sculpt && paint == &ts->sculpt->paint) {
paint->runtime.tool_offset = offsetof(Brush, sculpt_tool);
paint->runtime.ob_mode = OB_MODE_SCULPT;
}
else if (ts->vpaint && paint == &ts->vpaint->paint) {
paint->runtime.tool_offset = offsetof(Brush, vertexpaint_tool);
paint->runtime.ob_mode = OB_MODE_VERTEX_PAINT;
}
else if (ts->wpaint && paint == &ts->wpaint->paint) {
paint->runtime.tool_offset = offsetof(Brush, weightpaint_tool);
paint->runtime.ob_mode = OB_MODE_WEIGHT_PAINT;
}
else if (ts->gp_paint && paint == &ts->gp_paint->paint) {
paint->runtime.tool_offset = offsetof(Brush, gpencil_tool);
paint->runtime.ob_mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
else if (ts->gp_vertexpaint && paint == &ts->gp_vertexpaint->paint) {
paint->runtime.tool_offset = offsetof(Brush, gpencil_vertex_tool);
paint->runtime.ob_mode = OB_MODE_VERTEX_GPENCIL_LEGACY;
}
else if (ts->gp_sculptpaint && paint == &ts->gp_sculptpaint->paint) {
paint->runtime.tool_offset = offsetof(Brush, gpencil_sculpt_tool);
paint->runtime.ob_mode = OB_MODE_SCULPT_GPENCIL_LEGACY;
}
else if (ts->gp_weightpaint && paint == &ts->gp_weightpaint->paint) {
paint->runtime.tool_offset = offsetof(Brush, gpencil_weight_tool);
paint->runtime.ob_mode = OB_MODE_WEIGHT_GPENCIL_LEGACY;
}
else if (ts->curves_sculpt && paint == &ts->curves_sculpt->paint) {
paint->runtime.tool_offset = offsetof(Brush, curves_sculpt_tool);
paint->runtime.ob_mode = OB_MODE_SCULPT_CURVES;
}
else {
BLI_assert_unreachable();
}
paint->runtime.initialized = true;
}
uint BKE_paint_get_brush_tool_offset_from_paintmode(const PaintMode mode)
@ -1088,17 +1198,15 @@ eObjectMode BKE_paint_object_mode_from_paintmode(const PaintMode mode)
}
}
bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
bool BKE_paint_ensure(Main *bmain, ToolSettings *ts, Paint **r_paint)
{
Paint *paint = nullptr;
if (*r_paint) {
/* Tool offset should never be 0 for initialized paint settings, so it's a reliable way to
* check if already initialized. */
if ((*r_paint)->runtime.tool_offset == 0) {
if (!(*r_paint)->runtime.initialized) {
/* Currently only image painting is initialized this way, others have to be allocated. */
BLI_assert(ELEM(*r_paint, (Paint *)&ts->imapaint));
BKE_paint_runtime_init(ts, *r_paint);
paint_runtime_init(ts, *r_paint);
}
else {
BLI_assert(ELEM(*r_paint,
@ -1114,13 +1222,13 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
(Paint *)&ts->imapaint));
#ifndef NDEBUG
Paint paint_test = **r_paint;
BKE_paint_runtime_init(ts, *r_paint);
paint_runtime_init(ts, *r_paint);
/* Swap so debug doesn't hide errors when release fails. */
std::swap(**r_paint, paint_test);
BLI_assert(paint_test.runtime.ob_mode == (*r_paint)->runtime.ob_mode);
BLI_assert(paint_test.runtime.tool_offset == (*r_paint)->runtime.tool_offset);
#endif
}
paint_brush_set_from_asset_reference(bmain, *r_paint);
return true;
}
@ -1163,7 +1271,8 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
*r_paint = paint;
BKE_paint_runtime_init(ts, paint);
paint_runtime_init(ts, paint);
BKE_paint_brush_set_default(bmain, paint);
return false;
}
@ -1173,19 +1282,7 @@ void BKE_paint_init(Main *bmain, Scene *sce, PaintMode mode, const uchar col[3])
UnifiedPaintSettings *ups = &sce->toolsettings->unified_paint_settings;
Paint *paint = BKE_paint_get_active_from_paintmode(sce, mode);
BKE_paint_ensure_from_paintmode(sce, mode);
/* If there's no brush, create one */
Brush *brush = BKE_paint_brush(paint);
if (brush == nullptr) {
eObjectMode ob_mode = BKE_paint_object_mode_from_paintmode(mode);
brush = BKE_brush_first_search(bmain, ob_mode);
if (!brush) {
brush = BKE_brush_add(bmain, "Brush", ob_mode);
id_us_min(&brush->id); /* Fake user only. */
}
BKE_paint_brush_set(paint, brush);
}
BKE_paint_ensure_from_paintmode(bmain, sce, mode);
copy_v3_v3_uchar(paint->paint_cursor_col, col);
paint->paint_cursor_col[3] = 128;
@ -1200,23 +1297,21 @@ void BKE_paint_init(Main *bmain, Scene *sce, PaintMode mode, const uchar col[3])
void BKE_paint_free(Paint *paint)
{
BKE_curvemapping_free(paint->cavity_curve);
MEM_SAFE_FREE(paint->tool_slots);
MEM_delete(paint->brush_asset_reference);
}
void BKE_paint_copy(const Paint *src, Paint *tar, const int flag)
void BKE_paint_copy(const Paint *src, Paint *dst, const int flag)
{
tar->brush = src->brush;
tar->cavity_curve = BKE_curvemapping_copy(src->cavity_curve);
tar->tool_slots = static_cast<PaintToolSlot *>(MEM_dupallocN(src->tool_slots));
dst->brush = src->brush;
dst->cavity_curve = BKE_curvemapping_copy(src->cavity_curve);
if (src->brush_asset_reference) {
dst->brush_asset_reference = MEM_new<AssetWeakReference>(__func__,
*src->brush_asset_reference);
}
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus((ID *)tar->brush);
id_us_plus((ID *)tar->palette);
if (src->tool_slots != nullptr) {
for (int i = 0; i < tar->tool_slots_len; i++) {
id_us_plus((ID *)tar->tool_slots[i].brush);
}
}
id_us_plus((ID *)dst->palette);
}
}
@ -1237,7 +1332,9 @@ void BKE_paint_blend_write(BlendWriter *writer, Paint *p)
if (p->cavity_curve) {
BKE_curvemapping_blend_write(writer, p->cavity_curve);
}
BLO_write_struct_array(writer, PaintToolSlot, p->tool_slots_len, p->tool_slots);
if (p->brush_asset_reference) {
BKE_asset_weak_reference_write(writer, p->brush_asset_reference);
}
}
void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Paint *p)
@ -1250,17 +1347,13 @@ void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Pain
BKE_paint_cavity_curve_preset(p, CURVE_PRESET_LINE);
}
BLO_read_struct_array(reader, PaintToolSlot, p->tool_slots_len, &p->tool_slots);
/* Workaround for invalid data written in older versions. */
const size_t expected_size = sizeof(PaintToolSlot) * p->tool_slots_len;
if (p->tool_slots && MEM_allocN_len(p->tool_slots) < expected_size) {
MEM_freeN(p->tool_slots);
p->tool_slots = static_cast<PaintToolSlot *>(MEM_callocN(expected_size, "PaintToolSlot"));
BLO_read_struct(reader, AssetWeakReference, &p->brush_asset_reference);
if (p->brush_asset_reference) {
BKE_asset_weak_reference_read(reader, p->brush_asset_reference);
}
p->paint_cursor = nullptr;
BKE_paint_runtime_init(scene->toolsettings, p);
paint_runtime_init(scene->toolsettings, p);
}
bool paint_is_grid_face_hidden(const blender::BoundedBitSpan grid_hidden,
@ -1994,9 +2087,9 @@ void BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph,
}
}
void BKE_sculpt_toolsettings_data_ensure(Scene *scene)
void BKE_sculpt_toolsettings_data_ensure(Main *bmain, Scene *scene)
{
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->sculpt);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->sculpt);
Sculpt *sd = scene->toolsettings->sculpt;

View File

@ -1,167 +0,0 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <climits>
#include "MEM_guardedalloc.h"
#include "DNA_brush_types.h"
#include "DNA_scene_types.h"
#include "BLI_utildefines.h"
#include "BKE_brush.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_paint.hh"
/* -------------------------------------------------------------------- */
/** \name Tool Slot Initialization / Versioning
*
* These functions run to update old files (while versioning),
* take care only to perform low-level functions here.
* \{ */
void BKE_paint_toolslots_len_ensure(Paint *paint, int len)
{
/* Tool slots are 'uchar'. */
BLI_assert(len <= UCHAR_MAX);
if (paint->tool_slots_len < len) {
paint->tool_slots = static_cast<PaintToolSlot *>(
MEM_recallocN(paint->tool_slots, sizeof(*paint->tool_slots) * len));
paint->tool_slots_len = len;
}
}
static void paint_toolslots_init(Main *bmain, Paint *paint)
{
if (paint == nullptr) {
return;
}
const eObjectMode ob_mode = eObjectMode(paint->runtime.ob_mode);
BLI_assert(paint->runtime.tool_offset && ob_mode);
for (Brush *brush = static_cast<Brush *>(bmain->brushes.first); brush;
brush = static_cast<Brush *>(brush->id.next))
{
if (brush->ob_mode & ob_mode) {
const int slot_index = BKE_brush_tool_get(brush, paint);
BKE_paint_toolslots_len_ensure(paint, slot_index + 1);
if (paint->tool_slots[slot_index].brush == nullptr) {
paint->tool_slots[slot_index].brush = brush;
id_us_plus(&brush->id);
}
}
}
}
/**
* Initialize runtime since this is called from versioning code.
*/
static void paint_toolslots_init_with_runtime(Main *bmain, ToolSettings *ts, Paint *paint)
{
if (paint == nullptr) {
return;
}
/* Needed so #Paint_Runtime is updated when versioning. */
BKE_paint_runtime_init(ts, paint);
paint_toolslots_init(bmain, paint);
}
void BKE_paint_toolslots_init_from_main(Main *bmain)
{
for (Scene *scene = static_cast<Scene *>(bmain->scenes.first); scene;
scene = static_cast<Scene *>(scene->id.next))
{
ToolSettings *ts = scene->toolsettings;
paint_toolslots_init_with_runtime(bmain, ts, &ts->imapaint.paint);
if (ts->sculpt) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->sculpt->paint);
}
if (ts->vpaint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->vpaint->paint);
}
if (ts->wpaint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->wpaint->paint);
}
if (ts->gp_paint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->gp_paint->paint);
}
if (ts->gp_vertexpaint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->gp_vertexpaint->paint);
}
if (ts->gp_sculptpaint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->gp_sculptpaint->paint);
}
if (ts->gp_weightpaint) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->gp_weightpaint->paint);
}
if (ts->curves_sculpt) {
paint_toolslots_init_with_runtime(bmain, ts, &ts->curves_sculpt->paint);
}
}
}
/** \} */
void BKE_paint_toolslots_brush_update_ex(Paint *paint, Brush *brush)
{
const uint tool_offset = paint->runtime.tool_offset;
UNUSED_VARS_NDEBUG(tool_offset);
BLI_assert(tool_offset != 0);
const int slot_index = BKE_brush_tool_get(brush, paint);
BKE_paint_toolslots_len_ensure(paint, slot_index + 1);
PaintToolSlot *tslot = &paint->tool_slots[slot_index];
id_us_plus(&brush->id);
if (tslot->brush) {
id_us_min(&tslot->brush->id);
}
tslot->brush = brush;
}
void BKE_paint_toolslots_brush_update(Paint *paint)
{
if (paint->brush == nullptr) {
return;
}
BKE_paint_toolslots_brush_update_ex(paint, paint->brush);
}
void BKE_paint_toolslots_brush_validate(Main *bmain, Paint *paint)
{
/* Clear slots with invalid slots or mode (unlikely but possible). */
const uint tool_offset = paint->runtime.tool_offset;
UNUSED_VARS_NDEBUG(tool_offset);
const eObjectMode ob_mode = eObjectMode(paint->runtime.ob_mode);
BLI_assert(tool_offset && ob_mode);
for (int i = 0; i < paint->tool_slots_len; i++) {
PaintToolSlot *tslot = &paint->tool_slots[i];
if (tslot->brush) {
if ((i != BKE_brush_tool_get(tslot->brush, paint)) || (tslot->brush->ob_mode & ob_mode) == 0)
{
id_us_min(&tslot->brush->id);
tslot->brush = nullptr;
}
}
}
/* Unlikely but possible the active brush is not currently using a slot. */
BKE_paint_toolslots_brush_update(paint);
/* Fill slots from brushes. */
paint_toolslots_init(bmain, paint);
}
Brush *BKE_paint_toolslots_brush_get(Paint *paint, int slot_index)
{
if (slot_index < paint->tool_slots_len) {
PaintToolSlot *tslot = &paint->tool_slots[slot_index];
return tslot->brush;
}
return nullptr;
}

View File

@ -20,10 +20,12 @@
#include "BLI_string_utils.hh"
#include "BKE_appdir.hh"
#include "BKE_asset.hh"
#include "BKE_preferences.h"
#include "BLT_translation.hh"
#include "DNA_asset_types.h"
#include "DNA_defaults.h"
#include "DNA_userdef_types.h"
@ -399,5 +401,67 @@ int BKE_preferences_extension_repo_get_index(const UserDef *userdef,
{
return BLI_findindex(&userdef->extension_repos, repo);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #bUserAssetShelfSettings
* \{ */
static bUserAssetShelfSettings *asset_shelf_settings_new(UserDef *userdef,
const char *shelf_idname)
{
bUserAssetShelfSettings *settings = DNA_struct_default_alloc(bUserAssetShelfSettings);
BLI_addtail(&userdef->asset_shelves_settings, settings);
STRNCPY(settings->shelf_idname, shelf_idname);
BLI_assert(BLI_listbase_is_empty(&settings->enabled_catalog_paths));
return settings;
}
static bUserAssetShelfSettings *asset_shelf_settings_ensure(UserDef *userdef,
const char *shelf_idname)
{
if (bUserAssetShelfSettings *settings = BKE_preferences_asset_shelf_settings_get(userdef,
shelf_idname))
{
return settings;
}
return asset_shelf_settings_new(userdef, shelf_idname);
}
bUserAssetShelfSettings *BKE_preferences_asset_shelf_settings_get(const UserDef *userdef,
const char *shelf_idname)
{
return static_cast<bUserAssetShelfSettings *>(
BLI_findstring(&userdef->asset_shelves_settings,
shelf_idname,
offsetof(bUserAssetShelfSettings, shelf_idname)));
}
bool BKE_preferences_asset_shelf_settings_is_catalog_path_enabled(const UserDef *userdef,
const char *shelf_idname,
const char *catalog_path)
{
const bUserAssetShelfSettings *settings = BKE_preferences_asset_shelf_settings_get(userdef,
shelf_idname);
if (!settings) {
return false;
}
return BKE_asset_catalog_path_list_has_path(settings->enabled_catalog_paths, catalog_path);
}
bool BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(UserDef *userdef,
const char *shelf_idname,
const char *catalog_path)
{
if (BKE_preferences_asset_shelf_settings_is_catalog_path_enabled(
userdef, shelf_idname, catalog_path))
{
return false;
}
bUserAssetShelfSettings *settings = asset_shelf_settings_ensure(userdef, shelf_idname);
BKE_asset_catalog_path_list_add_path(settings->enabled_catalog_paths, catalog_path);
return true;
}
/** \} */

View File

@ -588,27 +588,7 @@ static void scene_foreach_paint(LibraryForeachIDData *data,
SCENE_FOREACH_UNDO_RESTORE,
reader,
&paint_old->brush,
IDWALK_CB_USER);
for (int i = 0; i < paint_old->tool_slots_len; i++) {
/* This is a bit tricky.
* - In case we do not do `undo_restore`, `paint` and `paint_old` pointers are the same, so
* this is equivalent to simply looping over slots from `paint`.
* - In case we do `undo_restore`, we only want to consider the slots from the old one, since
* those are the one we keep in the end.
* + In case the new data has less valid slots, we feed in a dummy null pointer.
* + In case the new data has more valid slots, the extra ones are ignored.
*/
brush_tmp = nullptr;
brush_p = (paint && i < paint->tool_slots_len) ? &paint->tool_slots[i].brush : &brush_tmp;
BKE_LIB_FOREACHID_UNDO_PRESERVE_PROCESS_IDSUPER_P(data,
brush_p,
do_undo_restore,
SCENE_FOREACH_UNDO_RESTORE,
reader,
&paint_old->tool_slots[i].brush,
IDWALK_CB_USER);
}
IDWALK_CB_NOP);
Palette *palette_tmp = nullptr;
Palette **palette_p = paint ? &paint->palette : &palette_tmp;
@ -1005,6 +985,9 @@ static void scene_foreach_path(ID *id, BPathForeachPathData *bpath_data)
if (scene->ed != nullptr) {
SEQ_for_each_callback(&scene->ed->seqbase, seq_foreach_path_callback, bpath_data);
}
/* TODO: Also handle `asset_weak_reference` here? Probably not, conceptually this is not a file
* path. */
}
static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_address)

View File

@ -3375,6 +3375,7 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead)
BLO_read_struct_list(reader, bUserScriptDirectory, &user->script_directories);
BLO_read_struct_list(reader, bUserAssetLibrary, &user->asset_libraries);
BLO_read_struct_list(reader, bUserExtensionRepo, &user->extension_repos);
BLO_read_struct_list(reader, bUserAssetShelfSettings, &user->asset_shelves_settings);
LISTBASE_FOREACH (wmKeyMap *, keymap, &user->user_keymaps) {
keymap->modal_items = nullptr;
@ -3422,6 +3423,10 @@ static BHead *read_userdef(BlendFileData *bfd, FileData *fd, BHead *bhead)
IDP_BlendDataRead(reader, &addon->prop);
}
LISTBASE_FOREACH (bUserAssetShelfSettings *, shelf_settings, &user->asset_shelves_settings) {
BKE_asset_catalog_path_list_blend_read_data(reader, shelf_settings->enabled_catalog_paths);
}
/* XXX */
user->uifonts.first = user->uifonts.last = nullptr;

View File

@ -2554,7 +2554,6 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
brush->gpencil_tool = brush->gpencil_settings->brush_type;
}
}
BKE_paint_toolslots_init_from_main(bmain);
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 280, 38)) {
@ -2874,10 +2873,10 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
ToolSettings *ts = scene->toolsettings;
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PaintMode::GPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::GPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::WeightGPencil);
/* Set default Draw brush. */
if (brush != nullptr) {
@ -2918,9 +2917,9 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
/* Reset all grease pencil brushes. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::WeightGPencil);
}
}

View File

@ -27,6 +27,7 @@
#include "DNA_movieclip_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "DNA_workspace_types.h"
#include "DNA_world_types.h"
#include "DNA_defaults.h"
@ -50,6 +51,7 @@
#include "BKE_armature.hh"
#include "BKE_attribute.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_curve.hh"
#include "BKE_effect.h"
#include "BKE_grease_pencil.hh"
@ -59,6 +61,7 @@
#include "BKE_mesh_legacy_convert.hh"
#include "BKE_nla.h"
#include "BKE_node_runtime.hh"
#include "BKE_paint.hh"
#include "BKE_scene.hh"
#include "BKE_tracking.h"
@ -2022,6 +2025,33 @@ static bool seq_filter_bilinear_to_auto(Sequence *seq, void * /*user_data*/)
return true;
}
static void update_paint_modes_for_brush_assets(Main &bmain)
{
/* Replace paint brushes with a reference to the default brush asset for that mode. */
LISTBASE_FOREACH (Scene *, scene, &bmain.scenes) {
BKE_paint_brush_set_default_references(scene->toolsettings);
}
/* Replace persistent tool references with the new single builtin brush tool. */
LISTBASE_FOREACH (WorkSpace *, workspace, &bmain.workspaces) {
LISTBASE_FOREACH (bToolRef *, tref, &workspace->tools) {
if (tref->space_type != SPACE_VIEW3D) {
continue;
}
if (!ELEM(tref->mode,
CTX_MODE_SCULPT,
CTX_MODE_SCULPT_CURVES,
CTX_MODE_PAINT_TEXTURE,
CTX_MODE_PAINT_VERTEX,
CTX_MODE_PAINT_WEIGHT))
{
continue;
}
STRNCPY(tref->idname, "builtin.brush");
}
}
}
static void image_settings_avi_to_ffmpeg(Scene *scene)
{
if (ELEM(scene->r.im_format.imtype, R_IMF_IMTYPE_AVIRAW, R_IMF_IMTYPE_AVIJPEG)) {
@ -3236,6 +3266,10 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 24)) {
update_paint_modes_for_brush_assets(*bmain);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -21,6 +21,7 @@
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_mempool.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
@ -555,9 +556,9 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
}
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::VertexGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::SculptGPencil);
BKE_paint_ensure_from_paintmode(bmain, scene, PaintMode::WeightGPencil);
/* Enable cursor. */
if (ts->gp_paint) {
@ -908,4 +909,26 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
}
}
}
{
/* Remove default brushes replaced by assets. Also remove outliner `treestore` that may point
* to brushes. Normally the treestore is updated properly but it doesn't seem to update during
* versioning code. It's not helpful anyway. */
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
LISTBASE_FOREACH (SpaceLink *, space_link, &area->spacedata) {
if (space_link->spacetype == SPACE_OUTLINER) {
SpaceOutliner *space_outliner = reinterpret_cast<SpaceOutliner *>(space_link);
if (space_outliner->treestore) {
BLI_mempool_destroy(space_outliner->treestore);
space_outliner->treestore = nullptr;
}
}
}
}
}
LISTBASE_FOREACH_MUTABLE (Brush *, brush, &bmain->brushes) {
BKE_id_delete(bmain, brush);
}
}
}

View File

@ -950,6 +950,15 @@ void blo_do_versions_userdef(UserDef *userdef)
}
}
{
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/Cloth");
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/General");
BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
userdef, "VIEW3D_AST_brush_sculpt", "Brushes/Mesh/Sculpt/Painting");
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@ -100,6 +100,7 @@
#include "MEM_guardedalloc.h" /* MEM_freeN */
#include "BKE_asset.hh"
#include "BKE_blender_version.h"
#include "BKE_bpath.hh"
#include "BKE_global.hh" /* For #Global `G`. */
@ -934,6 +935,12 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef)
LISTBASE_FOREACH (const bUserExtensionRepo *, repo_ref, &userdef->extension_repos) {
BLO_write_struct(writer, bUserExtensionRepo, repo_ref);
}
LISTBASE_FOREACH (
const bUserAssetShelfSettings *, shelf_settings, &userdef->asset_shelves_settings)
{
BLO_write_struct(writer, bUserAssetShelfSettings, shelf_settings);
BKE_asset_catalog_path_list_blend_write(writer, shelf_settings->enabled_catalog_paths);
}
LISTBASE_FOREACH (const uiStyle *, style, &userdef->uistyles) {
BLO_write_struct(writer, uiStyle, style);

View File

@ -34,6 +34,7 @@ set(SRC
intern/asset_shelf.cc
intern/asset_shelf_asset_view.cc
intern/asset_shelf_catalog_selector.cc
intern/asset_shelf_popup.cc
intern/asset_shelf_regiondata.cc
intern/asset_shelf_settings.cc
intern/asset_temp_id_consumer.cc

View File

@ -17,9 +17,16 @@ struct bContextDataResult;
struct BlendDataReader;
struct BlendWriter;
struct Main;
struct SpaceType;
struct uiBlock;
struct RegionPollParams;
struct wmWindowManager;
namespace blender {
class StringRef;
class StringRefNull;
} // namespace blender
namespace blender::ed::asset::shelf {
/* -------------------------------------------------------------------- */
@ -45,6 +52,7 @@ void region_on_user_resize(const ARegion *region);
void region_listen(const wmRegionListenerParams *params);
void region_layout(const bContext *C, ARegion *region);
void region_draw(const bContext *C, ARegion *region);
void region_on_poll_success(const bContext *C, ARegion *region);
void region_blend_read_data(BlendDataReader *reader, ARegion *region);
void region_blend_write(BlendWriter *writer, ARegion *region);
int region_prefsizey();
@ -57,6 +65,22 @@ void header_regiontype_register(ARegionType *region_type, const int space_type);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Asset shelf type */
bool type_poll(const bContext &C, const SpaceType &space_type, const AssetShelfType *shelf_type);
AssetShelfType *type_find_from_idname(const SpaceType &space_type, StringRefNull idname);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Asset shelf popup */
uiBlock *popup_block_create(const bContext *C, ARegion *region, AssetShelfType *shelf_type);
void type_popup_unlink(const AssetShelfType &shelf_type);
/** \} */
/* -------------------------------------------------------------------- */
void type_unlink(const Main &bmain, const AssetShelfType &shelf_type);
@ -64,6 +88,8 @@ void type_unlink(const Main &bmain, const AssetShelfType &shelf_type);
int tile_width(const AssetShelfSettings &settings);
int tile_height(const AssetShelfSettings &settings);
AssetShelf *active_shelf_from_area(const ScrArea *area);
int context(const bContext *C, const char *member, bContextDataResult *result);
} // namespace blender::ed::asset::shelf

View File

@ -16,7 +16,8 @@ namespace blender::ed::asset {
bool id_type_is_non_experimental(const ID *id);
#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS \
(FILTER_ID_MA | FILTER_ID_GR | FILTER_ID_OB | FILTER_ID_AC | FILTER_ID_WO | FILTER_ID_NT)
(FILTER_ID_BR | FILTER_ID_MA | FILTER_ID_GR | FILTER_ID_OB | FILTER_ID_AC | FILTER_ID_WO | \
FILTER_ID_NT)
/**
* Check if the asset type for \a id (which doesn't need to be an asset right now) can be an asset,
@ -39,6 +40,6 @@ int64_t types_supported_as_filter_flags();
* Should start with a consonant, so usages can prefix it with "a" (not "an").
*/
#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING \
"Material, Collection, Object, Pose Action, Node Group or World"
"Material, Collection, Object, Brush, Pose Action, Node Group or World"
} // namespace blender::ed::asset

View File

@ -142,7 +142,7 @@ void AssetList::setup()
/* Relevant bits from file_refresh(). */
/* TODO pass options properly. */
filelist_setrecursion(files, FILE_SELECT_MAX_RECURSIONS);
filelist_setsorting(files, FILE_SORT_ALPHA, false);
filelist_setsorting(files, FILE_SORT_ASSET_CATALOG, false);
filelist_setlibrary(files, &library_ref_);
filelist_setfilter_options(
files,
@ -272,6 +272,7 @@ void AssetList::clear(const bContext *C)
filelist_readjob_stop(files, CTX_wm_manager(C));
filelist_freelib(files);
filelist_clear(files);
filelist_tag_force_reset(files);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr);
}

View File

@ -89,7 +89,7 @@ static const asset_system::AssetRepresentation *get_local_asset_from_relative_id
return matching_asset;
}
static const asset_system::AssetRepresentation *find_asset_from_weak_ref(
const asset_system::AssetRepresentation *find_asset_from_weak_ref(
const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports)
{
if (weak_ref.asset_library_type == ASSET_LIBRARY_LOCAL) {

View File

@ -13,6 +13,7 @@
#include "AS_asset_catalog_path.hh"
#include "AS_asset_library.hh"
#include "BLI_function_ref.hh"
#include "BLI_string.h"
#include "BKE_context.hh"
@ -51,9 +52,7 @@ void send_redraw_notifier(const bContext &C)
/** \name Shelf Type
* \{ */
static bool asset_shelf_type_poll(const bContext &C,
const SpaceType &space_type,
const AssetShelfType *shelf_type)
bool type_poll(const bContext &C, const SpaceType &space_type, const AssetShelfType *shelf_type)
{
if (!shelf_type) {
return false;
@ -70,7 +69,17 @@ static bool asset_shelf_type_poll(const bContext &C,
return !shelf_type->poll || shelf_type->poll(&C, shelf_type);
}
static AssetShelfType *asset_shelf_type_ensure(const SpaceType &space_type, AssetShelf &shelf)
AssetShelfType *type_find_from_idname(const SpaceType &space_type, StringRefNull idname)
{
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type.asset_shelf_types) {
if (idname == shelf_type->idname) {
return shelf_type.get();
}
}
return nullptr;
}
AssetShelfType *type_ensure(const SpaceType &space_type, AssetShelf &shelf)
{
if (shelf.type) {
return shelf.type;
@ -86,11 +95,12 @@ static AssetShelfType *asset_shelf_type_ensure(const SpaceType &space_type, Asse
return nullptr;
}
static AssetShelf *create_shelf_from_type(AssetShelfType &type)
AssetShelf *create_shelf_from_type(AssetShelfType &type)
{
AssetShelf *shelf = MEM_new<AssetShelf>(__func__);
*shelf = dna::shallow_zero_initialize();
shelf->settings.preview_size = DEFAULT_TILE_SIZE;
shelf->settings.preview_size = type.default_preview_size ? type.default_preview_size :
DEFAULT_TILE_SIZE;
shelf->settings.asset_library_reference = asset_system::all_library_reference();
shelf->type = &type;
shelf->preferred_row_count = 1;
@ -133,19 +143,21 @@ static void activate_shelf(RegionAssetShelf &shelf_regiondata, AssetShelf &shelf
*
* The returned shelf is guaranteed to have its #AssetShelf.type pointer set.
*
* \param on_create: Function called when a new asset shelf is created (case 3).
*
* \return A non-owning pointer to the now active shelf. Might be null if no shelf is valid in
* current context (all polls failed).
*/
static AssetShelf *update_active_shelf(const bContext &C,
const SpaceType &space_type,
RegionAssetShelf &shelf_regiondata)
RegionAssetShelf &shelf_regiondata,
FunctionRef<void(AssetShelf &new_shelf)> on_create)
{
/* Note: Don't access #AssetShelf.type directly, use #asset_shelf_type_ensure(). */
/* Case 1: */
if (shelf_regiondata.active_shelf &&
asset_shelf_type_poll(
C, space_type, asset_shelf_type_ensure(space_type, *shelf_regiondata.active_shelf)))
type_poll(C, space_type, type_ensure(space_type, *shelf_regiondata.active_shelf)))
{
/* Not a strong precondition, but if this is wrong something weird might be going on. */
BLI_assert(shelf_regiondata.active_shelf == shelf_regiondata.shelves.first);
@ -159,7 +171,7 @@ static AssetShelf *update_active_shelf(const bContext &C,
continue;
}
if (asset_shelf_type_poll(C, space_type, asset_shelf_type_ensure(space_type, *shelf))) {
if (type_poll(C, space_type, type_ensure(space_type, *shelf))) {
/* Found a valid previously activated shelf, reactivate it. */
activate_shelf(shelf_regiondata, *shelf);
return shelf;
@ -168,11 +180,14 @@ static AssetShelf *update_active_shelf(const bContext &C,
/* Case 3: */
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type.asset_shelf_types) {
if (asset_shelf_type_poll(C, space_type, shelf_type.get())) {
if (type_poll(C, space_type, shelf_type.get())) {
AssetShelf *new_shelf = create_shelf_from_type(*shelf_type);
BLI_addhead(&shelf_regiondata.shelves, new_shelf);
/* Moves ownership to the regiondata. */
activate_shelf(shelf_regiondata, *new_shelf);
if (on_create) {
on_create(*new_shelf);
}
return new_shelf;
}
}
@ -216,7 +231,7 @@ static bool asset_shelf_space_poll(const bContext *C, const SpaceLink *space_lin
/* Is there any asset shelf type registered that returns true for it's poll? */
for (const std::unique_ptr<AssetShelfType> &shelf_type : space_type->asset_shelf_types) {
if (asset_shelf_type_poll(*C, *space_type, shelf_type.get())) {
if (type_poll(*C, *space_type, shelf_type.get())) {
return true;
}
}
@ -247,6 +262,9 @@ static void asset_shelf_region_listen(const wmRegionListenerParams *params)
ED_region_tag_redraw(region);
}
break;
case NC_ASSET:
ED_region_tag_redraw(region);
break;
}
}
@ -263,13 +281,13 @@ void region_listen(const wmRegionListenerParams *params)
void region_init(wmWindowManager *wm, ARegion *region)
{
if (!region->regiondata) {
region->regiondata = MEM_cnew<RegionAssetShelf>("RegionAssetShelf");
}
RegionAssetShelf &shelf_regiondata = *RegionAssetShelf::get_from_asset_shelf_region(*region);
/* Region-data should've been created by a previously called #region_before_redraw(). */
RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region);
BLI_assert_msg(
shelf_regiondata,
"Region-data should've been created by a previously called `region_before_redraw()`.");
/* Active shelf is only set on draw, so this may be null! */
AssetShelf *active_shelf = shelf_regiondata.active_shelf;
AssetShelf *active_shelf = shelf_regiondata->active_shelf;
UI_view2d_region_reinit(&region->v2d, V2D_COMMONVIEW_PANELS_UI, region->winx, region->winy);
@ -413,17 +431,12 @@ int region_prefsizey()
void region_layout(const bContext *C, ARegion *region)
{
const SpaceLink *space = CTX_wm_space_data(C);
SpaceType *space_type = BKE_spacetype_from_id(space->spacetype);
RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(*region);
if (!shelf_regiondata) {
/* Region-data should've been created by a previously called #region_init(). */
BLI_assert_unreachable();
return;
}
BLI_assert_msg(
shelf_regiondata,
"Region-data should've been created by a previously called `region_before_redraw()`.");
AssetShelf *active_shelf = update_active_shelf(*C, *space_type, *shelf_regiondata);
const AssetShelf *active_shelf = shelf_regiondata->active_shelf;
if (!active_shelf) {
return;
}
@ -475,6 +488,28 @@ void region_draw(const bContext *C, ARegion *region)
UI_view2d_scrollers_draw(&region->v2d, nullptr);
}
void region_on_poll_success(const bContext *C, ARegion *region)
{
RegionAssetShelf *shelf_regiondata = RegionAssetShelf::ensure_from_asset_shelf_region(*region);
if (!shelf_regiondata) {
BLI_assert_unreachable();
return;
}
ScrArea *area = CTX_wm_area(C);
update_active_shelf(
*C, *area->type, *shelf_regiondata, /*on_create=*/[&](AssetShelf &new_shelf) {
/* Update region visibility (`'DEFAULT_VISIBLE'` option). */
const int old_flag = region->flag;
SET_FLAG_FROM_TEST(region->flag,
(new_shelf.type->flag & ASSET_SHELF_TYPE_FLAG_DEFAULT_VISIBLE) == 0,
RGN_FLAG_HIDDEN);
if (old_flag != region->flag) {
ED_region_visibility_change_update(const_cast<bContext *>(C), area, region);
}
});
}
void header_region_listen(const wmRegionListenerParams *params)
{
asset_shelf_region_listen(params);
@ -489,15 +524,6 @@ void header_region_init(wmWindowManager * /*wm*/, ARegion *region)
void header_region(const bContext *C, ARegion *region)
{
const SpaceLink *space = CTX_wm_space_data(C);
SpaceType *space_type = BKE_spacetype_from_id(space->spacetype);
const ARegion *main_shelf_region = BKE_area_find_region_type(CTX_wm_area(C),
RGN_TYPE_ASSET_SHELF);
RegionAssetShelf *shelf_regiondata = RegionAssetShelf::get_from_asset_shelf_region(
*main_shelf_region);
update_active_shelf(*C, *space_type, *shelf_regiondata);
ED_region_header_with_button_sections(C, region, uiButtonSectionsAlign::Bottom);
}
@ -536,7 +562,7 @@ void region_blend_write(BlendWriter *writer, ARegion *region)
/** \name Asset Shelf Context
* \{ */
static AssetShelf *active_shelf_from_area(const ScrArea *area)
AssetShelf *active_shelf_from_area(const ScrArea *area)
{
const ARegion *shelf_region = BKE_area_find_region_type(area, RGN_TYPE_ASSET_SHELF);
if (!shelf_region) {
@ -601,7 +627,7 @@ int context(const bContext *C, const char *member, bContextDataResult *result)
/* XXX hack. Get the asset from the active item, but needs to be the file... */
if (CTX_data_equals(member, "active_file")) {
const ARegion *region = CTX_wm_region(C);
const uiBut *but = UI_region_views_find_active_item_but(region);
const uiBut *but = UI_region_views_find_mouse_over_but(CTX_wm_window(C), region);
if (!but) {
return CTX_RESULT_NO_DATA;
}
@ -668,9 +694,10 @@ static uiBut *add_tab_button(uiBlock &block, StringRefNull name)
return but;
}
static void add_catalog_tabs(AssetShelfSettings &shelf_settings, uiLayout &layout)
static void add_catalog_tabs(AssetShelf &shelf, uiLayout &layout)
{
uiBlock *block = uiLayoutGetBlock(&layout);
AssetShelfSettings &shelf_settings = shelf.settings;
/* "All" tab. */
{
@ -687,18 +714,17 @@ static void add_catalog_tabs(AssetShelfSettings &shelf_settings, uiLayout &layou
uiItemS(&layout);
/* Regular catalog tabs. */
settings_foreach_enabled_catalog_path(
shelf_settings, [&](const asset_system::AssetCatalogPath &path) {
uiBut *but = add_tab_button(*block, path.name());
settings_foreach_enabled_catalog_path(shelf, [&](const asset_system::AssetCatalogPath &path) {
uiBut *but = add_tab_button(*block, path.name());
UI_but_func_set(but, [&shelf_settings, path](bContext &C) {
settings_set_active_catalog(shelf_settings, path);
send_redraw_notifier(C);
});
UI_but_func_pushed_state_set(but, [&shelf_settings, path](const uiBut &) -> bool {
return settings_is_active_catalog(shelf_settings, path);
});
});
UI_but_func_set(but, [&shelf_settings, path](bContext &C) {
settings_set_active_catalog(shelf_settings, path);
send_redraw_notifier(C);
});
UI_but_func_pushed_state_set(but, [&shelf_settings, path](const uiBut &) -> bool {
return settings_is_active_catalog(shelf_settings, path);
});
});
}
/** \} */
@ -724,9 +750,8 @@ static void asset_shelf_header_draw(const bContext *C, Header *header)
uiItemS(layout);
PointerRNA shelf_ptr = active_shelf_ptr_from_context(C);
AssetShelf *shelf = static_cast<AssetShelf *>(shelf_ptr.data);
if (shelf) {
add_catalog_tabs(shelf->settings, *layout);
if (AssetShelf *shelf = static_cast<AssetShelf *>(shelf_ptr.data)) {
add_catalog_tabs(*shelf, *layout);
}
uiItemSpacer(layout);
@ -788,6 +813,8 @@ void type_unlink(const Main &bmain, const AssetShelfType &shelf_type)
}
}
}
type_popup_unlink(shelf_type);
}
/** \} */

View File

@ -9,14 +9,19 @@
#pragma once
#include "BLI_function_ref.hh"
#include "BLI_string_ref.hh"
struct ARegion;
struct ARegionType;
struct AssetLibraryReference;
struct AssetShelf;
struct AssetShelfType;
struct AssetShelfSettings;
struct bContext;
struct BlendDataReader;
struct BlendWriter;
struct RegionAssetShelf;
struct SpaceType;
struct uiLayout;
namespace blender::asset_system {
@ -39,6 +44,11 @@ AssetShelf *active_shelf_from_context(const bContext *C);
void send_redraw_notifier(const bContext &C);
AssetShelfType *type_ensure(const SpaceType &space_type, AssetShelf &shelf);
AssetShelf *create_shelf_from_type(AssetShelfType &type);
void library_selector_draw(const bContext *C, uiLayout *layout, AssetShelf &shelf);
/**
* Deep-copies \a shelf_regiondata into newly allocated memory. Must be freed using
* #regiondata_free().
@ -52,20 +62,24 @@ void regiondata_blend_read_data(BlendDataReader *reader, RegionAssetShelf **shel
void settings_blend_write(BlendWriter *writer, const AssetShelfSettings &settings);
void settings_blend_read_data(BlendDataReader *reader, AssetShelfSettings &settings);
void settings_clear_enabled_catalogs(AssetShelfSettings &settings);
void settings_set_active_catalog(AssetShelfSettings &settings,
const asset_system::AssetCatalogPath &path);
void settings_set_all_catalog_active(AssetShelfSettings &settings);
bool settings_is_active_catalog(const AssetShelfSettings &settings,
const asset_system::AssetCatalogPath &path);
bool settings_is_all_catalog_active(const AssetShelfSettings &settings);
bool settings_is_catalog_path_enabled(const AssetShelfSettings &settings,
/**
* Clears the list of enabled catalogs in either the Preferences (if any) or the asset shelf
* settings (if any), depending on the #ASSET_SHELF_TYPE_FLAG_STORE_CATALOGS_IN_PREFS flag.
*/
void settings_clear_enabled_catalogs(AssetShelf &shelf);
bool settings_is_catalog_path_enabled(const AssetShelf &shelf,
const asset_system::AssetCatalogPath &path);
void settings_set_catalog_path_enabled(AssetShelfSettings &settings,
void settings_set_catalog_path_enabled(AssetShelf &shelf,
const asset_system::AssetCatalogPath &path);
void settings_foreach_enabled_catalog_path(
const AssetShelfSettings &settings,
const AssetShelf &shelf,
FunctionRef<void(const asset_system::AssetCatalogPath &catalog_path)> fn);
} // namespace blender::ed::asset::shelf

View File

@ -11,6 +11,7 @@
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BKE_asset.hh"
#include "BKE_screen.hh"
#include "BLI_fnmatch.h"
@ -21,6 +22,7 @@
#include "ED_asset_handle.hh"
#include "ED_asset_list.hh"
#include "ED_asset_menu_utils.hh"
#include "ED_asset_shelf.hh"
#include "UI_grid_view.hh"
@ -38,16 +40,19 @@ namespace blender::ed::asset::shelf {
class AssetView : public ui::AbstractGridView {
const AssetLibraryReference library_ref_;
const AssetShelf &shelf_;
std::optional<AssetWeakReference> active_asset_;
/** Copy of the filter string from #AssetShelfSettings, with extra '*' added to the beginning and
* end of the string, for `fnmatch()` to work. */
char search_string[sizeof(AssetShelfSettings::search_string) + 2] = "";
std::optional<asset_system::AssetCatalogFilter> catalog_filter_ = std::nullopt;
bool is_popup_ = false;
friend class AssetViewItem;
friend class AssetDragController;
public:
AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf);
AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf, bool is_popup);
~AssetView();
void build_items() override;
bool begin_filtering(const bContext &C) const override;
@ -68,6 +73,8 @@ class AssetViewItem : public ui::PreviewGridItem {
void disable_asset_drag();
void build_grid_tile(uiLayout &layout) const override;
void build_context_menu(bContext &C, uiLayout &column) const override;
void on_activate(bContext &C) override;
std::optional<bool> should_be_active() const override;
bool is_filtered_visible() const override;
std::unique_ptr<ui::AbstractViewItemDragController> create_drag_controller() const override;
@ -83,15 +90,27 @@ class AssetDragController : public ui::AbstractViewItemDragController {
void *create_drag_data() const override;
};
AssetView::AssetView(const AssetLibraryReference &library_ref, const AssetShelf &shelf)
: library_ref_(library_ref), shelf_(shelf)
AssetView::AssetView(const AssetLibraryReference &library_ref,
const AssetShelf &shelf,
const bool is_popup)
: library_ref_(library_ref), shelf_(shelf), is_popup_(is_popup)
{
if (shelf.settings.search_string[0]) {
BLI_strncpy_ensure_pad(
search_string, shelf.settings.search_string, '*', sizeof(search_string));
}
if (shelf.type->get_active_asset) {
if (const AssetWeakReference *weak_ref = shelf.type->get_active_asset(shelf.type)) {
active_asset_ = *weak_ref;
}
else {
active_asset_.reset();
}
}
}
AssetView::~AssetView() {}
void AssetView::build_items()
{
const asset_system::AssetLibrary *library = list::library_get_once_available(library_ref_);
@ -192,16 +211,17 @@ void AssetViewItem::disable_asset_drag()
void AssetViewItem::build_grid_tile(uiLayout &layout) const
{
PointerRNA file_ptr = RNA_pointer_create(
nullptr,
&RNA_FileSelectEntry,
/* XXX passing file pointer here, should be asset handle or asset representation. */
const_cast<FileDirEntry *>(asset_.file_data));
const AssetView &asset_view = reinterpret_cast<const AssetView &>(this->get_view());
const AssetShelfType &shelf_type = *asset_view.shelf_.type;
uiBlock *block = uiLayoutGetBlock(&layout);
UI_but_context_ptr_set(
block, reinterpret_cast<uiBut *>(view_item_but_), "active_file", &file_ptr);
ui::PreviewGridItem::build_grid_tile(layout);
wmOperatorType *ot = WM_operatortype_find(shelf_type.activate_operator.c_str(), true);
PointerRNA op_props = PointerRNA_NULL;
if (ot) {
WM_operator_properties_create_ptr(&op_props, ot);
asset::operator_asset_reference_props_set(*handle_get_representation(&asset_), op_props);
}
ui::PreviewGridItem::build_grid_tile_button(layout, ot, &op_props);
}
void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const
@ -214,6 +234,27 @@ void AssetViewItem::build_context_menu(bContext &C, uiLayout &column) const
}
}
void AssetViewItem::on_activate(bContext & /*C*/)
{
const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view());
if (asset_view.is_popup_) {
UI_popup_menu_close_from_but(reinterpret_cast<uiBut *>(view_item_button()));
}
}
std::optional<bool> AssetViewItem::should_be_active() const
{
const AssetView &asset_view = dynamic_cast<const AssetView &>(get_view());
if (!asset_view.active_asset_) {
return false;
}
const asset_system::AssetRepresentation *asset = handle_get_representation(&asset_);
AssetWeakReference weak_ref = asset->make_weak_reference();
const bool matches = *asset_view.active_asset_ == weak_ref;
return matches;
}
bool AssetViewItem::is_filtered_visible() const
{
const AssetView &asset_view = dynamic_cast<const AssetView &>(this->get_view());
@ -255,7 +296,8 @@ void build_asset_view(uiLayout &layout,
BLI_assert(tile_width != 0);
BLI_assert(tile_height != 0);
std::unique_ptr asset_view = std::make_unique<AssetView>(library_ref, shelf);
const bool is_popup = region.regiontype == RGN_TYPE_TEMPORARY;
std::unique_ptr asset_view = std::make_unique<AssetView>(library_ref, shelf, is_popup);
asset_view->set_catalog_filter(catalog_filter_from_shelf_settings(shelf.settings, *library));
asset_view->set_tile_size(tile_width, tile_height);

View File

@ -73,7 +73,7 @@ class AssetCatalogSelectorTree : public ui::AbstractTreeView {
Item &build_catalog_items_recursive(ui::TreeViewOrItem &parent_view_item,
const asset_system::AssetCatalogTreeItem &catalog_item) const
{
Item &view_item = parent_view_item.add_tree_item<Item>(catalog_item, shelf_settings_);
Item &view_item = parent_view_item.add_tree_item<Item>(catalog_item, shelf_);
catalog_item.foreach_child(
[&view_item, this](const asset_system::AssetCatalogTreeItem &child) {
@ -92,12 +92,11 @@ class AssetCatalogSelectorTree : public ui::AbstractTreeView {
char catalog_path_enabled_ = false;
public:
Item(const asset_system::AssetCatalogTreeItem &catalog_item,
AssetShelfSettings &shelf_settings)
Item(const asset_system::AssetCatalogTreeItem &catalog_item, AssetShelf &shelf)
: ui::BasicTreeViewItem(catalog_item.get_name()),
catalog_item_(catalog_item),
catalog_path_enabled_(
settings_is_catalog_path_enabled(shelf_settings, catalog_item.catalog_path()))
settings_is_catalog_path_enabled(shelf, catalog_item.catalog_path()))
{
disable_activatable();
}
@ -166,41 +165,46 @@ class AssetCatalogSelectorTree : public ui::AbstractTreeView {
void AssetCatalogSelectorTree::update_shelf_settings_from_enabled_catalogs()
{
settings_clear_enabled_catalogs(shelf_settings_);
settings_clear_enabled_catalogs(shelf_);
foreach_item([this](ui::AbstractTreeViewItem &view_item) {
const auto &selector_tree_item = dynamic_cast<AssetCatalogSelectorTree::Item &>(view_item);
if (selector_tree_item.is_catalog_path_enabled()) {
settings_set_catalog_path_enabled(shelf_settings_, selector_tree_item.catalog_path());
settings_set_catalog_path_enabled(shelf_, selector_tree_item.catalog_path());
}
});
}
void library_selector_draw(const bContext *C, uiLayout *layout, AssetShelf &shelf)
{
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
PointerRNA shelf_ptr = RNA_pointer_create(&CTX_wm_screen(C)->id, &RNA_AssetShelf, &shelf);
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, &shelf_ptr, "asset_library_reference", UI_ITEM_NONE, "", ICON_NONE);
if (shelf.settings.asset_library_reference.type != ASSET_LIBRARY_LOCAL) {
uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_library_refresh");
}
}
static void catalog_selector_panel_draw(const bContext *C, Panel *panel)
{
const AssetLibraryReference *library_ref = CTX_wm_asset_library_ref(C);
AssetShelf *shelf = active_shelf_from_context(C);
if (!shelf) {
return;
}
uiLayout *layout = panel->layout;
uiBlock *block = uiLayoutGetBlock(layout);
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
library_selector_draw(C, layout, *shelf);
PointerRNA shelf_ptr = RNA_pointer_create(&CTX_wm_screen(C)->id, &RNA_AssetShelf, shelf);
uiLayout *row = uiLayoutRow(layout, true);
uiItemR(row, &shelf_ptr, "asset_library_reference", UI_ITEM_NONE, "", ICON_NONE);
if (library_ref->type != ASSET_LIBRARY_LOCAL) {
uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_library_refresh");
}
asset_system::AssetLibrary *library = list::library_get_once_available(*library_ref);
asset_system::AssetLibrary *library = list::library_get_once_available(
shelf->settings.asset_library_reference);
if (!library) {
return;
}
uiBlock *block = uiLayoutGetBlock(layout);
ui::AbstractTreeView *tree_view = UI_block_add_view(
*block,
"asset catalog tree view",

View File

@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edasset
*/
#include "asset_shelf.hh"
#include "BKE_screen.hh"
#include "BLT_translation.hh"
#include "UI_interface_c.hh"
#include "UI_tree_view.hh"
#include "ED_asset_filter.hh"
#include "ED_asset_list.hh"
#include "ED_asset_shelf.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
namespace blender::ed::asset::shelf {
class StaticPopupShelves {
public:
Vector<AssetShelf *> popup_shelves;
~StaticPopupShelves()
{
for (AssetShelf *shelf : popup_shelves) {
MEM_delete(shelf);
}
}
static Vector<AssetShelf *> &shelves()
{
static StaticPopupShelves storage;
return storage.popup_shelves;
}
};
void type_popup_unlink(const AssetShelfType &shelf_type)
{
for (AssetShelf *shelf : StaticPopupShelves::shelves()) {
if (shelf->type == &shelf_type) {
shelf->type = nullptr;
}
}
}
static AssetShelf *get_shelf_for_popup(const bContext *C, AssetShelfType &shelf_type)
{
const SpaceType *space_type = BKE_spacetype_from_id(shelf_type.space_type);
Vector<AssetShelf *> &popup_shelves = StaticPopupShelves::shelves();
for (AssetShelf *shelf : popup_shelves) {
if (STREQ(shelf->idname, shelf_type.idname)) {
if (type_poll(*C, *space_type, type_ensure(*space_type, *shelf))) {
return shelf;
}
break;
}
}
if (type_poll(*C, *space_type, &shelf_type)) {
AssetShelf *new_shelf = create_shelf_from_type(shelf_type);
popup_shelves.append(new_shelf);
return new_shelf;
}
return nullptr;
}
class AssetCatalogTreeView : public ui::AbstractTreeView {
AssetShelf &shelf_;
asset_system::AssetCatalogTree catalog_tree_;
public:
AssetCatalogTreeView(const asset_system::AssetLibrary &library, AssetShelf &shelf)
: shelf_(shelf)
{
catalog_tree_ = build_filtered_catalog_tree(
library,
shelf_.settings.asset_library_reference,
[this](const asset_system::AssetRepresentation &asset) {
return (!shelf_.type->asset_poll || shelf_.type->asset_poll(shelf_.type, &asset));
});
}
void build_tree() override
{
if (catalog_tree_.is_empty()) {
auto &item = this->add_tree_item<ui::BasicTreeViewItem>(RPT_("No applicable assets found"),
ICON_INFO);
item.disable_interaction();
return;
}
auto &all_item = this->add_tree_item<ui::BasicTreeViewItem>(IFACE_("All"));
all_item.set_on_activate_fn([this](bContext &C, ui::BasicTreeViewItem &) {
settings_set_all_catalog_active(shelf_.settings);
send_redraw_notifier(C);
});
all_item.set_is_active_fn(
[this]() { return settings_is_all_catalog_active(shelf_.settings); });
all_item.uncollapse_by_default();
catalog_tree_.foreach_root_item([&, this](
const asset_system::AssetCatalogTreeItem &catalog_item) {
ui::BasicTreeViewItem &item = this->build_catalog_items_recursive(all_item, catalog_item);
item.uncollapse_by_default();
});
}
ui::BasicTreeViewItem &build_catalog_items_recursive(
ui::TreeViewOrItem &parent_view_item,
const asset_system::AssetCatalogTreeItem &catalog_item) const
{
ui::BasicTreeViewItem &view_item = parent_view_item.add_tree_item<ui::BasicTreeViewItem>(
catalog_item.get_name());
std::string catalog_path = catalog_item.catalog_path().str();
view_item.set_on_activate_fn([this, catalog_path](bContext &C, ui::BasicTreeViewItem &) {
settings_set_active_catalog(shelf_.settings, catalog_path);
send_redraw_notifier(C);
});
view_item.set_is_active_fn([this, catalog_path]() {
return settings_is_active_catalog(shelf_.settings, catalog_path);
});
catalog_item.foreach_child(
[&view_item, this](const asset_system::AssetCatalogTreeItem &child) {
build_catalog_items_recursive(view_item, child);
});
return view_item;
}
};
static void catalog_tree_draw(uiLayout &layout, AssetShelf &shelf)
{
const asset_system::AssetLibrary *library = list::library_get_once_available(
shelf.settings.asset_library_reference);
if (!library) {
return;
}
uiBlock *block = uiLayoutGetBlock(&layout);
ui::AbstractTreeView *tree_view = UI_block_add_view(
*block,
"asset shelf catalog tree view",
std::make_unique<AssetCatalogTreeView>(*library, shelf));
ui::TreeViewBuilder::build_tree_view(*tree_view, layout);
}
uiBlock *popup_block_create(const bContext *C, ARegion *region, AssetShelfType *shelf_type)
{
uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS);
UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
AssetShelf *shelf = get_shelf_for_popup(C, *shelf_type);
if (!shelf) {
BLI_assert_unreachable();
return block;
}
const uiStyle *style = UI_style_get_dpi();
const float pad = 0.2f * UI_UNIT_Y; /* UI_MENU_PADDING */
uiLayout *layout = UI_block_layout(
block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, pad, 0, UI_UNIT_X * 40, 0, pad / 2, style);
PointerRNA library_ref_ptr = RNA_pointer_create(
&CTX_wm_screen(C)->id, &RNA_AssetLibraryReference, &shelf->settings.asset_library_reference);
uiLayoutSetContextPointer(layout, "asset_library_reference", &library_ref_ptr);
uiLayout *row = uiLayoutRow(layout, false);
uiLayout *sub = uiLayoutRow(row, false);
uiLayoutSetUnitsX(sub, 10);
uiLayoutSetFixedSize(sub, true);
uiLayout *catalogs_col = uiLayoutColumn(sub, false);
library_selector_draw(C, catalogs_col, *shelf);
catalog_tree_draw(*catalogs_col, *shelf);
uiLayout *asset_view_col = uiLayoutColumn(row, false);
build_asset_view(*asset_view_col, shelf->settings.asset_library_reference, *shelf, *C, *region);
return block;
}
} // namespace blender::ed::asset::shelf

View File

@ -25,6 +25,19 @@ RegionAssetShelf *RegionAssetShelf::get_from_asset_shelf_region(const ARegion &r
return static_cast<RegionAssetShelf *>(region.regiondata);
}
RegionAssetShelf *RegionAssetShelf::ensure_from_asset_shelf_region(ARegion &region)
{
if (region.regiontype != RGN_TYPE_ASSET_SHELF) {
/* Should only be called on main asset shelf region. */
BLI_assert_unreachable();
return nullptr;
}
if (!region.regiondata) {
region.regiondata = MEM_cnew<RegionAssetShelf>("RegionAssetShelf");
}
return static_cast<RegionAssetShelf *>(region.regiondata);
}
namespace blender::ed::asset::shelf {
RegionAssetShelf *regiondata_duplicate(const RegionAssetShelf *shelf_regiondata)

View File

@ -20,6 +20,10 @@
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BKE_asset.hh"
#include "BKE_preferences.h"
#include "BKE_screen.hh"
#include "asset_shelf.hh"
using namespace blender;
@ -45,18 +49,15 @@ AssetShelfSettings &AssetShelfSettings::operator=(const AssetShelfSettings &othe
if (active_catalog_path) {
active_catalog_path = BLI_strdup(other.active_catalog_path);
}
BLI_listbase_clear(&enabled_catalog_paths);
BKE_asset_catalog_path_list_free(enabled_catalog_paths);
enabled_catalog_paths = BKE_asset_catalog_path_list_duplicate(other.enabled_catalog_paths);
LISTBASE_FOREACH (LinkData *, catalog_path_item, &other.enabled_catalog_paths) {
LinkData *new_path_item = BLI_genericNodeN(BLI_strdup((char *)catalog_path_item->data));
BLI_addtail(&enabled_catalog_paths, new_path_item);
}
return *this;
}
AssetShelfSettings::~AssetShelfSettings()
{
shelf::settings_clear_enabled_catalogs(*this);
BKE_asset_catalog_path_list_free(enabled_catalog_paths);
MEM_delete(active_catalog_path);
}
@ -66,32 +67,16 @@ void settings_blend_write(BlendWriter *writer, const AssetShelfSettings &setting
{
BLO_write_struct(writer, AssetShelfSettings, &settings);
LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) {
BLO_write_struct(writer, LinkData, catalog_path_item);
BLO_write_string(writer, (const char *)catalog_path_item->data);
}
BKE_asset_catalog_path_list_blend_write(writer, settings.enabled_catalog_paths);
BLO_write_string(writer, settings.active_catalog_path);
}
void settings_blend_read_data(BlendDataReader *reader, AssetShelfSettings &settings)
{
BLO_read_struct_list(reader, LinkData, &settings.enabled_catalog_paths);
LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) {
BLO_read_string(reader, reinterpret_cast<char **>(&catalog_path_item->data));
}
BKE_asset_catalog_path_list_blend_read_data(reader, settings.enabled_catalog_paths);
BLO_read_string(reader, &settings.active_catalog_path);
}
void settings_clear_enabled_catalogs(AssetShelfSettings &settings)
{
LISTBASE_FOREACH_MUTABLE (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) {
MEM_freeN(catalog_path_item->data);
BLI_freelinkN(&settings.enabled_catalog_paths, catalog_path_item);
}
BLI_assert(BLI_listbase_is_empty(&settings.enabled_catalog_paths));
}
void settings_set_active_catalog(AssetShelfSettings &settings,
const asset_system::AssetCatalogPath &path)
{
@ -116,30 +101,76 @@ bool settings_is_all_catalog_active(const AssetShelfSettings &settings)
return !settings.active_catalog_path || !settings.active_catalog_path[0];
}
bool settings_is_catalog_path_enabled(const AssetShelfSettings &settings,
const asset_system::AssetCatalogPath &path)
static bool use_enabled_catalogs_from_prefs(const AssetShelf &shelf)
{
LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) {
if (StringRef((const char *)catalog_path_item->data) == path.str()) {
return true;
}
}
return false;
return shelf.type && (shelf.type->flag & ASSET_SHELF_TYPE_FLAG_STORE_CATALOGS_IN_PREFS);
}
void settings_set_catalog_path_enabled(AssetShelfSettings &settings,
static const ListBase *get_enabled_catalog_path_list(const AssetShelf &shelf)
{
if (use_enabled_catalogs_from_prefs(shelf)) {
bUserAssetShelfSettings *pref_settings = BKE_preferences_asset_shelf_settings_get(
&U, shelf.idname);
return pref_settings ? &pref_settings->enabled_catalog_paths : nullptr;
}
return &shelf.settings.enabled_catalog_paths;
}
static ListBase *get_enabled_catalog_path_list(AssetShelf &shelf)
{
return const_cast<ListBase *>(
get_enabled_catalog_path_list(const_cast<const AssetShelf &>(shelf)));
}
void settings_clear_enabled_catalogs(AssetShelf &shelf)
{
ListBase *enabled_catalog_paths = get_enabled_catalog_path_list(shelf);
if (enabled_catalog_paths) {
BKE_asset_catalog_path_list_free(*enabled_catalog_paths);
BLI_assert(BLI_listbase_is_empty(enabled_catalog_paths));
}
}
bool settings_is_catalog_path_enabled(const AssetShelf &shelf,
const asset_system::AssetCatalogPath &path)
{
const ListBase *enabled_catalog_paths = get_enabled_catalog_path_list(shelf);
if (!enabled_catalog_paths) {
return false;
}
return BKE_asset_catalog_path_list_has_path(*enabled_catalog_paths, path.c_str());
}
void settings_set_catalog_path_enabled(AssetShelf &shelf,
const asset_system::AssetCatalogPath &path)
{
char *path_copy = BLI_strdupn(path.c_str(), path.length());
BLI_addtail(&settings.enabled_catalog_paths, BLI_genericNodeN(path_copy));
if (use_enabled_catalogs_from_prefs(shelf)) {
if (BKE_preferences_asset_shelf_settings_ensure_catalog_path_enabled(
&U, shelf.idname, path.c_str()))
{
U.runtime.is_dirty = true;
}
}
else {
if (!BKE_asset_catalog_path_list_has_path(shelf.settings.enabled_catalog_paths, path.c_str()))
{
BKE_asset_catalog_path_list_add_path(shelf.settings.enabled_catalog_paths, path.c_str());
}
}
}
void settings_foreach_enabled_catalog_path(
const AssetShelfSettings &settings,
const AssetShelf &shelf,
FunctionRef<void(const asset_system::AssetCatalogPath &catalog_path)> fn)
{
LISTBASE_FOREACH (LinkData *, catalog_path_item, &settings.enabled_catalog_paths) {
fn(asset_system::AssetCatalogPath((char *)catalog_path_item->data));
const ListBase *enabled_catalog_paths = get_enabled_catalog_path_list(shelf);
if (!enabled_catalog_paths) {
return;
}
LISTBASE_FOREACH (const AssetCatalogPathLink *, path_link, enabled_catalog_paths) {
fn(asset_system::AssetCatalogPath(path_link->path));
}
}

View File

@ -20,7 +20,7 @@ bool id_type_is_non_experimental(const ID *id)
{
/* Remember to update #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING and
* #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS() with this! */
return ELEM(GS(id->name), ID_MA, ID_GR, ID_OB, ID_AC, ID_WO, ID_NT);
return ELEM(GS(id->name), ID_BR, ID_MA, ID_GR, ID_OB, ID_AC, ID_WO, ID_NT);
}
bool id_type_is_supported(const ID *id)

View File

@ -2200,7 +2200,7 @@ static int gpencil_brush_reset_all_exec(bContext *C, wmOperator * /*op*/)
}
}
BKE_paint_toolslots_brush_validate(bmain, paint);
BKE_paint_brush_validate(bmain, paint);
/* Set Again the first brush of the mode. */
Brush *deft_brush = gpencil_brush_get_first_by_mode(bmain, paint, mode, tool);

View File

@ -420,8 +420,8 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_PAINT_GPENCIL_LEGACY) {
/* Be sure we have brushes and Paint settings.
* Need Draw and Vertex (used for Tint). */
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(ts, (Paint **)&ts->gp_vertexpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_vertexpaint);
BKE_brush_gpencil_paint_presets(bmain, ts, false);
@ -434,7 +434,7 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) {
BKE_brush_gpencil_paint_presets(bmain, ts, true);
}
BKE_paint_toolslots_brush_validate(bmain, &ts->gp_paint->paint);
BKE_paint_brush_validate(bmain, &ts->gp_paint->paint);
}
if (ob->type == OB_GPENCIL_LEGACY) {
@ -572,12 +572,12 @@ static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_SCULPT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
BKE_paint_ensure(ts, (Paint **)&ts->gp_sculptpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_sculptpaint);
const bool reset_mode = (BKE_paint_brush(&ts->gp_sculptpaint->paint) == nullptr);
BKE_brush_gpencil_sculpt_presets(bmain, ts, reset_mode);
BKE_paint_toolslots_brush_validate(bmain, &ts->gp_sculptpaint->paint);
BKE_paint_brush_validate(bmain, &ts->gp_sculptpaint->paint);
}
/* setup other modes */
@ -711,7 +711,7 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_WEIGHT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
Paint *weight_paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure(ts, &weight_paint);
BKE_paint_ensure(bmain, ts, &weight_paint);
if (ob->type == OB_GREASE_PENCIL) {
ED_paint_cursor_start(weight_paint, grease_pencil_poll_weight_cursor);
@ -720,7 +720,7 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
const bool reset_mode = (BKE_paint_brush(weight_paint) == nullptr);
BKE_brush_gpencil_weight_presets(bmain, ts, reset_mode);
BKE_paint_toolslots_brush_validate(bmain, weight_paint);
BKE_paint_brush_validate(bmain, weight_paint);
}
/* setup other modes */
@ -829,13 +829,13 @@ static int gpencil_vertexmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_VERTEX_GPENCIL_LEGACY) {
/* Be sure we have brushes.
* Need Draw as well (used for Palettes). */
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(ts, (Paint **)&ts->gp_vertexpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_vertexpaint);
const bool reset_mode = (BKE_paint_brush(&ts->gp_vertexpaint->paint) == nullptr);
BKE_brush_gpencil_vertex_presets(bmain, ts, reset_mode);
BKE_paint_toolslots_brush_validate(bmain, &ts->gp_vertexpaint->paint);
BKE_paint_brush_validate(bmain, &ts->gp_vertexpaint->paint);
/* Ensure Palette by default. */
BKE_gpencil_palette_ensure(bmain, CTX_data_scene(C));

View File

@ -1216,12 +1216,8 @@ static void gpencil_primitive_init(bContext *C, wmOperator *op)
BKE_brush_gpencil_paint_presets(bmain, ts, true);
}
/* Set Draw brush. */
brush = BKE_paint_toolslots_brush_get(paint, 0);
BKE_brush_tool_set(brush, paint, 0);
BKE_paint_brush_set(paint, brush);
tgpi->brush = brush;
/* Set brush. */
tgpi->brush = BKE_paint_brush(paint);
/* control points */
tgpi->gpd->runtime.cp_points = static_cast<bGPDcontrolpoint *>(

View File

@ -1428,7 +1428,7 @@ void ED_gpencil_add_defaults(bContext *C, Object *ob)
Main *bmain = CTX_data_main(C);
ToolSettings *ts = CTX_data_tool_settings(C);
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
Paint *paint = &ts->gp_paint->paint;
Brush *brush = BKE_paint_brush(paint);
/* if not exist, create a new one */

View File

@ -44,6 +44,9 @@ void operator_asset_reference_props_set(const asset_system::AssetRepresentation
PointerRNA &ptr);
void operator_asset_reference_props_register(StructRNA &srna);
const asset_system::AssetRepresentation *find_asset_from_weak_ref(
const bContext &C, const AssetWeakReference &weak_ref, ReportList *reports);
/**
* Load all asset libraries to find an asset from the #operator_asset_reference_props_register
* properties. The loading happens in the background, so there may be no result immediately. In

View File

@ -19,6 +19,7 @@
#include "UI_resources.hh"
struct bContext;
struct PointerRNA;
struct uiBlock;
struct uiButViewItem;
struct uiLayout;
@ -204,6 +205,13 @@ class PreviewGridItem : public AbstractGridViewItem {
void build_grid_tile(uiLayout &layout) const override;
/**
* \note: Takes ownership of the operator properies defined in \a op_props.
*/
void build_grid_tile_button(uiLayout &layout,
const wmOperatorType *ot = nullptr,
const PointerRNA *op_props = nullptr) const;
/**
* Set a custom callback to execute when activating this view item. This way users don't have to
* sub-class #PreviewGridItem, just to implement custom activation behavior (a common thing to

View File

@ -707,6 +707,18 @@ uiLayout *UI_popup_menu_layout(uiPopupMenu *pup);
void UI_popup_menu_reports(bContext *C, ReportList *reports) ATTR_NONNULL();
int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) ATTR_NONNULL(1, 2);
/**
* If \a block is displayed in a popup menu, tag it for closing.
* \param is_cancel: If set to true, the popup will be closed as being cancelled (e.g. when
* pressing escape) as opposed to being handled successfully.
*/
void UI_popup_menu_close(const uiBlock *block, bool is_cancel = false);
/**
* Version of #UI_popup_menu_close() that can be called on a button contained in a popup menu
* block. Convenience since the block may not be available.
*/
void UI_popup_menu_close_from_but(const uiBut *but, bool is_cancel = false);
/**
* Allow setting menu return value from externals.
* E.g. WM might need to do this for exiting files correctly.
@ -1488,6 +1500,10 @@ uiBut *uiDefIconMenuBut(uiBlock *block,
short height,
const char *tip);
/**
* Note that \a fun can set the #UI_BLOCK_KEEP_OPEN flag to the block it creates, to allow
* refreshing the popup. That is, redrawing the layout, potentially affecting the popup size.
*/
uiBut *uiDefBlockBut(uiBlock *block,
uiBlockCreateFunc func,
void *arg,
@ -2703,6 +2719,9 @@ void uiTemplateAssetView(uiLayout *layout,
const char *drag_opname,
PointerRNA *r_drag_op_properties);
void uiTemplateAssetShelfPopover(
uiLayout *layout, bContext *C, const char *asset_shelf_id, const char *name, const int icon);
void uiTemplateLightLinkingCollection(uiLayout *layout,
uiLayout *context_layout,
PointerRNA *ptr,
@ -3394,4 +3413,4 @@ blender::ui::AbstractView *UI_region_view_find_at(const ARegion *region, const i
blender::ui::AbstractViewItem *UI_region_views_find_item_at(const ARegion &region,
const int xy[2]);
blender::ui::AbstractViewItem *UI_region_views_find_active_item(const ARegion *region);
uiBut *UI_region_views_find_active_item_but(const ARegion *region);
uiBut *UI_region_views_find_mouse_over_but(const wmWindow *win, const ARegion *region);

View File

@ -5,6 +5,7 @@
set(INC
.
../include
../asset
../../asset_system
../../blenkernel
../../blenloader
@ -64,6 +65,7 @@ set(SRC
regions/interface_regions.cc
interface_string_search.cc
interface_style.cc
templates/interface_template_asset_shelf_popover.cc
templates/interface_template_asset_view.cc
templates/interface_template_attribute_search.cc
templates/interface_template_bone_collection_tree.cc

View File

@ -415,6 +415,10 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um)
STRNCPY(drawstr, idname);
#endif
}
else if (but->tip_label_func) {
/* The "quick tooltip" often contains a short string that can be used as a fallback. */
drawstr = but->tip_label_func(but);
}
}
ED_screen_user_menu_item_add_operator(
&um->items,

View File

@ -2311,6 +2311,7 @@ static void ui_apply_but(
switch (but_type) {
case UI_BTYPE_BUT:
case UI_BTYPE_DECORATOR:
case UI_BTYPE_PREVIEW_TILE:
ui_apply_but_BUT(C, but, data);
break;
case UI_BTYPE_TEXT:
@ -11502,6 +11503,8 @@ static int ui_handle_menus_recursive(bContext *C,
if (!menu->retvalue) {
ui_handle_viewlist_items_hover(event, menu->region);
}
/* Handle mouse clicks on overlapping view item button. */
ui_handle_view_item_event(C, event, but, menu->region);
if (do_towards_reinit) {
ui_mouse_motion_towards_reinit(menu, event->xy);

View File

@ -977,6 +977,10 @@ uiBlock *ui_popup_block_refresh(bContext *C,
ARegion *butregion,
uiBut *but);
/**
* Note that callbacks can set the #UI_BLOCK_KEEP_OPEN flag to the block it creates, to allow
* refreshing the popup. That is, redrawing the layout, potentially affecting the popup size.
*/
uiPopupBlockHandle *ui_popup_block_create(bContext *C,
ARegion *butregion,
uiBut *but,

View File

@ -504,7 +504,7 @@ void ui_rna_collection_search_update_fn(
}
cis->index = items_list.size();
cis->iconid = ICON_NONE;
cis->iconid = visit_params.icon_id.value_or(ICON_NONE);
cis->is_id = false;
cis->name_prefix_offset = 0;
cis->has_sep_char = visit_params.info.has_value();

View File

@ -762,4 +762,14 @@ bool UI_popup_block_name_exists(const bScreen *screen, const blender::StringRef
return false;
}
void UI_popup_menu_close(const uiBlock *block, const bool is_cancel)
{
UI_popup_menu_retval_set(block, is_cancel ? UI_RETURN_CANCEL : UI_RETURN_OK, true);
}
void UI_popup_menu_close_from_but(const uiBut *but, const bool is_cancel)
{
UI_popup_menu_close(but->block, is_cancel);
}
/** \} */

View File

@ -885,6 +885,10 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C,
uiBlock *block = ui_popup_block_refresh(C, handle, butregion, but);
handle = block->handle;
if (block->flag & UI_BLOCK_KEEP_OPEN) {
handle->can_refresh = true;
}
/* keep centered on window resizing */
if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) {
type.listener = ui_block_region_popup_window_listener;

View File

@ -571,7 +571,6 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
/* There are different kinds of shortcuts:
*
* - Direct access to the tool (as if the toolbar button is pressed).
* - The key is bound to a brush type (not the exact brush name).
* - The key is assigned to the operator itself
* (bypassing the tool, executing the operator).
*
@ -579,36 +578,6 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
*/
std::string shortcut = UI_but_string_get_operator_keymap(*C, *but);
if (shortcut.empty()) {
const PaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
const char *tool_attr = BKE_paint_get_tool_prop_id_from_paintmode(paint_mode);
if (tool_attr != nullptr) {
const EnumPropertyItem *items = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
const char *tool_id_lstrip = strrchr(tool_id, '.');
const int tool_id_offset = tool_id_lstrip ? ((tool_id_lstrip - tool_id) + 1) : 0;
const int i = RNA_enum_from_name(items, tool_id + tool_id_offset);
if (i != -1) {
wmOperatorType *ot = WM_operatortype_find("paint.brush_select", true);
PointerRNA op_props;
WM_operator_properties_create_ptr(&op_props, ot);
RNA_enum_set(&op_props, tool_attr, items[i].value);
/* Check for direct access to the tool. */
if (std::optional<std::string> shortcut_brush = WM_key_event_operator_string(
C,
ot->idname,
WM_OP_INVOKE_REGION_WIN,
static_cast<IDProperty *>(op_props.data),
true))
{
shortcut = *shortcut_brush;
}
WM_operator_properties_free(&op_props);
}
}
}
if (shortcut.empty()) {
/* Check for direct access to the tool. */
if (std::optional<std::string> shortcut_toolbar = WM_key_event_operator_string(

View File

@ -0,0 +1,61 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include "BKE_context.hh"
#include "BKE_screen.hh"
#include "RNA_access.hh"
#include "UI_interface_c.hh"
#include "UI_resources.hh"
#include "interface_intern.hh"
#include "ED_asset_shelf.hh"
using namespace blender;
static uiBlock *asset_shelf_block_fn(bContext *C, ARegion *region, void *arg_shelf_type)
{
AssetShelfType *shelf_type = reinterpret_cast<AssetShelfType *>(arg_shelf_type);
return ed::asset::shelf::popup_block_create(C, region, shelf_type);
}
void uiTemplateAssetShelfPopover(uiLayout *layout,
bContext *C,
const char *asset_shelf_id,
const char *name,
const BIFIconID icon)
{
const ScrArea *area = CTX_wm_area(C);
AssetShelfType *shelf_type = ed::asset::shelf::type_find_from_idname(*area->type,
asset_shelf_id);
if (!shelf_type) {
RNA_warning("Asset shelf type not found: %s", asset_shelf_id);
return;
}
const ARegion *region = CTX_wm_region(C);
const bool use_big_size = !RGN_TYPE_IS_HEADER_ANY(region->regiontype);
const short width = [&]() -> short {
if (use_big_size) {
return UI_UNIT_X * 6;
}
return UI_UNIT_X * (name ? 7 : 1.6f);
}();
const short height = UI_UNIT_Y * (use_big_size ? 6 : 1);
uiBlock *block = uiLayoutGetBlock(layout);
uiBut *but = uiDefBlockBut(
block, asset_shelf_block_fn, shelf_type, name, 0, 0, width, height, "Select an asset");
ui_def_but_icon(but, icon, UI_HAS_ICON);
UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT);
if (ed::asset::shelf::type_poll(*C, *area->type, shelf_type) == false) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}

View File

@ -44,6 +44,8 @@
#include "BLF_api.hh"
#include "BLT_translation.hh"
#include "BKE_action.h"
#include "BKE_asset.hh"
#include "BKE_blender_version.h"
#include "BKE_blendfile.hh"
#include "BKE_colorband.hh"
@ -961,6 +963,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
TemplateID *template_ui = (TemplateID *)arg_litem;
PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
ID *id = static_cast<ID *>(idptr.data);
Main *id_main = BKE_main_from_id(CTX_data_main(C), id);
const int event = POINTER_AS_INT(arg_event);
const char *undo_push_label = nullptr;
@ -1011,12 +1014,12 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
break;
case UI_ID_LOCAL:
if (id) {
Main *bmain = CTX_data_main(C);
if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label);
template_id_liboverride_hierarchy_make(
C, id_main, template_ui, &idptr, &undo_push_label);
}
else {
if (BKE_lib_id_make_local(bmain, id, 0)) {
if (BKE_lib_id_make_local(id_main, id, 0)) {
BKE_id_newptr_and_tag_clear(id);
/* Reassign to get proper updates/notifiers. */
@ -1032,12 +1035,12 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
break;
case UI_ID_OVERRIDE:
if (id && ID_IS_OVERRIDE_LIBRARY(id)) {
Main *bmain = CTX_data_main(C);
if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label);
template_id_liboverride_hierarchy_make(
C, id_main, template_ui, &idptr, &undo_push_label);
}
else {
BKE_lib_override_library_make_local(bmain, id);
BKE_lib_override_library_make_local(id_main, id);
/* Reassign to get proper updates/notifiers. */
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr);
@ -1053,16 +1056,14 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
/* make copy */
if (do_scene_obj) {
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
blender::ed::object::object_single_user_make(bmain, scene, (Object *)id);
blender::ed::object::object_single_user_make(id_main, scene, (Object *)id);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
DEG_relations_tag_update(bmain);
DEG_relations_tag_update(id_main);
}
else {
Main *bmain = CTX_data_main(C);
id_single_user(C, id, &template_ui->ptr, template_ui->prop);
DEG_relations_tag_update(bmain);
DEG_relations_tag_update(id_main);
}
undo_push_label = "Make Single User";
}
@ -1766,10 +1767,15 @@ static void ui_template_id(uiLayout *layout,
flag |= UI_ID_OPEN;
}
Main *id_main = CTX_data_main(C);
if (ptr->owner_id) {
id_main = BKE_main_from_id(id_main, ptr->owner_id);
}
StructRNA *type = RNA_property_pointer_type(ptr, prop);
short idcode = RNA_type_to_ID_code(type);
template_ui->idcode = idcode;
template_ui->idlb = which_libbase(CTX_data_main(C), idcode);
template_ui->idlb = which_libbase(id_main, idcode);
/* create UI elements for this template
* - template_ID makes a copy of the template data and assigns it to the relevant buttons
@ -6351,13 +6357,47 @@ void uiTemplateInputStatus(uiLayout *layout, bContext *C)
}
}
static void ui_template_status_info_warnings_messages(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
std::string &warning_message,
std::string &regular_message,
std::string &tooltip_message)
{
tooltip_message = "";
char statusbar_info_flag = U.statusbar_flag;
if (bmain->has_forward_compatibility_issues) {
warning_message = ED_info_statusbar_string_ex(
bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
statusbar_info_flag &= ~STATUSBAR_SHOW_VERSION;
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
tooltip_message += fmt::format(RPT_("File saved by newer Blender\n({}), expect loss of data"),
writer_ver_str);
}
if (bmain->is_asset_repository) {
if (!tooltip_message.empty()) {
tooltip_message += "\n\n";
}
tooltip_message += RPT_(
"This file is managed by the Blender asset system\n"
"and is expected to contain a single asset data-block.\n"
"Take care to avoid data loss when editing assets.");
}
regular_message = ED_info_statusbar_string_ex(bmain, scene, view_layer, statusbar_info_flag);
}
void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (!bmain->has_forward_compatibility_issues) {
if (!BKE_main_has_issues(bmain)) {
const char *status_info_txt = ED_info_statusbar_string(bmain, scene, view_layer);
uiItemL(layout, status_info_txt, ICON_NONE);
return;
@ -6366,13 +6406,13 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
/* Blender version part is shown as warning area when there are forward compatibility issues with
* currently loaded .blend file. */
const char *status_info_txt = ED_info_statusbar_string_ex(
bmain, scene, view_layer, (U.statusbar_flag & ~STATUSBAR_SHOW_VERSION));
uiItemL(layout, status_info_txt, ICON_NONE);
std::string warning_message;
std::string regular_message;
std::string tooltip_message;
ui_template_status_info_warnings_messages(
bmain, scene, view_layer, warning_message, regular_message, tooltip_message);
status_info_txt = ED_info_statusbar_string_ex(bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
uiBut *but;
uiItemL(layout, regular_message.c_str(), ICON_NONE);
const uiStyle *style = UI_style_get();
uiLayout *ui_abs = uiLayoutAbsolute(layout, false);
@ -6380,25 +6420,26 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
eUIEmbossType previous_emboss = UI_block_emboss_get(block);
UI_fontstyle_set(&style->widgetlabel);
int width = int(
BLF_width(style->widgetlabel.uifont_id, status_info_txt, strlen(status_info_txt)));
width = max_ii(width, int(10 * UI_SCALE_FAC));
const int width = max_ii(int(BLF_width(style->widgetlabel.uifont_id,
warning_message.c_str(),
warning_message.length())),
int(10 * UI_SCALE_FAC));
UI_block_align_begin(block);
/* Background for icon. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
uiBut *but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
"");
/* UI_BTYPE_ROUNDBOX's bg color is set in but->col. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
@ -6423,14 +6464,13 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
UI_block_align_end(block);
UI_block_emboss_set(block, UI_EMBOSS_NONE);
/* The report icon itself. */
static char compat_error_msg[256];
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
SNPRINTF(compat_error_msg,
RPT_("File saved by newer Blender\n(%s), expect loss of data"),
writer_ver_str);
/* Tool tips have to be static currently.
* FIXME This is a horrible requirement from uiBut, should probably just store an std::string for
* the tooltip as well? */
static char tooltip_static_storage[256];
BLI_strncpy(tooltip_static_storage, tooltip_message.c_str(), sizeof(tooltip_static_storage));
/* The warning icon itself. */
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
@ -6442,23 +6482,25 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
nullptr,
0.0f,
0.0f,
compat_error_msg);
tooltip_static_storage);
UI_GetThemeColorType4ubv(TH_INFO_WARNING_TEXT, SPACE_INFO, but->col);
but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */
/* The report message. */
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
status_info_txt,
UI_UNIT_X,
0,
short(width + UI_UNIT_X),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
compat_error_msg);
/* The warning message, if any. */
if (!warning_message.empty()) {
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
warning_message.c_str(),
UI_UNIT_X,
0,
short(width + UI_UNIT_X),
UI_UNIT_Y,
nullptr,
0.0f,
0.0f,
tooltip_static_storage);
}
UI_block_emboss_set(block, previous_emboss);
}

View File

@ -17,6 +17,8 @@
#include "WM_types.hh"
#include "RNA_access.hh"
#include "UI_interface.hh"
#include "interface_intern.hh"
@ -202,7 +204,6 @@ GridViewItemDropTarget::GridViewItemDropTarget(AbstractGridView &view) : view_(v
* side(s) as well.
*/
class BuildOnlyVisibleButtonsHelper {
const View2D &v2d_;
const AbstractGridView &grid_view_;
const GridViewStyle &style_;
const int cols_per_row_ = 0;
@ -219,30 +220,34 @@ class BuildOnlyVisibleButtonsHelper {
void fill_layout_after_visible(uiBlock &block) const;
private:
IndexRange get_visible_range() const;
IndexRange get_visible_range(const View2D &v2d) const;
void add_spacer_button(uiBlock &block, int row_count) const;
};
BuildOnlyVisibleButtonsHelper::BuildOnlyVisibleButtonsHelper(const View2D &v2d,
const AbstractGridView &grid_view,
const int cols_per_row)
: v2d_(v2d), grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
: grid_view_(grid_view), style_(grid_view.get_style()), cols_per_row_(cols_per_row)
{
visible_items_range_ = this->get_visible_range();
visible_items_range_ = this->get_visible_range(v2d);
}
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range() const
IndexRange BuildOnlyVisibleButtonsHelper::get_visible_range(const View2D &v2d) const
{
if ((v2d.flag & V2D_IS_INIT) == 0) {
return IndexRange(0, grid_view_.get_item_count_filtered());
}
int first_idx_in_view = 0;
const float scroll_ofs_y = std::abs(v2d_.cur.ymax - v2d_.tot.ymax);
const float scroll_ofs_y = std::abs(v2d.cur.ymax - v2d.tot.ymax);
if (!IS_EQF(scroll_ofs_y, 0)) {
const int scrolled_away_rows = int(scroll_ofs_y) / style_.tile_height;
first_idx_in_view = scrolled_away_rows * cols_per_row_;
}
const int view_height = BLI_rcti_size_y(&v2d_.mask);
const int view_height = BLI_rcti_size_y(&v2d.mask);
const int count_rows_in_view = std::max(view_height / style_.tile_height, 1);
const int max_items_in_view = (count_rows_in_view + 1) * cols_per_row_;
@ -405,23 +410,42 @@ PreviewGridItem::PreviewGridItem(StringRef identifier, StringRef label, int prev
{
}
void PreviewGridItem::build_grid_tile(uiLayout &layout) const
void PreviewGridItem::build_grid_tile_button(uiLayout &layout,
const wmOperatorType *ot,
const PointerRNA *op_props) const
{
const GridViewStyle &style = this->get_view().get_style();
uiBlock *block = uiLayoutGetBlock(&layout);
uiBut *but = uiDefBut(block,
uiBut *but;
if (ot) {
but = uiDefButO_ptr(block,
UI_BTYPE_PREVIEW_TILE,
0,
const_cast<wmOperatorType *>(ot),
WM_OP_INVOKE_REGION_WIN,
hide_label_ ? "" : label,
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
"");
but->opptr = MEM_new<PointerRNA>(__func__, *op_props);
}
else {
but = uiDefBut(block,
UI_BTYPE_PREVIEW_TILE,
0,
hide_label_ ? "" : label,
0,
0,
style.tile_width,
style.tile_height,
nullptr,
0,
0,
"");
}
/* Draw icons that are not previews or images as normal icons with a fixed icon size. Otherwise
* they will be upscaled to the button size. Should probably be done by the widget code. */
const int is_preview_flag = (BKE_icon_is_preview(preview_icon_id) ||
@ -436,6 +460,11 @@ void PreviewGridItem::build_grid_tile(uiLayout &layout) const
but->emboss = UI_EMBOSS_NONE;
}
void PreviewGridItem::build_grid_tile(uiLayout &layout) const
{
this->build_grid_tile_button(layout);
}
void PreviewGridItem::set_on_activate_fn(ActivateFn fn)
{
activate_fn_ = fn;

View File

@ -22,6 +22,7 @@
#include <variant>
#include "DNA_screen_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_screen.hh"
@ -201,9 +202,9 @@ ui::AbstractViewItem *UI_region_views_find_active_item(const ARegion *region)
return item_but->view_item;
}
uiBut *UI_region_views_find_active_item_but(const ARegion *region)
uiBut *UI_region_views_find_mouse_over_but(const wmWindow *win, const ARegion *region)
{
return ui_view_item_find_active(region);
return ui_view_item_find_mouse_over(region, win->eventstate->xy);
}
namespace blender::ui {

View File

@ -35,6 +35,7 @@
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_appdir.hh"
#include "BKE_asset.hh"
#include "BKE_blender_copybuffer.hh"
#include "BKE_brush.hh"
#include "BKE_context.hh"
@ -821,22 +822,26 @@ void MATERIAL_OT_new(wmOperatorType *ot)
static int new_texture_exec(bContext *C, wmOperator * /*op*/)
{
Tex *tex = static_cast<Tex *>(CTX_data_pointer_get_type(C, "texture", &RNA_Texture).data);
Main *bmain = CTX_data_main(C);
PointerRNA ptr;
PropertyRNA *prop;
UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
Main *id_main = CTX_data_main(C);
if (ptr.owner_id) {
id_main = BKE_main_from_id(id_main, ptr.owner_id);
}
/* add or copy texture */
Tex *tex = static_cast<Tex *>(CTX_data_pointer_get_type(C, "texture", &RNA_Texture).data);
if (tex) {
tex = (Tex *)BKE_id_copy(bmain, &tex->id);
tex = (Tex *)BKE_id_copy(id_main, &tex->id);
}
else {
tex = BKE_texture_add(bmain, DATA_("Texture"));
tex = BKE_texture_add(id_main, DATA_("Texture"));
}
/* hook into UI */
UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
if (prop) {
/* when creating new ID blocks, use is already 1, but RNA
* pointer use also increases user, so this compensates it */

View File

@ -2021,6 +2021,9 @@ void ED_area_update_region_sizes(wmWindowManager *wm, wmWindow *win, ScrArea *ar
area_azone_init(win, screen, area);
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->flag & RGN_FLAG_POLL_FAILED) {
continue;
}
region_evaulate_visibility(region);
/* region size may have changed, init does necessary adjustments */

View File

@ -755,6 +755,9 @@ static void screen_regions_poll(bContext *C, const wmWindow *win, bScreen *scree
if (region_poll(C, screen, area, region) == false) {
region->flag |= RGN_FLAG_POLL_FAILED;
}
else if (region->type && region->type->on_poll_success) {
region->type->on_poll_success(C, region);
}
if (old_region_flag != region->flag) {
any_changed = true;

View File

@ -4,8 +4,12 @@
set(INC
../include
../asset
../uvedit
../../asset_system
../../blenkernel
../../blenlib
../../blenloader
../../blentranslation
../../bmesh
../../draw

View File

@ -282,7 +282,8 @@ static void curves_sculptmode_enter(bContext *C)
wmMsgBus *mbus = CTX_wm_message_bus(C);
Object *ob = CTX_data_active_object(C);
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt);
BKE_paint_ensure(
CTX_data_main(C), scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt);
CurvesSculpt *curves_sculpt = scene->toolsettings->curves_sculpt;
ob->mode = OB_MODE_SCULPT_CURVES;

View File

@ -901,7 +901,7 @@ void ED_object_texture_paint_mode_enter_ex(Main *bmain,
BKE_paint_init(bmain, scene, PaintMode::Texture3D, PAINT_CURSOR_TEXTURE_PAINT);
BKE_paint_toolslots_brush_validate(bmain, &imapaint->paint);
BKE_paint_brush_validate(bmain, &imapaint->paint);
if (U.glreslimit != 0) {
BKE_image_free_all_gputextures(bmain);

View File

@ -555,7 +555,8 @@ void get_brush_alpha_data(const Scene *scene,
void init_stroke(Depsgraph *depsgraph, Object *ob);
void init_session_data(const ToolSettings *ts, Object *ob);
void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode);
void init_session(
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode);
Vector<PBVHNode *> pbvh_gather_generic(Object *ob, VPaint *wp, Brush *brush);

File diff suppressed because it is too large Load Diff

View File

@ -214,10 +214,11 @@ void init_stroke(Depsgraph *depsgraph, Object *ob)
}
/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.cc) */
void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode)
void init_session(
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode)
{
/* Create persistent sculpt mode data */
BKE_sculpt_toolsettings_data_ensure(scene);
BKE_sculpt_toolsettings_data_ensure(bmain, scene);
BLI_assert(ob->sculpt == nullptr);
ob->sculpt = MEM_new<SculptSession>(__func__);
@ -321,7 +322,7 @@ void mode_enter_generic(
const PaintMode paint_mode = PaintMode::Vertex;
ED_mesh_color_ensure(mesh, nullptr);
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->vpaint);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->vpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
ED_paint_cursor_start(paint, vertex_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_VERTEX_PAINT);
@ -329,7 +330,7 @@ void mode_enter_generic(
else if (mode_flag == OB_MODE_WEIGHT_PAINT) {
const PaintMode paint_mode = PaintMode::Weight;
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->wpaint);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->wpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
ED_paint_cursor_start(paint, weight_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_WEIGHT_PAINT);
@ -351,7 +352,7 @@ void mode_enter_generic(
BKE_sculptsession_free(ob);
}
vwpaint::init_session(depsgraph, scene, ob, mode_flag);
vwpaint::init_session(bmain, depsgraph, scene, ob, mode_flag);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
@ -576,24 +577,28 @@ void last_stroke_update(Scene *scene, const float location[3])
/* -------------------------------------------------------------------- */
void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Brush *cur_brush = BKE_paint_brush(paint);
/* Switch to the blur (smooth) brush if possible. */
/* Note: used for both vertexpaint and weightpaint, VPAINT_TOOL_BLUR & WPAINT_TOOL_BLUR are the
* same, see comments for eBrushVertexPaintTool & eBrushWeightPaintTool. */
Brush *smooth_brush = BKE_paint_toolslots_brush_get(paint, WPAINT_TOOL_BLUR);
BKE_paint_brush_set_essentials(bmain,
paint,
(paint->runtime.ob_mode = OB_MODE_WEIGHT_PAINT) ? "Blur Weight" :
"Blur Vertex");
Brush *smooth_brush = BKE_paint_brush(paint);
if (!smooth_brush) {
BKE_paint_brush_set(paint, cur_brush);
CLOG_WARN(&LOG, "Switching to the blur (smooth) brush not possible, corresponding brush not");
cache->saved_active_brush_name[0] = '\0';
return;
}
Brush *cur_brush = paint->brush;
int cur_brush_size = BKE_brush_size_get(scene, cur_brush);
STRNCPY(cache->saved_active_brush_name, cur_brush->id.name + 2);
BKE_paint_brush_set(paint, smooth_brush);
cache->saved_smooth_size = BKE_brush_size_get(scene, smooth_brush);
BKE_brush_size_set(scene, smooth_brush, cur_brush_size);
BKE_curvemapping_init(smooth_brush->curve);
@ -839,7 +844,7 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op)
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_vpaintmode_enter_ex(bmain, depsgraph, scene, ob);
BKE_paint_toolslots_brush_validate(bmain, &ts->vpaint->paint);
BKE_paint_brush_validate(bmain, &ts->vpaint->paint);
}
BKE_mesh_batch_cache_dirty_tag((Mesh *)ob->data, BKE_MESH_BATCH_DIRTY_ALL);

View File

@ -1646,7 +1646,7 @@ static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op)
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_wpaintmode_enter_ex(bmain, depsgraph, scene, ob);
BKE_paint_toolslots_brush_validate(bmain, &ts->wpaint->paint);
BKE_paint_brush_validate(bmain, &ts->wpaint->paint);
}
/* Prepare armature posemode. */

View File

@ -4200,8 +4200,9 @@ static void sculpt_init_mirror_clipping(Object *ob, SculptSession *ss)
static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Brush *cur_brush = paint->brush;
Brush *cur_brush = BKE_paint_brush(paint);
if (cur_brush->sculpt_tool == SCULPT_TOOL_MASK) {
cache->saved_mask_brush_tool = cur_brush->mask_tool;
@ -4220,8 +4221,11 @@ static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache
}
/* Switch to the smooth brush if possible. */
Brush *smooth_brush = BKE_paint_toolslots_brush_get(paint, SCULPT_TOOL_SMOOTH);
BKE_paint_brush_set_essentials(bmain, paint, "Smooth");
Brush *smooth_brush = BKE_paint_brush(paint);
if (!smooth_brush) {
BKE_paint_brush_set(paint, cur_brush);
CLOG_WARN(&LOG, "Switching to the smooth brush not possible, corresponding brush not");
cache->saved_active_brush_name[0] = '\0';
return;
@ -4231,7 +4235,6 @@ static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache
STRNCPY(cache->saved_active_brush_name, cur_brush->id.name + 2);
BKE_paint_brush_set(paint, smooth_brush);
cache->saved_smooth_size = BKE_brush_size_get(scene, smooth_brush);
BKE_brush_size_set(scene, smooth_brush, cur_brush_size);
BKE_curvemapping_init(smooth_brush->curve);

View File

@ -280,7 +280,7 @@ namespace blender::ed::sculpt_paint {
static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
/* Create persistent sculpt mode data. */
BKE_sculpt_toolsettings_data_ensure(scene);
BKE_sculpt_toolsettings_data_ensure(bmain, scene);
/* Create sculpt mode session data. */
if (ob->sculpt != nullptr) {
@ -527,7 +527,7 @@ static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op)
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports);
BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint);
BKE_paint_brush_validate(bmain, &ts->sculpt->paint);
if (ob->mode & mode_flag) {
Mesh *mesh = static_cast<Mesh *>(ob->data);

View File

@ -567,6 +567,53 @@ static int compare_extension(void *user_data, const void *a1, const void *a2)
return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data);
}
static int compare_asset_catalog(void *user_data, const void *a1, const void *a2)
{
const FileListInternEntry *entry1 = static_cast<const FileListInternEntry *>(a1);
const FileListInternEntry *entry2 = static_cast<const FileListInternEntry *>(a2);
const FileSortData *sort_data = static_cast<const FileSortData *>(user_data);
if (entry1->asset && !entry2->asset) {
return 1;
}
if (!entry1->asset && entry2->asset) {
return -1;
}
if (!entry1->asset && !entry2->asset) {
if (int ret = compare_direntry_generic(entry1, entry2); ret) {
return ret;
}
return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data);
}
const asset_system::AssetLibrary &asset_library1 = entry1->asset->owner_asset_library();
const asset_system::AssetLibrary &asset_library2 = entry2->asset->owner_asset_library();
const asset_system::AssetCatalog *catalog1 = asset_library1.catalog_service().find_catalog(
entry1->asset->get_metadata().catalog_id);
const asset_system::AssetCatalog *catalog2 = asset_library2.catalog_service().find_catalog(
entry2->asset->get_metadata().catalog_id);
/* Always keep assets without catalog last. */
if (catalog1 && !catalog2) {
return 1;
}
if (!catalog1 && catalog2) {
return -1;
}
if (catalog1 && catalog2) {
const int order = BLI_strcasecmp_natural(catalog1->path.name().c_str(),
catalog2->path.name().c_str());
if (order) {
return compare_apply_inverted(order, sort_data);
}
}
return compare_apply_inverted(compare_tiebreaker(entry1, entry2), sort_data);
}
void filelist_sort(FileList *filelist)
{
if (filelist->flags & FL_NEED_SORTING) {
@ -585,6 +632,9 @@ void filelist_sort(FileList *filelist)
case FILE_SORT_EXTENSION:
sort_cb = compare_extension;
break;
case FILE_SORT_ASSET_CATALOG:
sort_cb = compare_asset_catalog;
break;
case FILE_SORT_DEFAULT:
default:
BLI_assert(0);

View File

@ -34,6 +34,7 @@
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "BKE_asset.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_global.hh"
@ -1323,7 +1324,6 @@ static Image *image_open_single(Main *bmain,
static int image_open_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
ScrArea *area = CTX_wm_area(C);
Scene *scene = CTX_data_scene(C);
ImageUser *iuser = nullptr;
@ -1338,9 +1338,16 @@ static int image_open_exec(bContext *C, wmOperator *op)
image_open_init(C, op);
}
ListBase ranges = ED_image_filesel_detect_sequences(bmain, op, use_udim);
ImageOpenData *iod = static_cast<ImageOpenData *>(op->customdata);
Main *id_main = CTX_data_main(C);
if (iod->pprop.ptr.owner_id) {
id_main = BKE_main_from_id(id_main, iod->pprop.ptr.owner_id);
}
ListBase ranges = ED_image_filesel_detect_sequences(id_main, op, use_udim);
LISTBASE_FOREACH (ImageFrameRange *, range, &ranges) {
Image *ima_range = image_open_single(bmain, op, range, use_multiview);
Image *ima_range = image_open_single(id_main, op, range, use_multiview);
/* take the first image */
if ((ima == nullptr) && ima_range) {
@ -1357,9 +1364,6 @@ static int image_open_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
/* hook into UI */
ImageOpenData *iod = static_cast<ImageOpenData *>(op->customdata);
if (iod->pprop.prop) {
/* when creating new ID blocks, use is already 1, but RNA
* pointer use also increases user, so this compensates it */
@ -1375,7 +1379,7 @@ static int image_open_exec(bContext *C, wmOperator *op)
}
else if (area && area->spacetype == SPACE_IMAGE) {
SpaceImage *sima = static_cast<SpaceImage *>(area->spacedata.first);
ED_space_image_set(bmain, sima, ima, false);
ED_space_image_set(id_main, sima, ima, false);
iuser = &sima->iuser;
}
else {
@ -1415,9 +1419,9 @@ static int image_open_exec(bContext *C, wmOperator *op)
}
/* XXX BKE_packedfile_unpack_image frees image buffers */
ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
ED_preview_kill_jobs(CTX_wm_manager(C), id_main);
BKE_image_signal(bmain, ima, iuser, IMA_SIGNAL_RELOAD);
BKE_image_signal(id_main, ima, iuser, IMA_SIGNAL_RELOAD);
WM_event_add_notifier(C, NC_IMAGE | NA_EDITED, ima);
MEM_freeN(op->customdata);
@ -2530,7 +2534,7 @@ static int image_new_exec(bContext *C, wmOperator *op)
{
SpaceImage *sima;
Image *ima;
Main *bmain;
Main *id_main;
PropertyRNA *prop;
char name_buffer[MAX_ID_NAME - 2];
const char *name;
@ -2538,9 +2542,14 @@ static int image_new_exec(bContext *C, wmOperator *op)
int width, height, floatbuf, gen_type, alpha;
int stereo3d;
ImageNewData *data = image_new_init(C, op);
/* retrieve state */
sima = CTX_wm_space_image(C);
bmain = CTX_data_main(C);
id_main = CTX_data_main(C);
if (data->pprop.ptr.owner_id) {
id_main = BKE_main_from_id(id_main, data->pprop.ptr.owner_id);
}
prop = RNA_struct_find_property(op->ptr, "name");
RNA_property_string_get(op->ptr, prop, name_buffer);
@ -2564,7 +2573,7 @@ static int image_new_exec(bContext *C, wmOperator *op)
color[3] = 1.0f;
}
ima = BKE_image_add_generated(bmain,
ima = BKE_image_add_generated(id_main,
width,
height,
name,
@ -2582,8 +2591,6 @@ static int image_new_exec(bContext *C, wmOperator *op)
}
/* hook into UI */
ImageNewData *data = image_new_init(C, op);
if (data->pprop.prop) {
/* when creating new ID blocks, use is already 1, but RNA
* pointer use also increases user, so this compensates it */
@ -2594,7 +2601,7 @@ static int image_new_exec(bContext *C, wmOperator *op)
RNA_property_update(C, &data->pprop.ptr, data->pprop.prop);
}
else if (sima) {
ED_space_image_set(bmain, sima, ima, false);
ED_space_image_set(id_main, sima, ima, false);
}
else {
/* #BKE_image_add_generated creates one user by default, remove it if image is not linked to
@ -2602,7 +2609,7 @@ static int image_new_exec(bContext *C, wmOperator *op)
id_us_min(&ima->id);
}
BKE_image_signal(bmain, ima, (sima) ? &sima->iuser : nullptr, IMA_SIGNAL_USER_NEW_IMAGE);
BKE_image_signal(id_main, ima, (sima) ? &sima->iuser : nullptr, IMA_SIGNAL_USER_NEW_IMAGE);
WM_event_add_notifier(C, NC_IMAGE | NA_ADDED, ima);

View File

@ -1200,6 +1200,7 @@ void ED_spacetype_image()
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_ASSET_SHELF | ED_KEYMAP_FRAMES;
art->duplicate = asset::shelf::region_duplicate;
art->free = asset::shelf::region_free;
art->on_poll_success = asset::shelf::region_on_poll_success;
art->listener = asset::shelf::region_listen;
art->poll = asset::shelf::regions_poll;
art->snap_size = asset::shelf::region_snap;

View File

@ -2209,6 +2209,7 @@ void ED_spacetype_view3d()
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_ASSET_SHELF | ED_KEYMAP_FRAMES;
art->duplicate = asset::shelf::region_duplicate;
art->free = asset::shelf::region_free;
art->on_poll_success = asset::shelf::region_on_poll_success;
art->listener = asset::shelf::region_listen;
art->poll = asset::shelf::regions_poll;
art->snap_size = asset::shelf::region_snap;

View File

@ -1000,6 +1000,15 @@ enum {
* RESET_NEVER
*/
LIB_TAG_NOT_ALLOCATED = 1 << 26,
/**
* ID is part of an asset #Main separate from the regular main database.
*
* RESET_NEVER
*
* Datablocks like this can not be linked to and from datablocks in regular main.
* They should stay isolated from each other.
*/
LIB_TAG_ASSET_MAIN = 1 << 27,
/* ------------------------------------------------------------------------------------------- */

View File

@ -174,6 +174,12 @@ typedef struct AssetWeakReference {
AssetWeakReference &operator=(AssetWeakReference &&);
~AssetWeakReference();
friend bool operator==(const AssetWeakReference &a, const AssetWeakReference &b);
friend bool operator!=(const AssetWeakReference &a, const AssetWeakReference &b)
{
return !(a == b);
}
/**
* See AssetRepresentation::make_weak_reference().
*/
@ -199,3 +205,8 @@ typedef struct AssetWeakReference {
typedef struct AssetHandle {
const struct FileDirEntry *file_data;
} AssetHandle;
struct AssetCatalogPathLink {
struct AssetCatalogPathLink *next, *prev;
char *path;
};

View File

@ -899,27 +899,26 @@ typedef struct TimeMarker {
typedef struct Paint_Runtime {
/** Avoid having to compare with scene pointer everywhere. */
unsigned int tool_offset;
unsigned int initialized;
unsigned short ob_mode;
char _pad[2];
} Paint_Runtime;
/** We might want to store other things here. */
typedef struct PaintToolSlot {
struct Brush *brush;
} PaintToolSlot;
/** Paint Tool Base. */
typedef struct Paint {
/**
* The active brush. Possibly null. Possibly stored in a separate #Main data-base and not user-
* counted.
* //TODO: Or always stored in a separate #Main?
*/
struct Brush *brush;
/**
* Each tool has its own active brush,
* The currently active tool is defined by the current 'brush'.
* A weak asset reference to the #brush, if not NULL.
* Used to attempt restoring the active brush from the AssetLibrary system, typically on
* file load.
*/
struct PaintToolSlot *tool_slots;
int tool_slots_len;
char _pad1[4];
struct AssetWeakReference *brush_asset_reference;
struct Palette *palette;
/** Cavity curve. */

View File

@ -799,7 +799,7 @@ typedef struct AssetShelfSettings {
AssetLibraryReference asset_library_reference;
ListBase enabled_catalog_paths; /* #LinkData */
ListBase enabled_catalog_paths; /* #AssetCatalogPathLink */
/** If not set (null or empty string), all assets will be displayed ("All" catalog behavior). */
const char *active_catalog_path;
@ -833,6 +833,7 @@ typedef struct AssetShelf {
AssetShelfSettings settings;
/** Only for the permanent asset shelf regions, not asset shelves in temporary popups. */
short preferred_row_count;
char _pad[6];
} AssetShelf;
@ -855,6 +856,8 @@ typedef struct RegionAssetShelf {
AssetShelf *active_shelf; /* Non-owning. */
#ifdef __cplusplus
static RegionAssetShelf *get_from_asset_shelf_region(const ARegion &region);
/** Creates the asset shelf region data if necessary, and returns it. */
static RegionAssetShelf *ensure_from_asset_shelf_region(ARegion &region);
#endif
} RegionAssetShelf;

View File

@ -985,6 +985,8 @@ enum eFileSortType {
FILE_SORT_EXTENSION = 2,
FILE_SORT_TIME = 3,
FILE_SORT_SIZE = 4,
/* Assets: Sort by catalog. Within each catalog, assets will be sorted by name. */
FILE_SORT_ASSET_CATALOG = 5,
};
/** #SpaceFile.tags */

View File

@ -39,4 +39,16 @@
/** \} */
/* -------------------------------------------------------------------- */
/** \name bUserExtensionRepo Struct
* \{ */
#define _DNA_DEFAULT_bUserAssetShelfSettings \
{ \
.shelf_idname = {'\0'}, \
.enabled_catalog_paths = {NULL, NULL}, \
}
/** \} */
/* clang-format on */

View File

@ -754,6 +754,20 @@ typedef struct bUserScriptDirectory {
char dir_path[768]; /* FILE_MAXDIR */
} bUserScriptDirectory;
/**
* Settings for an asset shelf, stored in the Preferences. Most settings are still stored in the
* asset shelf instance in #AssetShelfSettings. This is just for the options that should be shared
* as Preferences.
*/
typedef struct bUserAssetShelfSettings {
struct bUserAssetShelfSettings *next, *prev;
/** Identifier that matches the #AssetShelfType.idname of the shelf these settings apply to. */
char shelf_idname[64]; /* MAX_NAME */
ListBase enabled_catalog_paths; /* #AssetCatalogPathLink */
} bUserAssetShelfSettings;
/**
* Main user preferences data, typically accessed from #U.
* See: #BKE_blendfile_userdef_from_defaults & #BKE_blendfile_userdef_read.
@ -891,6 +905,7 @@ typedef struct UserDef {
struct ListBase asset_libraries;
/** #bUserExtensionRepo */
struct ListBase extension_repos;
struct ListBase asset_shelves_settings; /* #bUserAssetShelfSettings */
char keyconfigstr[64];

View File

@ -238,6 +238,7 @@ SDNA_DEFAULT_DECL_STRUCT(Tex);
/* DNA_userdef_types.h */
SDNA_DEFAULT_DECL_STRUCT(bUserAssetLibrary);
SDNA_DEFAULT_DECL_STRUCT(bUserExtensionRepo);
SDNA_DEFAULT_DECL_STRUCT(bUserAssetShelfSettings);
/* DNA_view3d_defaults.h */
SDNA_DEFAULT_DECL_STRUCT(View3D);
@ -517,6 +518,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL_EX(WalkNavigation, UserDef.walk_navigation),
SDNA_DEFAULT_DECL(bUserAssetLibrary),
SDNA_DEFAULT_DECL(bUserExtensionRepo),
SDNA_DEFAULT_DECL(bUserAssetShelfSettings),
/* DNA_view3d_defaults.h */
SDNA_DEFAULT_DECL(View3D),

View File

@ -545,6 +545,7 @@ struct StringPropertySearchVisitParams {
std::string text;
/** Additional information to display. */
std::optional<std::string> info;
std::optional<int> icon_id;
};
enum eStringPropertySearchFlag {

View File

@ -1269,14 +1269,30 @@ static char *rna_def_property_set_func(
else {
rna_print_data_get(f, dp);
PointerPropertyRNA *pprop = (PointerPropertyRNA *)dp->prop;
StructRNA *type = (pprop->type) ? rna_find_struct((const char *)pprop->type) : nullptr;
if (prop->flag & PROP_ID_SELF_CHECK) {
/* No pointers to self allowed. */
rna_print_id_get(f, dp);
fprintf(f, " if (id == value.data) {\n");
fprintf(f, " return;\n");
fprintf(f, " }\n");
}
if (type && (type->flag & STRUCT_ID)) {
/* Can only assign pointers between datablocks in the same main database. */
fprintf(f, " if (value.data) {\n");
fprintf(f, " Main *owner_main = BKE_main_from_id(G_MAIN, ptr->owner_id);\n");
fprintf(f, " Main *value_main = BKE_main_from_id(G_MAIN, (ID *)value.data);\n");
fprintf(f, " if (owner_main && owner_main != value_main) {\n");
fprintf(f, " return;\n");
fprintf(f, " }\n");
fprintf(f, " }\n");
}
if (prop->flag & PROP_ID_REFCOUNT) {
/* Perform reference counting. */
fprintf(f, "\n if (data->%s) {\n", dp->dnaname);
fprintf(f, " id_us_min((ID *)data->%s);\n", dp->dnaname);
fprintf(f, " }\n");
@ -1284,14 +1300,11 @@ static char *rna_def_property_set_func(
fprintf(f, " id_us_plus((ID *)value.data);\n");
fprintf(f, " }\n");
}
else {
PointerPropertyRNA *pprop = (PointerPropertyRNA *)dp->prop;
StructRNA *type = (pprop->type) ? rna_find_struct((const char *)pprop->type) : nullptr;
if (type && (type->flag & STRUCT_ID)) {
fprintf(f, " if (value.data) {\n");
fprintf(f, " id_lib_extern((ID *)value.data);\n");
fprintf(f, " }\n");
}
else if (type && (type->flag & STRUCT_ID)) {
/* Still mark linked data as used if not reference counting. */
fprintf(f, " if (value.data) {\n");
fprintf(f, " id_lib_extern((ID *)value.data);\n");
fprintf(f, " }\n");
}
fprintf(f, " *(void **)&data->%s = value.data;\n", dp->dnaname);
@ -4880,6 +4893,7 @@ static void rna_generate(BlenderRNA *brna, FILE *f, const char *filename, const
fprintf(f, "#include \"BLI_utildefines.h\"\n\n");
fprintf(f, "#include \"BKE_context.hh\"\n");
fprintf(f, "#include \"BKE_global.hh\"\n");
fprintf(f, "#include \"BKE_lib_id.hh\"\n");
fprintf(f, "#include \"BKE_main.hh\"\n");
fprintf(f, "#include \"BKE_report.hh\"\n");

View File

@ -16,6 +16,7 @@
#include "BLI_utildefines.h"
#include "BKE_asset.hh"
#include "BKE_icons.h"
#include "BKE_lib_id.hh"
#include "BKE_main_namemap.hh"
@ -289,10 +290,9 @@ int rna_ID_name_length(PointerRNA *ptr)
void rna_ID_name_set(PointerRNA *ptr, const char *value)
{
ID *id = (ID *)ptr->data;
BLI_assert(BKE_id_is_in_global_main(id));
BLI_assert(!ID_IS_LINKED(id));
Main *id_main = BKE_main_from_id(G_MAIN, id);
BKE_libblock_rename(G_MAIN, id, value);
BKE_libblock_rename(id_main, id, value);
if (GS(id->name) == ID_OB) {
Object *ob = (Object *)id;
@ -300,6 +300,8 @@ void rna_ID_name_set(PointerRNA *ptr, const char *value)
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
}
/* TODO: this does not update immediately in the asset shelf. */
}
static int rna_ID_name_editable(const PointerRNA *ptr, const char **r_info)
@ -325,7 +327,7 @@ static int rna_ID_name_editable(const PointerRNA *ptr, const char **r_info)
return 0;
}
}
else if (!BKE_id_is_in_global_main(id)) {
else if (BKE_main_from_id(G_MAIN, id, true) == nullptr) {
if (r_info) {
*r_info = N_("Datablocks not in global Main data-base cannot be renamed");
}
@ -1166,16 +1168,20 @@ bool rna_IDMaterials_assign_int(PointerRNA *ptr, int key, const PointerRNA *assi
{
ID *id = ptr->owner_id;
short *totcol = BKE_id_material_len_p(id);
Material *mat_id = (Material *)assign_ptr->owner_id;
if (totcol && (key >= 0 && key < *totcol)) {
BLI_assert(BKE_id_is_in_global_main(id));
BLI_assert(BKE_id_is_in_global_main(&mat_id->id));
BKE_id_material_assign(G_MAIN, id, mat_id, key + 1);
return true;
}
else {
Material *mat = (Material *)assign_ptr->owner_id;
if (!(totcol && (key >= 0 && key < *totcol))) {
return false;
}
Main *id_main = BKE_main_from_id(G_MAIN, id);
if (mat) {
if (id_main != BKE_main_from_id(G_MAIN, &mat->id)) {
return false;
}
}
BKE_id_material_assign(id_main, id, mat, key + 1);
return true;
}
static void rna_IDMaterials_append_id(ID *id, Main *bmain, Material *ma)
@ -1226,8 +1232,8 @@ static void rna_IDMaterials_clear_id(ID *id, Main *bmain)
static void rna_Library_filepath_set(PointerRNA *ptr, const char *value)
{
Library *lib = (Library *)ptr->data;
BLI_assert(BKE_id_is_in_global_main(&lib->id));
BKE_library_filepath_set(G_MAIN, lib, value);
Main *id_main = BKE_main_from_id(G_MAIN, &lib->id);
BKE_library_filepath_set(id_main, lib, value);
}
/* ***** ImagePreview ***** */
@ -2315,6 +2321,14 @@ static void rna_def_ID(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Is Indirect", "Is this ID block linked indirectly");
prop = RNA_def_property(srna, "is_asset_library_data", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "tag", LIB_TAG_ASSET_MAIN);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Asset Library Data",
"This data-block is part of an asset library blend file, not the blend "
"file opened for editing");
prop = RNA_def_property(srna, "library", PROP_POINTER, PROP_NONE);
RNA_def_property_pointer_sdna(prop, nullptr, "lib");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@ -2144,7 +2144,8 @@ static bool rna_property_editable_do(const PointerRNA *ptr,
return false;
}
if (ID_IS_OVERRIDE_LIBRARY(id)) {
const bool is_liboverride_system = BKE_lib_override_library_is_system_defined(G_MAIN, id);
Main *main = BKE_main_from_id(G_MAIN, id);
const bool is_liboverride_system = BKE_lib_override_library_is_system_defined(main, id);
if (!RNA_property_overridable_get(ptr, prop_orig)) {
if (r_info != nullptr && (*r_info)[0] == '\0') {
*r_info = N_("Can't edit this property from an override data-block");
@ -2389,7 +2390,11 @@ bool RNA_property_update_check(PropertyRNA *prop)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
{
rna_property_update(C, CTX_data_main(C), CTX_data_scene(C), ptr, prop);
Main *main = CTX_data_main(C);
if (ptr->owner_id) {
main = BKE_main_from_id(main, ptr->owner_id);
}
rna_property_update(C, main, CTX_data_scene(C), ptr, prop);
}
void RNA_property_update_main(Main *bmain, Scene *scene, PointerRNA *ptr, PropertyRNA *prop)

View File

@ -791,28 +791,36 @@ static void rna_EditBone_name_set(PointerRNA *ptr, const char *value)
{
bArmature *arm = (bArmature *)ptr->owner_id;
EditBone *ebone = (EditBone *)ptr->data;
char oldname[sizeof(ebone->name)], newname[sizeof(ebone->name)];
Main *main = BKE_main_from_id(G_MAIN, &arm->id);
if (main == nullptr) {
return;
}
char oldname[sizeof(ebone->name)], newname[sizeof(ebone->name)];
/* need to be on the stack */
STRNCPY_UTF8(newname, value);
STRNCPY(oldname, ebone->name);
BLI_assert(BKE_id_is_in_global_main(&arm->id));
ED_armature_bone_rename(G_MAIN, arm, oldname, newname);
ED_armature_bone_rename(main, arm, oldname, newname);
}
static void rna_Bone_name_set(PointerRNA *ptr, const char *value)
{
bArmature *arm = (bArmature *)ptr->owner_id;
Bone *bone = (Bone *)ptr->data;
char oldname[sizeof(bone->name)], newname[sizeof(bone->name)];
Main *main = BKE_main_from_id(G_MAIN, &arm->id);
if (main == nullptr) {
return;
}
char oldname[sizeof(bone->name)], newname[sizeof(bone->name)];
/* need to be on the stack */
STRNCPY_UTF8(newname, value);
STRNCPY(oldname, bone->name);
BLI_assert(BKE_id_is_in_global_main(&arm->id));
ED_armature_bone_rename(G_MAIN, arm, oldname, newname);
ED_armature_bone_rename(main, arm, oldname, newname);
}
static void rna_EditBone_connected_check(EditBone *ebone)

View File

@ -86,18 +86,23 @@ static void rna_Image_source_set(PointerRNA *ptr, int value)
{
Image *ima = (Image *)ptr->owner_id;
if (value != ima->source) {
ima->source = value;
BLI_assert(BKE_id_is_in_global_main(&ima->id));
BKE_image_signal(G_MAIN, ima, nullptr, IMA_SIGNAL_SRC_CHANGE);
if (ima->source == IMA_SRC_TILED) {
BKE_image_signal(G_MAIN, ima, nullptr, IMA_SIGNAL_RELOAD);
}
DEG_id_tag_update(&ima->id, 0);
DEG_id_tag_update(&ima->id, ID_RECALC_EDITORS | ID_RECALC_SOURCE);
DEG_relations_tag_update(G_MAIN);
if (value == ima->source) {
return;
}
ima->source = value;
Main *main = BKE_main_from_id(G_MAIN, &ima->id);
if (main == nullptr) {
return;
}
BKE_image_signal(main, ima, nullptr, IMA_SIGNAL_SRC_CHANGE);
if (ima->source == IMA_SRC_TILED) {
BKE_image_signal(main, ima, nullptr, IMA_SIGNAL_RELOAD);
}
DEG_id_tag_update(&ima->id, 0);
DEG_id_tag_update(&ima->id, ID_RECALC_EDITORS | ID_RECALC_SOURCE);
DEG_relations_tag_update(main);
}
static void rna_Image_reload_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)

View File

@ -127,6 +127,17 @@ static void rna_Main_ID_remove(Main *bmain,
id->name + 2);
return;
}
if (bmain != BKE_main_from_id(bmain, id)) {
BKE_reportf(reports,
RPT_ERROR,
"%s '%s' is part of a different main database and should be removed from there",
BKE_idtype_idcode_to_name(GS(id->name)),
id->name + 2);
return;
}
/* TODO: this will not clear pointers from regular main to this asset.
* Those probably should not exist, and be purely runtime lookups? */
if (do_unlink) {
BKE_id_delete(bmain, id);
RNA_POINTER_INVALIDATE(id_ptr);

Some files were not shown because too many files have changed in this diff Show More