Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py24
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py1243
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py40
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py78
-rw-r--r--source/blender/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/BKE_brush.h8
-rw-r--r--source/blender/blenkernel/BKE_brush_channel.h330
-rw-r--r--source/blender/blenkernel/CMakeLists.txt3
-rw-r--r--source/blender/blenkernel/intern/brush.cc116
-rw-r--r--source/blender/blenkernel/intern/brush_channel.cc1492
-rw-r--r--source/blender/blenkernel/intern/brush_channel_define.h235
-rw-r--r--source/blender/blenkernel/intern/scene.cc16
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_comb.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_delete.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_density.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_intern.hh4
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_ops.cc8
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_puff.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_slide.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc2
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc2
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c38
-rw-r--r--source/blender/editors/sculpt_paint/paint_intern.h1
-rw-r--r--source/blender/editors/sculpt_paint/paint_ops.c75
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c29
-rw-r--r--source/blender/editors/sculpt_paint/paint_utils.c128
-rw-r--r--source/blender/editors/sculpt_paint/paint_vertex.cc4
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c132
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h10
-rw-r--r--source/blender/makesdna/DNA_brush_channel_types.h206
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h1
-rw-r--r--source/blender/makesdna/DNA_brush_types.h3
-rw-r--r--source/blender/makesdna/DNA_scene_types.h5
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt1
-rw-r--r--source/blender/makesrna/intern/makesrna.c1
-rw-r--r--source/blender/makesrna/intern/rna_brush.c14
-rw-r--r--source/blender/makesrna/intern/rna_brush_channels.c890
-rw-r--r--source/blender/makesrna/intern/rna_internal.h14
-rw-r--r--source/blender/makesrna/intern/rna_path.cc17
-rw-r--r--source/blender/makesrna/intern/rna_scene.c94
43 files changed, 4955 insertions, 326 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index b83c4916330..0f1cf5e1880 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -4669,18 +4669,26 @@ def km_curve(params):
def radial_control_properties(paint, prop, secondary_prop, secondary_rotation=False, color=False, zoom=False):
brush_path = 'tool_settings.' + paint + '.brush'
- unified_path = 'tool_settings.unified_paint_settings'
+ unified_path = 'tool_settings.unified_properties'
+ channel_path = 'tool_settings.unified_channels'
rotation = 'mask_texture_slot.angle' if secondary_rotation else 'texture_slot.angle'
+
+ def idprop(key):
+ return '["%s"]' % key
+
+ def rnaprop(key):
+ return "." + key
+
return {
"properties": [
("data_path_primary", brush_path + '.' + prop),
- ("data_path_secondary", unified_path + '.' + prop if secondary_prop else ''),
- ("use_secondary", unified_path + '.' + secondary_prop if secondary_prop else ''),
- ("rotation_path", brush_path + '.' + rotation),
- ("color_path", brush_path + '.cursor_color_add'),
- ("fill_color_path", brush_path + '.color' if color else ''),
- ("fill_color_override_path", unified_path + '.color' if color else ''),
- ("fill_color_override_test_path", unified_path + '.use_unified_color' if color else ''),
+ ("data_path_secondary", unified_path + idprop(prop) if secondary_prop else ''),
+ ("use_secondary", channel_path + idprop(prop) + '.unified' if secondary_prop else ''),
+ ("rotation_path", brush_path + rnaprop(rotation)),
+ ("color_path", brush_path + rnaprop('cursor_color_add')),
+ ("fill_color_path", brush_path + rnaprop('color') if color else ''),
+ ("fill_color_override_path", unified_path + idprop('color') if color else ''),
+ ("fill_color_override_test_path", channel_path + idprop('color') + '.unified' if color else ''),
("zoom_path", 'space_data.zoom' if zoom else ''),
("image_id", brush_path + ''),
("secondary_tex", secondary_rotation),
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index 0e49a506e73..061795231cc 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -1,6 +1,335 @@
# SPDX-License-Identifier: GPL-2.0-or-later
-from bpy.types import Menu
+from bpy.types import Menu, Panel
+builtin_channel_categories = [
+ "Basic", "Paint", "Smooth", "Cloth", "Automasking"
+]
+
+class DynamicBrushCategoryPanel(Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_category = "Tool"
+
+ @classmethod
+ def poll(self, context):
+ ok = context.mode == "SCULPT" and context.tool_settings.sculpt and context.tool_settings.sculpt.brush
+ ok = ok and len(self.get_channels(context)) > 0
+
+ return ok
+
+ @classmethod
+ def get_channels(self, context):
+ brush = context.tool_settings.sculpt.brush
+
+ idname = self.get_category(self)
+
+ #for ch in brush.channels:
+ # print(ch.idname, ch.show_in_workspace)
+
+ channels = list(filter(lambda ch: ch.show_in_workspace and ch.category == idname, brush.channels))
+ channels.sort(key=lambda ch: ch.ui_order)
+
+ return channels
+
+ def draw(self, context):
+ layout = self.layout
+ brush = context.tool_settings.sculpt.brush
+
+ idname = self.get_category()
+ opt = self.get_options()
+
+ layout.use_property_split = True
+
+ channels = self.get_channels(context)
+ group = DynamicPaintPanelGen.getGroup(self)
+
+ for ch in channels:
+ name = ch.name
+
+ # Handle size/unprojected_radius hack.
+ if ch.idname == "size" and brush.use_locked_size == "SCENE":
+ continue
+ if ch.idname == "unprojected_radius":
+ if brush.use_locked_size != "SCENE":
+ continue
+ name = "Radius"
+
+ inserts = group.getInserts(ch.idname) if group else []
+
+ ok = ch.show_in_workspace
+ ok = ok and ch.category == idname
+
+ if not ok:
+ continue
+
+ row = layout if len(inserts) == 0 else layout.row(align=True)
+
+ UnifiedPaintPanel.channel_unified(row,
+ context,
+ brush,
+ ch.idname,
+ slider=True,
+ ui_editing=opt["ui_editing"],
+ show_reorder=opt["show_reorder"],
+ show_mappings=opt["show_mappings"],
+ text=name)
+
+ for item in inserts:
+ if item.sameLine:
+ item.cb(row)
+
+ for item in inserts:
+ if not item.sameLine:
+ item.cb(layout)
+
+class InsertAfterItem:
+ def __init__(self, cb, sameLine):
+ self.cb = cb
+ self.sameLine = sameLine
+
+class DynamicPaintPanelGen:
+ class Group:
+ def __init__(self, idname, name, prefix, parent):
+ self.idname = idname
+ self.name = name
+ self.prefix = prefix
+ self.rnaclass = None
+ self.parent = parent
+ self.options = {}
+ self.insert_cbs = {}
+
+ def getInserts(self, key):
+ return self.insert_cbs[key] if key in self.insert_cbs else []
+
+ def insertEachAfter(self, insertdict):
+ #return
+ for key in insertdict.keys():
+ item = insertdict[key]
+
+ if isinstance(item, dict):
+ callback = item["callback"]
+ sameLine = item["sameLine"] if "sameLine" in item else False
+ else:
+ callback = item
+ sameLine = False
+
+ self.insertAfter(key, callback, sameLine)
+
+ def insertAfter(self, key, cb, sameLine=True):
+ """
+ example:
+ group.insertAfter("color", lambda layout: layout.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False))
+ """
+ if key not in self.insert_cbs:
+ self.insert_cbs[key] = []
+
+ self.insert_cbs[key].append(InsertAfterItem(cb, sameLine))
+
+ groups = {}
+
+ @staticmethod
+ def getGroup(panel):
+ idname = panel.bl_idname
+
+ return DynamicPaintPanelGen.groups[idname] if idname in DynamicPaintPanelGen.groups else None
+
+ @staticmethod
+ def ensureCategory(idname, name=None, prefix="VIEW3D_PT_brush_category_", parent=None, show_reorder=False, ui_editing=False, show_mappings=None):
+ if name is None:
+ name = idname
+
+ groupid = prefix + idname.lower()
+
+ if groupid in DynamicPaintPanelGen.groups:
+ return DynamicPaintPanelGen.groups[groupid]
+
+ group = DynamicPaintPanelGen.Group(idname, name, prefix, parent)
+ DynamicPaintPanelGen.groups[groupid] = group
+
+ group.options = {
+ "ui_editing": ui_editing,
+ "show_reorder": show_reorder,
+ "show_mappings" : show_mappings
+ }
+
+ def callback():
+ DynamicPaintPanelGen.createPanel(group)
+ pass
+
+ import bpy
+ bpy.app.timers.register(callback)
+
+ return group
+
+ @staticmethod
+ def get(idname, prefix):
+ return DynamicPaintPanelGen.groups[idname]
+
+ @staticmethod
+ def createPanel(group):
+ from bpy.utils import register_class, unregister_class
+
+ from bpy.types import Panel
+ global classes
+
+ name = group.prefix + group.idname.lower()
+ name1 = name
+ name2 = ""
+
+ for c in name:
+ n = ord(c)
+
+ ok = n >= ord("a") and n <= ord("z")
+ ok = ok or (n >= ord("A") and n <= ord("Z"))
+ ok = ok or (n >= ord("0") and n <= ord("9"))
+ ok = ok or c == "_"
+
+ if not ok:
+ c = "_"
+
+ name2 += c
+ name = name2
+
+ for cls in classes[:]:
+ if cls.bl_rna.identifier == name:
+ try:
+ unregister_class(cls)
+ except:
+ print("failed to unregister", name)
+
+ classes.remove(cls)
+
+ if group.parent:
+ parent = 'bl_parent_id = "%s"' % group.parent
+ else:
+ parent = ""
+
+ opt = repr(group.options)
+
+ code = """
+
+global classes
+
+class CLASSNAME (DynamicBrushCategoryPanel):
+ bl_label = "LABEL"
+ PARENT
+
+ def get_category(self):
+ return "IDNAME"
+
+ def get_options(self):
+ return OPT
+
+register_class(CLASSNAME)
+classes.append(CLASSNAME)
+
+""".strip().replace("CLASSNAME", name).replace("PARENT", parent).replace("LABEL", group.name).replace("OPT", opt)
+ code = code.replace("IDNAME", group.idname)
+
+ exec(code)
+
+#custom UI code that is inserted
+#for different brush channel properties
+insertAfters = {
+ "color" : {
+ "sameLine" : True,
+ "callback" : lambda layout: layout.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False)
+ }
+}
+
+#pre create category panels in correct order
+for cat in builtin_channel_categories:
+ DynamicPaintPanelGen.ensureCategory(cat, cat, parent="VIEW3D_PT_tools_brush_settings_channels", prefix="VIEW3D_PT_brush_category_",
+ ui_editing=False, show_reorder=True, show_mappings=True).insertEachAfter(insertAfters)
+ #DynamicPaintPanelGen.ensureCategory(cat, cat, prefix="VIEW3D_PT_brush_category_edit_",
+ # parent="VIEW3D_PT_tools_brush_settings_channels_preview").insertEachAfter(insertAfters)
+
+
+def template_curve(layout, base, propname, full_path, use_negative_slope=None):
+ layout.template_curve_mapping(base, propname, brush=True, use_negative_slope=use_negative_slope)
+
+ path = full_path
+
+ col = layout.column(align=True)
+ row = col.row(align=True)
+
+ shapes = ['SMOOTH', 'ROUND', 'ROOT', 'SHARP', 'LINE', 'MAX']
+ icons = ['SMOOTHCURVE', 'SPHERECURVE', 'ROOTCURVE', 'SHARPCURVE', 'LINCURVE', 'NOCURVE']
+
+ for i, shape in enumerate(shapes):
+ props = row.operator("brush.curve_preset_load", icon=icons[i], text="")
+ props.invert = not use_negative_slope
+ props.shape = shape
+ props.path = path
+
+class BRUSH_PT_channel_panel(Panel):
+ bl_label = "Settings"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+
+ @classmethod
+ def poll(cls, context):
+ if not context.object:
+ return False
+
+ mode = context.object.mode
+ return mode in {"SCULPT", "VERTEX_PAINT", "WEIGHT_PAINT", "TEXTURE_PAINT"}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.use_property_split = True
+ scene = context.scene
+ ts = context.tool_settings
+
+ print("\n")
+ for k in dir(context):
+ print(k)
+ print("\n")
+
+ ch = context.active_brush_channel
+ finalch = context.final_brush_channel
+
+ mp = ch.mappings[ch.active_mapping]
+
+ ch_inherit = ch.inherit
+ ch_inherit = ch_inherit or (finalch.unified and not ch.disable_unified)
+
+ inherit = mp.inherit_mode == "ALWAYS" or (mp.inherit_mode == "USE_CHANNEL" and ch_inherit)
+ finalmp = finalch.mappings[ch.active_mapping] if inherit else mp
+ finalid = context.active_brush if finalmp == mp else context.scene
+
+ col = layout.column()
+ row = col.row()
+ row.prop(ch, "active_mapping", text="")
+ row.prop(finalmp, "enabled", text="", icon="STYLUS_PRESSURE")
+
+ col.prop(mp, "inherit_mode", text="Unified Mode")
+
+ if finalmp == mp:
+ path2 = UnifiedPaintPanel.paint_settings(context).path_from_id()
+ path2 += ".brush."
+ else:
+ path2 = "scene."
+
+ path2 += finalmp.path_from_id()
+
+ print("PATH2", path2)
+
+ col.prop(finalmp.curve, "curve_preset", text="Curve")
+ if finalmp.curve.curve_preset == "CUSTOM":
+ template_curve(col, mp.curve, "curve", path2 + ".curve.curve", use_negative_slope=True)
+
+ #col.label(text="Input Mapping")
+ col.prop(finalmp, "premultiply")
+ #col.prop(mp, "mapfunc", text="Repeat")
+
+ #col.label(text="Output Mapping")
+ row = col.row()
+ row.prop(mp, "min", slider=True)
+ row.prop(mp, "max", slider=True)
+
+ #col.prop(finalmp, "blendmode")
+
class UnifiedPaintPanel:
# subclass must set
@@ -9,15 +338,16 @@ class UnifiedPaintPanel:
@staticmethod
def get_brush_mode(context):
- """ Get the correct mode for this context. For any context where this returns None,
- no brush options should be displayed."""
+ """Get the correct mode for this context. For any context where this returns None,
+ no brush options should be displayed."""
mode = context.mode
- if mode == 'PARTICLE':
+ if mode == "PARTICLE":
# Particle brush settings currently completely do their own thing.
return None
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
+
tool = ToolSelectPanelHelper.tool_active_from_context(context)
if not tool:
@@ -33,12 +363,12 @@ class UnifiedPaintPanel:
if space_data:
space_type = space_data.type
- if space_type == 'IMAGE_EDITOR':
+ if space_type == "IMAGE_EDITOR":
if space_data.show_uvedit:
- return 'UV_SCULPT'
- return 'PAINT_2D'
- elif space_type in {'VIEW_3D', 'PROPERTIES'}:
- if mode == 'PAINT_TEXTURE':
+ return "UV_SCULPT"
+ return "PAINT_2D"
+ elif space_type in {"VIEW_3D", "PROPERTIES"}:
+ if mode == "PAINT_TEXTURE":
if tool_settings.image_paint:
return mode
else:
@@ -47,43 +377,327 @@ class UnifiedPaintPanel:
return None
@staticmethod
+ def get_channel(context, brush, prop_name, toolsettings_only=False, need_path=False):
+ ch = brush.channels[prop_name] if prop_name in brush.channels else None
+ unified_ch = context.tool_settings.unified_channels[prop_name]
+
+ path = None
+ inherit = False
+
+ if ch:
+ path = "sculpt.brush.channels[\"%s\"]" % prop_name
+ inherit = ch.inherit or (unified_ch.unified and not ch.disable_unified)
+
+ if not ch or inherit or toolsettings_only:
+ ch = context.tool_settings.unified_channels[prop_name]
+ path = "tool_settings.unified_channels[\"%s\"]" % prop_name
+
+ if need_path:
+ path = "tool_settings." + path
+ return (ch, path)
+ else:
+ return ch
+
+ @staticmethod
def paint_settings(context):
tool_settings = context.tool_settings
mode = UnifiedPaintPanel.get_brush_mode(context)
# 3D paint settings
- if mode == 'SCULPT':
+ if mode == "SCULPT":
return tool_settings.sculpt
- elif mode == 'PAINT_VERTEX':
+ elif mode == "PAINT_VERTEX":
return tool_settings.vertex_paint
- elif mode == 'PAINT_WEIGHT':
+ elif mode == "PAINT_WEIGHT":
return tool_settings.weight_paint
- elif mode == 'PAINT_TEXTURE':
+ elif mode == "PAINT_TEXTURE":
return tool_settings.image_paint
- elif mode == 'PARTICLE':
+ elif mode == "PARTICLE":
return tool_settings.particle_edit
# 2D paint settings
- elif mode == 'PAINT_2D':
+ elif mode == "PAINT_2D":
return tool_settings.image_paint
- elif mode == 'UV_SCULPT':
+ elif mode == "UV_SCULPT":
return tool_settings.uv_sculpt
# Grease Pencil settings
- elif mode == 'PAINT_GPENCIL':
+ elif mode == "PAINT_GPENCIL":
return tool_settings.gpencil_paint
- elif mode == 'SCULPT_GPENCIL':
+ elif mode == "SCULPT_GPENCIL":
return tool_settings.gpencil_sculpt_paint
- elif mode == 'WEIGHT_GPENCIL':
+ elif mode == "WEIGHT_GPENCIL":
return tool_settings.gpencil_weight_paint
- elif mode == 'VERTEX_GPENCIL':
+ elif mode == "VERTEX_GPENCIL":
return tool_settings.gpencil_vertex_paint
- elif mode == 'SCULPT_CURVES':
+ elif mode == "SCULPT_CURVES":
return tool_settings.curves_sculpt
return None
+
+ @staticmethod
+ def channel_unified(layout, context, brush, prop_name, icon='NONE', pressure=None, text=None, baselayout=None,
+ slider=False, header=False, show_reorder=False, expand=None, toolsettings_only=False, ui_editing=None,
+ show_mappings=True, brush_only=False, use_negative_slope=None):
+ """ Generalized way of adding brush options to the UI,
+ along with their pen pressure setting and global toggle
+
+ note that ui_editing is no longer a bool, it can also be "mappings_only"
+ to just show the input mappings controls.
+
+ for curve channels, if use_negative_slope is None then
+ `channel.curve_preset_negative_slope` will be used.
+ """
+
+ if baselayout is None:
+ baselayout = layout
+
+ if slider is None:
+ slider = False
+
+ if header:
+ ui_editing = False
+ show_mappings = False
+ elif ui_editing is None:
+ ui_editing = True
+
+ if not context.tool_settings.brush_editor_mode:
+ ui_editing = False
+ show_reorder = False
+
+ if context.mode != "SCULPT":
+ return UnifiedPaintPanel.prop_unified(layout, context, brush, prop_name, icon=icon, text=text, slider=slider, header=header, expand=expand)
+
+ sculpt = context.tool_settings.sculpt
+ ch = brush.channels[prop_name]
+
+ inherit = ch.inherit
+ if context.tool_settings.unified_channels[ch.idname].unified:
+ inherit = True
+
+ # dynamically switch to unprojected radius if necassary
+ if prop_name == "size":
+ size_mode = brush.use_locked_size == "SCENE"
+ if size_mode:
+ prop_name = "unprojected_radius"
+ ch = brush.channels[prop_name]
+
+ finalch = ch
+
+ if prop_name in {"direction", "use_locked_size", "automasking"}:
+ expand = True
+
+ if ch.type == "BITMASK":
+ layout = layout.column(align=True)
+
+ row = layout.row(align=True)
+
+ if not header:
+ row.use_property_split = True
+ row.use_property_decorate = False
+
+ if pressure is None:
+ pressure = ch.type not in ["VEC3", "VEC4", "BITMASK", "ENUM", "BOOL"]
+
+ if text is None:
+ text = ch.name
+
+ path = ""
+ proppath = ""
+ final_prop_name = prop_name
+
+ pressurech = ch
+
+ if not brush_only and (inherit or toolsettings_only):
+ finalch = context.tool_settings.unified_channels[prop_name]
+
+ path = "tool_settings.unified_channels[\"%s\"]" % ch.idname
+ proppath = "tool_settings.unified_properties"
+ final_prop_name = '["%s"]' % (prop_name)
+ else:
+ path = "tool_settings.sculpt.brush.channels[\"%s\"]" % ch.idname
+ proppath = "tool_settings.sculpt.brush"
+
+ finalowner = context.path_resolve(proppath)
+
+ inherit_pressure = ch.mappings["PRESSURE"].inherit_mode == "ALWAYS"
+ if ch.mappings["PRESSURE"].inherit_mode == "USE_CHANNEL":
+ inherit_pressure = inherit
+
+ if pressurech == ch and inherit_pressure:
+ pressurech = context.tool_settings.unified_channels[ch.idname]
+
+ if show_reorder:
+ props = row.operator("brush.change_channel_order", text="", icon="TRIA_UP")
+ props.channel = ch.idname
+ props.filterkey = "show_in_workspace"
+ props.direction = -1
+
+ props = row.operator("brush.change_channel_order", text="", icon="TRIA_DOWN")
+ props.filterkey = "show_in_workspace"
+ props.channel = ch.idname
+ props.direction = 1
+
+ if ui_editing and not header:
+ row2 = row.row(align=True)
+ row2.prop(ch, "show_in_workspace", text="", icon="WORKSPACE")
+ row2.prop(ch, "show_in_context_menu", text="", icon="MENU_PANEL")
+ row2.prop(ch, "show_in_header", text="", icon="TOPBAR")
+
+ if ch.type == "CURVE":
+ row.prop(finalch.curve, "curve_preset", text=text)
+
+ if use_negative_slope is None:
+ use_negative_slope = finalch.curve.preset_slope_negative
+
+ if not header and finalch.curve.curve_preset == "CUSTOM":
+ path2 = path + ".curve.curve"
+ template_curve(layout, finalch.curve, "curve", path2, use_negative_slope=use_negative_slope)
+
+ elif ch.type == "BITMASK":
+ if header or not expand:
+ row.label(text=text)
+ row.prop_menu_enum(finalowner, final_prop_name, text=text)
+ else:
+ # why is it so hard to make bitflag checkboxes? - joeedh
+
+ row.label(text=text)
+ col = layout.column(align=True)
+ col.emboss = "NONE"
+ col.use_property_decorate = False
+ col.use_property_split = False
+
+ for j, item in enumerate(finalch.enum_items):
+ if item.identifier in finalch.value:
+ itemicon = "CHECKBOX_HLT"
+ else:
+ itemicon = "CHECKBOX_DEHLT"
+ col.prop_enum(finalowner, final_prop_name, item.identifier, icon=itemicon)
+
+ elif header and ch.idname == "direction":
+ row2 = row.row(align=True)
+ row2.use_property_split = False
+ row2.use_property_decorate = False
+
+ # replicate pre-existing functionality of direction showing up as
+ # +/- in the header
+ row2.prop_enum(finalowner, final_prop_name, "ADD", text="")
+ row2.prop_enum(finalowner, final_prop_name, "SUBTRACT", text="")
+ elif expand is not None:
+ row.prop(finalowner, final_prop_name, icon=icon, text=text, slider=slider, expand=expand)
+ else:
+ row.prop(finalowner, final_prop_name, icon=icon, text=text, slider=slider)
+
+ pressure = pressure and ch.type not in ["BOOL", "ENUM", "BITMASK", "CURVE"]
+
+ if pressure:
+ row.prop(pressurech.mappings["PRESSURE"], "enabled", text="", icon="STYLUS_PRESSURE")
+
+ if not header:
+ if ch.type == "BITMASK" and not toolsettings_only and ch == finalch:
+ row.prop(ch, "inherit_if_unset", text="Combine With Defaults")
+
+ if not toolsettings_only:
+ row.prop(context.tool_settings.unified_channels[prop_name], "unified", text="", icon='BRUSHES_ALL')
+
+ if ui_editing and not toolsettings_only:
+ row.prop(ch, "inherit", text="", icon='BRUSHES_ALL')
+
+ if ch.type in ["BITMASK", "BOOL", "CURVE", "ENUM"]:
+ return
+
+ if not show_mappings and not show_reorder:
+ return
+
+ row.context_pointer_set("active_brush", brush)
+ row.context_pointer_set("active_brush_channel", ch)
+ row.context_pointer_set("final_brush_channel", finalch)
+
+ row.popover("BRUSH_PT_channel_panel", icon="DOWNARROW_HLT", text="")
+ #row.prop(ch, "ui_expanded", emboss=False, text="", icon="DOWNARROW_HLT" if ch.ui_expanded else "RIGHTARROW")
+
+ if 0: #ch.ui_expanded:
+ layout = baselayout.column()
+
+ for i, mp in enumerate(ch.mappings):
+ mp0 = mp
+
+ if mp.inherit_mode != "NEVER":
+ mp = finalch.mappings[i]
+
+ if mp.inherit_mode == "ALWAYS" and finalch == ch:
+ mp = context.tool_channels.unified_channels[ch.idname].mappings[i]
+
+ row2 = layout.row(align=True)
+ row2.use_property_split = False
+ row2.use_property_decorate = False
+
+ name = mp.type.lower()
+
+ if len(name) > 0:
+ name = name[0].upper() + name[1:]
+ else:
+ name = "name error"
+
+ row2.prop(mp0, "ui_expanded", text="", emboss=False, icon="DOWNARROW_HLT" if mp.ui_expanded else "RIGHTARROW")
+
+ row2.label(text=name)
+
+ #note we only expose a single icon that toggles between ALWAYS and NEVER
+ #mode, to lessen user confusion.
+
+ #row3 = row2.row(align=True)
+ #row3.prop_enum(mp0, "inherit_mode", "ALWAYS", icon="BRUSHES_ALL", text="")
+ #row3.prop_enum(mp0, "inherit_mode", "NEVER", icon="FORWARD", text="")
+ #row3.prop_enum(mp0, "inherit_mode", "USE_CHANNEL", icon="FORWARD", text="")
+
+ row2.prop(mp0, "inherit", text="", icon="BRUSHES_ALL")
+
+ row2.prop(mp, "enabled", text="", icon="STYLUS_PRESSURE")
+ row2.prop(mp, "invert", text="", icon="ARROW_LEFTRIGHT")
+
+ if mp0.ui_expanded:
+ #XXX why do I have to feed use_negative_slope as true
+ #here?
+ box = layout.box()
+
+ col = box.column(align=True)
+ col.use_property_split = True
+ col.use_property_decorate = False
+
+ if mp0.inherit_mode == "ALWAYS" or (mp0.inherit_mode == "USE_CHANNEL" and inherit):
+ path2 = path + ".mappings[\"%s\"].curve" % (mp.type)
+ else:
+ brushpath = "tool_settings.sculpt.brush.channels[\"%s\"]" % ch.idname
+ path2 = brushpath + ".mappings[\"%s\"].curve" % (mp.type)
+
+
+ col.prop(mp.curve, "curve_preset", text=text)
+
+ row = col.row(align=True)
+
+ if not header and mp.curve.curve_preset == "CUSTOM":
+ template_curve(col, mp.curve, "curve", path2 + ".curve", use_negative_slope=True)
+
+ col.prop(mp, "factor")
+ col.prop(mp, "blendmode")
+
+ col.label(text="Input Mapping")
+ #row = col.row()
+ col.prop(mp, "premultiply", slider=True)
+ col.prop(mp, "mapfunc")
+
+ if mp.mapfunc in ("SQUARE", "CUTOFF"):
+ col.prop(mp, "func_cutoff")
+
+ col.label(text="Output Mapping")
+ col.prop(mp, "min", slider=True)
+ col.prop(mp, "max", slider=True)
+
+ #row2.prop(mp, "curve")
+
+ return row
@staticmethod
- def prop_unified(
- layout,
+ def prop_unified(layout,
context,
brush,
prop_name,
@@ -93,9 +707,49 @@ class UnifiedPaintPanel:
text=None,
slider=False,
header=False,
- ):
+ expand=None):
""" Generalized way of adding brush options to the UI,
along with their pen pressure setting and global toggle, if they exist. """
+
+ if context.mode == "SCULPT":
+ return UnifiedPaintPanel.channel_unified(layout, context, brush, prop_name, icon=icon, text=text, slider=slider, header=header)
+
+ row = layout.row(align=True)
+ ups = context.tool_settings.unified_paint_settings
+ prop_owner = brush
+ if unified_name and getattr(ups, unified_name):
+ prop_owner = ups
+
+ if expand is not None:
+ row.prop(prop_owner, prop_name, icon=icon, text=text, slider=slider, expand=expand)
+ else:
+ row.prop(prop_owner, prop_name, icon=icon, text=text, slider=slider)
+
+ if pressure_name:
+ row.prop(brush, pressure_name, text="")
+
+ if unified_name and not header:
+ # NOTE: We don't draw UnifiedPaintSettings in the header to reduce
+ # clutter. D5928#136281
+ row.prop(ups, unified_name, text="", icon='BRUSHES_ALL')
+
+ return row
+
+ @staticmethod
+ def prop_unified(
+ layout,
+ context,
+ brush,
+ prop_name,
+ unified_name=None,
+ pressure_name=None,
+ icon="NONE",
+ text=None,
+ slider=False,
+ header=False,
+ ):
+ """Generalized way of adding brush options to the UI,
+ along with their pen pressure setting and global toggle, if they exist."""
row = layout.row(align=True)
ups = context.tool_settings.unified_paint_settings
prop_owner = brush
@@ -109,7 +763,7 @@ class UnifiedPaintPanel:
if unified_name and not header:
# NOTE: We don't draw UnifiedPaintSettings in the header to reduce clutter. D5928#136281
- row.prop(ups, unified_name, text="", icon='BRUSHES_ALL')
+ row.prop(ups, unified_name, text="", icon="BRUSHES_ALL")
return row
@@ -144,14 +798,16 @@ class BrushSelectPanel(BrushPanel):
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)
+ 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")
col = row.column()
- col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
+ 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="")
+ col.prop(brush, "use_custom_icon", toggle=True, icon="FILE_IMAGE", text="")
if brush.use_custom_icon:
layout.prop(brush, "icon_filepath", text="")
@@ -159,7 +815,7 @@ class BrushSelectPanel(BrushPanel):
class ColorPalettePanel(BrushPanel):
bl_label = "Color Palette"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -169,7 +825,7 @@ class ColorPalettePanel(BrushPanel):
settings = cls.paint_settings(context)
brush = settings.brush
- if context.space_data.type == 'IMAGE_EDITOR' or context.image_paint_object:
+ if context.space_data.type == "IMAGE_EDITOR" or context.image_paint_object:
capabilities = brush.image_paint_capabilities
return capabilities.has_color
@@ -193,7 +849,7 @@ class ColorPalettePanel(BrushPanel):
class ClonePanel(BrushPanel):
bl_label = "Clone"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -203,9 +859,9 @@ class ClonePanel(BrushPanel):
settings = cls.paint_settings(context)
mode = cls.get_brush_mode(context)
- if mode == 'PAINT_TEXTURE':
+ if mode == "PAINT_TEXTURE":
brush = settings.brush
- return brush.image_tool == 'CLONE'
+ return brush.image_tool == "CLONE"
return False
def draw_header(self, context):
@@ -221,13 +877,16 @@ class ClonePanel(BrushPanel):
ob = context.active_object
col = layout.column()
- if settings.mode == 'MATERIAL':
+ if settings.mode == "MATERIAL":
if len(ob.material_slots) > 1:
col.label(text="Materials")
col.template_list(
- "MATERIAL_UL_matslots", "",
- ob, "material_slots",
- ob, "active_material_index",
+ "MATERIAL_UL_matslots",
+ "",
+ ob,
+ "material_slots",
+ ob,
+ "active_material_index",
rows=2,
)
@@ -235,25 +894,30 @@ class ClonePanel(BrushPanel):
if mat:
col.label(text="Source Clone Slot")
col.template_list(
- "TEXTURE_UL_texpaintslots", "",
- mat, "texture_paint_slots",
- mat, "paint_clone_slot",
+ "TEXTURE_UL_texpaintslots",
+ "",
+ mat,
+ "texture_paint_slots",
+ mat,
+ "paint_clone_slot",
rows=2,
)
- elif settings.mode == 'IMAGE':
+ elif settings.mode == "IMAGE":
mesh = ob.data
clone_text = mesh.uv_layer_clone.name if mesh.uv_layer_clone else ""
col.label(text="Source Clone Image")
col.template_ID(settings, "clone_image")
col.label(text="Source Clone UV Map")
- col.menu("VIEW3D_MT_tools_projectpaint_clone", text=clone_text, translate=False)
+ col.menu(
+ "VIEW3D_MT_tools_projectpaint_clone", text=clone_text, translate=False
+ )
class TextureMaskPanel(BrushPanel):
bl_label = "Texture Mask"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
layout = self.layout
@@ -264,13 +928,15 @@ class TextureMaskPanel(BrushPanel):
mask_tex_slot = brush.mask_texture_slot
col = layout.column()
- col.template_ID_preview(mask_tex_slot, "texture", new="texture.new", rows=3, cols=8)
+ col.template_ID_preview(
+ mask_tex_slot, "texture", new="texture.new", rows=3, cols=8
+ )
# map_mode
layout.row().prop(mask_tex_slot, "mask_map_mode", text="Mask Mapping")
- if mask_tex_slot.map_mode == 'STENCIL':
- if brush.mask_texture and brush.mask_texture.type == 'IMAGE':
+ if mask_tex_slot.map_mode == "STENCIL":
+ if brush.mask_texture and brush.mask_texture.type == "IMAGE":
layout.operator("brush.stencil_fit_image_aspect").mask = True
layout.operator("brush.stencil_reset_transform").mask = True
@@ -283,7 +949,10 @@ class TextureMaskPanel(BrushPanel):
if mask_tex_slot.has_texture_angle_source:
col.prop(mask_tex_slot, "use_rake", text="Rake")
- if brush.brush_capabilities.has_random_texture_angle and mask_tex_slot.has_random_texture_angle:
+ if (
+ brush.brush_capabilities.has_random_texture_angle
+ and mask_tex_slot.has_random_texture_angle
+ ):
col.prop(mask_tex_slot, "use_random", text="Random")
if mask_tex_slot.use_random:
col.prop(mask_tex_slot, "random_angle", text="Random Angle")
@@ -295,7 +964,7 @@ class TextureMaskPanel(BrushPanel):
class StrokePanel(BrushPanel):
bl_label = "Stroke"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
bl_ui_units_x = 13
def draw(self, context):
@@ -327,11 +996,16 @@ class StrokePanel(BrushPanel):
row = col.row(align=True)
row.prop(brush, "spacing", text="Spacing")
- if mode == 'SCULPT':
- col.row().prop(brush, "use_scene_spacing", text="Spacing Distance", expand=True)
+ if mode == "SCULPT":
+ col.row().prop(
+ brush, "use_scene_spacing", text="Spacing Distance", expand=True
+ )
- if mode in {'PAINT_TEXTURE', 'PAINT_2D', 'SCULPT'}:
- if brush.image_paint_capabilities.has_space_attenuation or brush.sculpt_capabilities.has_space_attenuation:
+ if mode in {"PAINT_TEXTURE", "PAINT_2D", "SCULPT"}:
+ if (
+ brush.image_paint_capabilities.has_space_attenuation
+ or brush.sculpt_capabilities.has_space_attenuation
+ ):
col.prop(brush, "use_space_attenuation")
if brush.use_curve:
@@ -346,10 +1020,12 @@ class StrokePanel(BrushPanel):
col.prop(brush, "dash_ratio", text="Dash Ratio")
col.prop(brush, "dash_samples", text="Dash Length")
- if (mode == 'SCULPT' and brush.sculpt_capabilities.has_jitter) or mode != 'SCULPT':
+ if (
+ mode == "SCULPT" and brush.sculpt_capabilities.has_jitter
+ ) or mode != "SCULPT":
col.separator()
row = col.row(align=True)
- if brush.jitter_unit == 'BRUSH':
+ if brush.jitter_unit == "BRUSH":
row.prop(brush, "jitter", slider=True)
else:
row.prop(brush, "jitter_absolute")
@@ -362,7 +1038,7 @@ class StrokePanel(BrushPanel):
class SmoothStrokePanel(BrushPanel):
bl_label = "Stabilize Stroke"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -396,7 +1072,7 @@ class SmoothStrokePanel(BrushPanel):
class FalloffPanel(BrushPanel):
bl_label = "Falloff"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
@classmethod
def poll(cls, context):
@@ -405,9 +1081,9 @@ class FalloffPanel(BrushPanel):
settings = cls.paint_settings(context)
if not (settings and settings.brush and settings.brush.curve):
return False
- if cls.get_brush_mode(context) == 'SCULPT_CURVES':
+ if cls.get_brush_mode(context) == "SCULPT_CURVES":
brush = settings.brush
- if brush.curves_sculpt_tool in {'ADD', 'DELETE'}:
+ if brush.curves_sculpt_tool in {"ADD", "DELETE"}:
return False
return True
@@ -424,22 +1100,35 @@ class FalloffPanel(BrushPanel):
row = col.row(align=True)
row.prop(brush, "curve_preset", text="")
- if brush.curve_preset == 'CUSTOM':
+ if brush.curve_preset == "CUSTOM":
layout.template_curve_mapping(brush, "curve", brush=True)
col = layout.column(align=True)
row = col.row(align=True)
- row.operator("brush.curve_preset", icon='SMOOTHCURVE', text="").shape = 'SMOOTH'
- row.operator("brush.curve_preset", icon='SPHERECURVE', text="").shape = 'ROUND'
- row.operator("brush.curve_preset", icon='ROOTCURVE', text="").shape = 'ROOT'
- row.operator("brush.curve_preset", icon='SHARPCURVE', text="").shape = 'SHARP'
- row.operator("brush.curve_preset", icon='LINCURVE', text="").shape = 'LINE'
- row.operator("brush.curve_preset", icon='NOCURVE', text="").shape = 'MAX'
+ row.operator(
+ "brush.curve_preset", icon="SMOOTHCURVE", text=""
+ ).shape = "SMOOTH"
+ row.operator(
+ "brush.curve_preset", icon="SPHERECURVE", text=""
+ ).shape = "ROUND"
+ row.operator("brush.curve_preset", icon="ROOTCURVE", text="").shape = "ROOT"
+ row.operator(
+ "brush.curve_preset", icon="SHARPCURVE", text=""
+ ).shape = "SHARP"
+ row.operator("brush.curve_preset", icon="LINCURVE", text="").shape = "LINE"
+ row.operator("brush.curve_preset", icon="NOCURVE", text="").shape = "MAX"
show_fallof_shape = False
- if mode in {'SCULPT', 'PAINT_VERTEX', 'PAINT_WEIGHT'} and brush.sculpt_tool != 'POSE':
+ if (
+ mode in {"SCULPT", "PAINT_VERTEX", "PAINT_WEIGHT"}
+ and brush.sculpt_tool != "POSE"
+ ):
show_fallof_shape = True
- if not show_fallof_shape and mode == 'SCULPT_CURVES' and context.space_data.type == 'PROPERTIES':
+ if (
+ not show_fallof_shape
+ and mode == "SCULPT_CURVES"
+ and context.space_data.type == "PROPERTIES"
+ ):
show_fallof_shape = True
if show_fallof_shape:
@@ -452,7 +1141,7 @@ class FalloffPanel(BrushPanel):
class DisplayPanel(BrushPanel):
bl_label = "Brush Cursor"
- bl_options = {'DEFAULT_CLOSED'}
+ bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
settings = self.paint_settings(context)
@@ -479,37 +1168,64 @@ class DisplayPanel(BrushPanel):
col.active = brush.brush_capabilities.has_overlay and settings.show_brush
col.prop(brush, "cursor_color_add", text="Cursor Color")
- if mode == 'SCULPT' and brush.sculpt_capabilities.has_secondary_color:
+ if mode == "SCULPT" and brush.sculpt_capabilities.has_secondary_color:
col.prop(brush, "cursor_color_subtract", text="Inverse Color")
col.separator()
row = col.row(align=True)
row.prop(brush, "cursor_overlay_alpha", text="Falloff Opacity")
- row.prop(brush, "use_cursor_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
row.prop(
- brush, "use_cursor_overlay", text="", toggle=True,
- icon='HIDE_OFF' if brush.use_cursor_overlay else 'HIDE_ON',
+ brush,
+ "use_cursor_overlay_override",
+ toggle=True,
+ text="",
+ icon="BRUSH_DATA",
+ )
+ row.prop(
+ brush,
+ "use_cursor_overlay",
+ text="",
+ toggle=True,
+ icon="HIDE_OFF" if brush.use_cursor_overlay else "HIDE_ON",
)
- if mode in {'PAINT_2D', 'PAINT_TEXTURE', 'PAINT_VERTEX', 'SCULPT'}:
+ if mode in {"PAINT_2D", "PAINT_TEXTURE", "PAINT_VERTEX", "SCULPT"}:
row = col.row(align=True)
row.prop(brush, "texture_overlay_alpha", text="Texture Opacity")
- row.prop(brush, "use_primary_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
- if tex_slot.map_mode != 'STENCIL':
+ row.prop(
+ brush,
+ "use_primary_overlay_override",
+ toggle=True,
+ text="",
+ icon="BRUSH_DATA",
+ )
+ if tex_slot.map_mode != "STENCIL":
row.prop(
- brush, "use_primary_overlay", text="", toggle=True,
- icon='HIDE_OFF' if brush.use_primary_overlay else 'HIDE_ON',
+ brush,
+ "use_primary_overlay",
+ text="",
+ toggle=True,
+ icon="HIDE_OFF" if brush.use_primary_overlay else "HIDE_ON",
)
- if mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
+ if mode in {"PAINT_TEXTURE", "PAINT_2D"}:
row = col.row(align=True)
row.prop(brush, "mask_overlay_alpha", text="Mask Texture Opacity")
- row.prop(brush, "use_secondary_overlay_override", toggle=True, text="", icon='BRUSH_DATA')
- if tex_slot_mask.map_mode != 'STENCIL':
+ row.prop(
+ brush,
+ "use_secondary_overlay_override",
+ toggle=True,
+ text="",
+ icon="BRUSH_DATA",
+ )
+ if tex_slot_mask.map_mode != "STENCIL":
row.prop(
- brush, "use_secondary_overlay", text="", toggle=True,
- icon='HIDE_OFF' if brush.use_secondary_overlay else 'HIDE_ON',
+ brush,
+ "use_secondary_overlay",
+ text="",
+ toggle=True,
+ icon="HIDE_OFF" if brush.use_secondary_overlay else "HIDE_ON",
)
@@ -520,14 +1236,16 @@ class VIEW3D_MT_tools_projectpaint_clone(Menu):
layout = self.layout
for i, uv_layer in enumerate(context.active_object.data.uv_layers):
- props = layout.operator("wm.context_set_int", text=uv_layer.name, translate=False)
+ props = layout.operator(
+ "wm.context_set_int", text=uv_layer.name, translate=False
+ )
props.data_path = "active_object.data.uv_layer_clone_index"
props.value = i
def brush_settings(layout, context, brush, popover=False):
- """ Draw simple brush settings for Sculpt,
- Texture/Vertex/Weight Paint modes, or skip certain settings for the popover """
+ """Draw simple brush settings for Sculpt,
+ Texture/Vertex/Weight Paint modes, or skip certain settings for the popover"""
mode = UnifiedPaintPanel.get_brush_mode(context)
@@ -535,14 +1253,17 @@ def brush_settings(layout, context, brush, popover=False):
brush_shared_settings(layout, context, brush, popover)
# Sculpt Mode #
- if mode == 'SCULPT':
+ if mode == "SCULPT":
capabilities = brush.sculpt_capabilities
sculpt_tool = brush.sculpt_tool
# normal_radius_factor
layout.prop(brush, "normal_radius_factor", slider=True)
- if context.preferences.experimental.use_sculpt_tools_tilt and capabilities.has_tilt:
+ if (
+ context.preferences.experimental.use_sculpt_tools_tilt
+ and capabilities.has_tilt
+ ):
layout.prop(brush, "tilt_strength_factor", slider=True)
row = layout.row(align=True)
@@ -563,8 +1284,8 @@ def brush_settings(layout, context, brush, popover=False):
# topology_rake_factor
if (
- capabilities.has_topology_rake and
- context.sculpt_object.use_dynamic_topology_sculpting
+ capabilities.has_topology_rake
+ and context.sculpt_object.use_dynamic_topology_sculpting
):
layout.prop(brush, "topology_rake_factor", slider=True)
@@ -575,7 +1296,7 @@ def brush_settings(layout, context, brush, popover=False):
# crease_pinch_factor
if capabilities.has_pinch_factor:
text = "Pinch"
- if sculpt_tool in {'BLOB', 'SNAKE_HOOK'}:
+ if sculpt_tool in {"BLOB", "SNAKE_HOOK"}:
text = "Magnify"
layout.prop(brush, "crease_pinch_factor", slider=True, text=text)
@@ -618,30 +1339,34 @@ def brush_settings(layout, context, brush, popover=False):
ups = context.scene.tool_settings.unified_paint_settings
row = layout.row(align=True)
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
- UnifiedPaintPanel.prop_unified_color(row, context, brush, "secondary_color", text="")
+ UnifiedPaintPanel.prop_unified_color(
+ row, context, brush, "secondary_color", text=""
+ )
row.separator()
- row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False)
- row.prop(ups, "use_unified_color", text="", icon='BRUSHES_ALL')
+ row.operator(
+ "paint.brush_colors_flip", icon="FILE_REFRESH", text="", emboss=False
+ )
+ row.prop(ups, "use_unified_color", text="", icon="BRUSHES_ALL")
layout.prop(brush, "blend", text="Blend Mode")
# Per sculpt tool options.
- if sculpt_tool == 'CLAY_STRIPS':
+ if sculpt_tool == "CLAY_STRIPS":
row = layout.row()
row.prop(brush, "tip_roundness")
- elif sculpt_tool == 'ELASTIC_DEFORM':
+ elif sculpt_tool == "ELASTIC_DEFORM":
layout.separator()
layout.prop(brush, "elastic_deform_type")
layout.prop(brush, "elastic_deform_volume_preservation", slider=True)
layout.separator()
- elif sculpt_tool == 'SNAKE_HOOK':
+ elif sculpt_tool == "SNAKE_HOOK":
layout.separator()
layout.prop(brush, "snake_hook_deform_type")
layout.separator()
- elif sculpt_tool == 'POSE':
+ elif sculpt_tool == "POSE":
layout.separator()
layout.prop(brush, "deform_target")
layout.separator()
@@ -649,9 +1374,12 @@ def brush_settings(layout, context, brush, popover=False):
layout.prop(brush, "pose_origin_type")
layout.prop(brush, "pose_offset")
layout.prop(brush, "pose_smooth_iterations")
- if brush.pose_deform_type == 'ROTATE_TWIST' and brush.pose_origin_type in {'TOPOLOGY', 'FACE_SETS'}:
+ if brush.pose_deform_type == "ROTATE_TWIST" and brush.pose_origin_type in {
+ "TOPOLOGY",
+ "FACE_SETS",
+ }:
layout.prop(brush, "pose_ik_segments")
- if brush.pose_deform_type == 'SCALE_TRANSLATE':
+ if brush.pose_deform_type == "SCALE_TRANSLATE":
layout.prop(brush, "use_pose_lock_rotation")
layout.prop(brush, "use_pose_ik_anchored")
layout.prop(brush, "use_connected_only")
@@ -659,14 +1387,14 @@ def brush_settings(layout, context, brush, popover=False):
layout.separator()
- elif sculpt_tool == 'CLOTH':
+ elif sculpt_tool == "CLOTH":
layout.separator()
layout.prop(brush, "cloth_simulation_area_type")
- if brush.cloth_simulation_area_type != 'GLOBAL':
+ if brush.cloth_simulation_area_type != "GLOBAL":
layout.prop(brush, "cloth_sim_limit")
layout.prop(brush, "cloth_sim_falloff")
- if brush.cloth_simulation_area_type == 'LOCAL':
+ if brush.cloth_simulation_area_type == "LOCAL":
layout.prop(brush, "use_cloth_pin_simulation_boundary")
layout.separator()
@@ -680,25 +1408,25 @@ def brush_settings(layout, context, brush, popover=False):
layout.prop(brush, "use_cloth_collision")
layout.separator()
- elif sculpt_tool == 'SCRAPE':
+ elif sculpt_tool == "SCRAPE":
row = layout.row(align=True)
row.prop(brush, "area_radius_factor")
row.prop(brush, "use_pressure_area_radius", text="")
row = layout.row()
row.prop(brush, "invert_to_scrape_fill", text="Invert to Fill")
- elif sculpt_tool == 'FILL':
+ elif sculpt_tool == "FILL":
row = layout.row(align=True)
row.prop(brush, "area_radius_factor")
row.prop(brush, "use_pressure_area_radius", text="")
row = layout.row()
row.prop(brush, "invert_to_scrape_fill", text="Invert to Scrape")
- elif sculpt_tool == 'GRAB':
+ elif sculpt_tool == "GRAB":
layout.prop(brush, "use_grab_active_vertex")
layout.prop(brush, "use_grab_silhouette")
- elif sculpt_tool == 'PAINT':
+ elif sculpt_tool == "PAINT":
row = layout.row(align=True)
row.prop(brush, "flow")
row.prop(brush, "invert_flow_pressure", text="")
@@ -728,11 +1456,11 @@ def brush_settings(layout, context, brush, popover=False):
row = layout.row()
row.prop(brush, "tip_scale_x")
- elif sculpt_tool == 'SMEAR':
+ elif sculpt_tool == "SMEAR":
col = layout.column()
col.prop(brush, "smear_deform_type")
- elif sculpt_tool == 'BOUNDARY':
+ elif sculpt_tool == "BOUNDARY":
layout.prop(brush, "deform_target")
layout.separator()
col = layout.column()
@@ -740,52 +1468,58 @@ def brush_settings(layout, context, brush, popover=False):
col.prop(brush, "boundary_falloff_type")
col.prop(brush, "boundary_offset")
- elif sculpt_tool == 'TOPOLOGY':
+ elif sculpt_tool == "TOPOLOGY":
col = layout.column()
col.prop(brush, "slide_deform_type")
- elif sculpt_tool == 'MULTIPLANE_SCRAPE':
+ elif sculpt_tool == "MULTIPLANE_SCRAPE":
col = layout.column()
col.prop(brush, "multiplane_scrape_angle")
col.prop(brush, "use_multiplane_scrape_dynamic")
col.prop(brush, "show_multiplane_scrape_planes_preview")
- elif sculpt_tool == 'SMOOTH':
+ elif sculpt_tool == "SMOOTH":
col = layout.column()
col.prop(brush, "smooth_deform_type")
- if brush.smooth_deform_type == 'SURFACE':
+ if brush.smooth_deform_type == "SURFACE":
col.prop(brush, "surface_smooth_shape_preservation")
col.prop(brush, "surface_smooth_current_vertex")
col.prop(brush, "surface_smooth_iterations")
- elif sculpt_tool == 'DISPLACEMENT_SMEAR':
+ elif sculpt_tool == "DISPLACEMENT_SMEAR":
col = layout.column()
col.prop(brush, "smear_deform_type")
- elif sculpt_tool == 'MASK':
+ elif sculpt_tool == "MASK":
layout.row().prop(brush, "mask_tool", expand=True)
# End sculpt_tool interface.
# 3D and 2D Texture Paint Mode.
- elif mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
+ elif mode in {"PAINT_TEXTURE", "PAINT_2D"}:
capabilities = brush.image_paint_capabilities
- if brush.image_tool == 'FILL':
+ if brush.image_tool == "FILL":
# For some reason fill threshold only appears to be implemented in 2D paint.
- if brush.color_type == 'COLOR':
- if mode == 'PAINT_2D':
- layout.prop(brush, "fill_threshold", text="Fill Threshold", slider=True)
- elif brush.color_type == 'GRADIENT':
+ if brush.color_type == "COLOR":
+ if mode == "PAINT_2D":
+ layout.prop(
+ brush, "fill_threshold", text="Fill Threshold", slider=True
+ )
+ elif brush.color_type == "GRADIENT":
layout.row().prop(brush, "gradient_fill_mode", expand=True)
- elif mode == 'SCULPT_CURVES':
- if brush.curves_sculpt_tool == 'ADD':
+ elif mode == "SCULPT_CURVES":
+ if brush.curves_sculpt_tool == "ADD":
layout.prop(brush.curves_sculpt_settings, "add_amount")
col = layout.column(heading="Interpolate", align=True)
col.prop(brush.curves_sculpt_settings, "interpolate_length", text="Length")
col.prop(brush.curves_sculpt_settings, "interpolate_shape", text="Shape")
- col.prop(brush.curves_sculpt_settings, "interpolate_point_count", text="Point Count")
+ col.prop(
+ brush.curves_sculpt_settings,
+ "interpolate_point_count",
+ text="Point Count",
+ )
col = layout.column()
col.active = not brush.curves_sculpt_settings.interpolate_length
@@ -794,13 +1528,13 @@ def brush_settings(layout, context, brush, popover=False):
col = layout.column()
col.active = not brush.curves_sculpt_settings.interpolate_point_count
col.prop(brush.curves_sculpt_settings, "points_per_curve")
- elif brush.curves_sculpt_tool == 'GROW_SHRINK':
+ elif brush.curves_sculpt_tool == "GROW_SHRINK":
layout.prop(brush.curves_sculpt_settings, "scale_uniform")
layout.prop(brush.curves_sculpt_settings, "minimum_length")
def brush_shared_settings(layout, context, brush, popover=False):
- """ Draw simple brush settings that are shared between different paint modes. """
+ """Draw simple brush settings that are shared between different paint modes."""
mode = UnifiedPaintPanel.get_brush_mode(context)
@@ -814,14 +1548,14 @@ def brush_shared_settings(layout, context, brush, popover=False):
direction = False
# 3D and 2D Texture Paint #
- if mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
+ if mode in {"PAINT_TEXTURE", "PAINT_2D"}:
if not popover:
blend_mode = brush.image_paint_capabilities.has_color
size = brush.image_paint_capabilities.has_radius
strength = strength_pressure = True
# Sculpt #
- if mode == 'SCULPT':
+ if mode == "SCULPT":
size_mode = True
if not popover:
size = True
@@ -830,7 +1564,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
direction = not brush.sculpt_capabilities.has_direction
# Vertex Paint #
- if mode == 'PAINT_VERTEX':
+ if mode == "PAINT_VERTEX":
if not popover:
blend_mode = True
size = True
@@ -838,25 +1572,25 @@ def brush_shared_settings(layout, context, brush, popover=False):
strength_pressure = True
# Weight Paint #
- if mode == 'PAINT_WEIGHT':
+ if mode == "PAINT_WEIGHT":
if not popover:
size = True
weight = brush.weight_paint_capabilities.has_weight
strength = strength_pressure = True
# Only draw blend mode for the Draw tool, because for other tools it is pointless. D5928#137944
- if brush.weight_tool == 'DRAW':
+ if brush.weight_tool == "DRAW":
blend_mode = True
# UV Sculpt #
- if mode == 'UV_SCULPT':
+ if mode == "UV_SCULPT":
size = True
strength = True
# Sculpt Curves #
- if mode == 'SCULPT_CURVES':
+ if mode == "SCULPT_CURVES":
size = True
strength = True
- direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
+ direction = brush.curves_sculpt_tool in {"GROW_SHRINK", "SELECTION_PAINT"}
### Draw settings. ###
ups = context.scene.tool_settings.unified_paint_settings
@@ -877,10 +1611,19 @@ def brush_shared_settings(layout, context, brush, popover=False):
size_owner = ups if ups.use_unified_size else brush
size_prop = "size"
- if size_mode and (size_owner.use_locked_size == 'SCENE'):
+ if size_mode and (size_owner.use_locked_size == "SCENE"):
size_prop = "unprojected_radius"
if size or size_mode:
if size:
+ UnifiedPaintPanel.channel_unified(
+ layout,
+ context,
+ brush,
+ size_prop,
+ text="Radius",
+ slider=True
+ )
+ """
UnifiedPaintPanel.prop_unified(
layout,
context,
@@ -891,19 +1634,20 @@ def brush_shared_settings(layout, context, brush, popover=False):
text="Radius",
slider=True,
)
+ """
if size_mode:
layout.row().prop(size_owner, "use_locked_size", expand=True)
layout.separator()
if strength:
- pressure_name = "use_pressure_strength" if strength_pressure else None
- UnifiedPaintPanel.prop_unified(
+ #pressure_name = "use_pressure_strength" if strength_pressure else None
+ UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"strength",
- unified_name="use_unified_strength",
- pressure_name=pressure_name,
+ #unified_name="use_unified_strength",
+ #pressure_name=pressure_name,
slider=True,
)
layout.separator()
@@ -911,6 +1655,45 @@ def brush_shared_settings(layout, context, brush, popover=False):
if direction:
layout.row().prop(brush, "direction", expand=True)
+def get_ui_channels(channels, filterkeys=["show_in_workspace"]):
+ ret = []
+
+ for ch in channels:
+ ok = len(filterkeys) == 0
+ for key in filterkeys:
+ if getattr(ch, key):
+ ok = True
+ break
+ if ok:
+ ret.append(ch)
+
+ ret.sort(key=lambda x: x.ui_order)
+
+ return ret
+
+def brush_settings_channels(layout, context, brush, ui_editing=False, popover=False, show_reorder=None, filterkey="show_in_workspace",
+ parent="VIEW3D_PT_tools_brush_settings_channels", prefix="VIEW3D_PT_brush_category_"):
+ channels = get_ui_channels(brush.channels, [filterkey])
+
+ if show_reorder is None:
+ show_reorder = ui_editing
+
+ DynamicPaintPanelGen.ensureCategory("Basic", "Basic", parent=parent,
+ prefix=prefix, ui_editing=ui_editing,
+ show_reorder=show_reorder)
+
+ for ch in channels:
+ if len(ch.category) > 0:
+ DynamicPaintPanelGen.ensureCategory(ch.category, ch.category, parent=parent,
+ prefix=prefix, ui_editing=ui_editing,
+ show_reorder=show_reorder, show_mappings=True)
+ continue
+
+ # VIEW3D_PT_brush_category_edit_
+ UnifiedPaintPanel.channel_unified(layout.column(),
+ context,
+ brush,
+ ch.idname, show_reorder=show_reorder, expand=False, ui_editing=ui_editing, show_mappings=True)
def brush_settings_advanced(layout, context, brush, popover=False):
"""Draw advanced brush settings for Sculpt, Texture/Vertex/Weight Paint modes."""
@@ -1007,42 +1790,42 @@ def brush_settings_advanced(layout, context, brush, popover=False):
layout.separator()
# 3D and 2D Texture Paint.
- elif mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
+ elif mode in {"PAINT_TEXTURE", "PAINT_2D"}:
capabilities = brush.image_paint_capabilities
use_accumulate = capabilities.has_accumulate
- if mode == 'PAINT_2D':
+ if mode == "PAINT_2D":
layout.prop(brush, "use_paint_antialiasing")
else:
layout.prop(brush, "use_alpha")
# Tool specific settings
- if brush.image_tool == 'SOFTEN':
+ if brush.image_tool == "SOFTEN":
layout.separator()
layout.row().prop(brush, "direction", expand=True)
layout.prop(brush, "sharp_threshold")
- if mode == 'PAINT_2D':
+ if mode == "PAINT_2D":
layout.prop(brush, "blur_kernel_radius")
layout.prop(brush, "blur_mode")
- elif brush.image_tool == 'MASK':
+ elif brush.image_tool == "MASK":
layout.prop(brush, "weight", text="Mask Value", slider=True)
- elif brush.image_tool == 'CLONE':
- if mode == 'PAINT_2D':
+ elif brush.image_tool == "CLONE":
+ if mode == "PAINT_2D":
layout.prop(brush, "clone_image", text="Image")
layout.prop(brush, "clone_alpha", text="Alpha")
# Vertex Paint #
- elif mode == 'PAINT_VERTEX':
+ elif mode == "PAINT_VERTEX":
layout.prop(brush, "use_alpha")
- if brush.vertex_tool != 'SMEAR':
+ if brush.vertex_tool != "SMEAR":
use_accumulate = True
use_frontface = True
# Weight Paint
- elif mode == 'PAINT_WEIGHT':
- if brush.weight_tool != 'SMEAR':
+ elif mode == "PAINT_WEIGHT":
+ if brush.weight_tool != "SMEAR":
use_accumulate = True
use_frontface = True
@@ -1064,24 +1847,30 @@ def draw_color_settings(context, layout, brush, color_type=False):
row.prop(brush, "color_type", expand=True)
# Color wheel
- if brush.color_type == 'COLOR':
- UnifiedPaintPanel.prop_unified_color_picker(layout, context, brush, "color", value_slider=True)
+ if brush.color_type == "COLOR":
+ UnifiedPaintPanel.prop_unified_color_picker(
+ layout, context, brush, "color", value_slider=True
+ )
row = layout.row(align=True)
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
- UnifiedPaintPanel.prop_unified_color(row, context, brush, "secondary_color", text="")
+ UnifiedPaintPanel.prop_unified_color(
+ row, context, brush, "secondary_color", text=""
+ )
row.separator()
- row.operator("paint.brush_colors_flip", icon='FILE_REFRESH', text="", emboss=False)
- row.prop(ups, "use_unified_color", text="", icon='BRUSHES_ALL')
+ row.operator(
+ "paint.brush_colors_flip", icon="FILE_REFRESH", text="", emboss=False
+ )
+ row.prop(ups, "use_unified_color", text="", icon="BRUSHES_ALL")
# Gradient
- elif brush.color_type == 'GRADIENT':
+ elif brush.color_type == "GRADIENT":
layout.template_color_ramp(brush, "gradient", expand=True)
layout.use_property_split = True
col = layout.column()
- if brush.image_tool == 'DRAW':
+ if brush.image_tool == "DRAW":
UnifiedPaintPanel.prop_unified(
col,
context,
@@ -1093,7 +1882,7 @@ def draw_color_settings(context, layout, brush, color_type=False):
)
col.prop(brush, "gradient_stroke_mode", text="Gradient Mapping")
- if brush.gradient_stroke_mode in {'SPACING_REPEAT', 'SPACING_CLAMP'}:
+ if brush.gradient_stroke_mode in {"SPACING_REPEAT", "SPACING_CLAMP"}:
col.prop(brush, "grad_spacing")
@@ -1109,8 +1898,8 @@ def brush_texture_settings(layout, brush, sculpt):
layout.separator()
- if tex_slot.map_mode == 'STENCIL':
- if brush.texture and brush.texture.type == 'IMAGE':
+ if tex_slot.map_mode == "STENCIL":
+ if brush.texture and brush.texture.type == "IMAGE":
layout.operator("brush.stencil_fit_image_aspect")
layout.operator("brush.stencil_reset_transform")
@@ -1121,7 +1910,10 @@ def brush_texture_settings(layout, brush, sculpt):
if tex_slot.has_texture_angle_source:
col.prop(tex_slot, "use_rake", text="Rake")
- if brush.brush_capabilities.has_random_texture_angle and tex_slot.has_random_texture_angle:
+ if (
+ brush.brush_capabilities.has_random_texture_angle
+ and tex_slot.has_random_texture_angle
+ ):
if sculpt:
if brush.sculpt_capabilities.has_random_texture_angle:
col.prop(tex_slot, "use_random", text="Random")
@@ -1150,8 +1942,8 @@ def brush_mask_texture_settings(layout, brush):
# map_mode
layout.row().prop(mask_tex_slot, "mask_map_mode", text="Mask Mapping")
- if mask_tex_slot.map_mode == 'STENCIL':
- if brush.mask_texture and brush.mask_texture.type == 'IMAGE':
+ if mask_tex_slot.map_mode == "STENCIL":
+ if brush.mask_texture and brush.mask_texture.type == "IMAGE":
layout.operator("brush.stencil_fit_image_aspect").mask = True
layout.operator("brush.stencil_reset_transform").mask = True
@@ -1164,7 +1956,10 @@ def brush_mask_texture_settings(layout, brush):
if mask_tex_slot.has_texture_angle_source:
col.prop(mask_tex_slot, "use_rake", text="Rake")
- if brush.brush_capabilities.has_random_texture_angle and mask_tex_slot.has_random_texture_angle:
+ if (
+ brush.brush_capabilities.has_random_texture_angle
+ and mask_tex_slot.has_random_texture_angle
+ ):
col.prop(mask_tex_slot, "use_random", text="Random")
if mask_tex_slot.use_random:
col.prop(mask_tex_slot, "random_angle", text="Random Angle")
@@ -1182,7 +1977,9 @@ def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
row = layout.row(align=True)
row.ui_units_x = 4
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
- UnifiedPaintPanel.prop_unified_color(row, context, brush, "secondary_color", text="")
+ UnifiedPaintPanel.prop_unified_color(
+ row, context, brush, "secondary_color", text=""
+ )
row.separator()
layout.prop(brush, "blend", text="" if compact else "Blend")
@@ -1195,7 +1992,7 @@ def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
unified_name="use_unified_size",
slider=True,
text="Radius",
- header=True
+ header=True,
)
UnifiedPaintPanel.prop_unified(
layout,
@@ -1204,7 +2001,7 @@ def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
"strength",
pressure_name="use_pressure_strength",
unified_name="use_unified_strength",
- header=True
+ header=True,
)
@@ -1225,7 +2022,7 @@ def brush_basic__draw_color_selector(context, layout, brush, gp_settings, props)
txt_ma = ma.name
maxw = 25
if len(txt_ma) > maxw:
- txt_ma = txt_ma[:maxw - 5] + '..' + txt_ma[-3:]
+ txt_ma = txt_ma[: maxw - 5] + ".." + txt_ma[-3:]
sub = row.row(align=True)
sub.enabled = not gp_settings.use_material_pin
@@ -1238,20 +2035,37 @@ def brush_basic__draw_color_selector(context, layout, brush, gp_settings, props)
row.prop(gp_settings, "use_material_pin", text="")
- if brush.gpencil_tool in {'DRAW', 'FILL'}:
+ if brush.gpencil_tool in {"DRAW", "FILL"}:
row.separator(factor=1.0)
sub_row = row.row(align=True)
sub_row.enabled = not gp_settings.pin_draw_mode
if gp_settings.pin_draw_mode:
- sub_row.prop_enum(gp_settings, "brush_draw_mode", 'MATERIAL', text="", icon='MATERIAL')
- sub_row.prop_enum(gp_settings, "brush_draw_mode", 'VERTEXCOLOR', text="", icon='VPAINT_HLT')
+ sub_row.prop_enum(
+ gp_settings, "brush_draw_mode", "MATERIAL", text="", icon="MATERIAL"
+ )
+ sub_row.prop_enum(
+ gp_settings,
+ "brush_draw_mode",
+ "VERTEXCOLOR",
+ text="",
+ icon="VPAINT_HLT",
+ )
else:
- sub_row.prop_enum(settings, "color_mode", 'MATERIAL', text="", icon='MATERIAL')
- sub_row.prop_enum(settings, "color_mode", 'VERTEXCOLOR', text="", icon='VPAINT_HLT')
+ sub_row.prop_enum(
+ settings, "color_mode", "MATERIAL", text="", icon="MATERIAL"
+ )
+ sub_row.prop_enum(
+ settings, "color_mode", "VERTEXCOLOR", text="", icon="VPAINT_HLT"
+ )
sub_row = row.row(align=True)
- sub_row.enabled = settings.color_mode == 'VERTEXCOLOR' or gp_settings.brush_draw_mode == 'VERTEXCOLOR'
- sub_row.prop_with_popover(brush, "color", text="", panel="TOPBAR_PT_gpencil_vertexcolor")
+ sub_row.enabled = (
+ settings.color_mode == "VERTEXCOLOR"
+ or gp_settings.brush_draw_mode == "VERTEXCOLOR"
+ )
+ sub_row.prop_with_popover(
+ brush, "color", text="", panel="TOPBAR_PT_gpencil_vertexcolor"
+ )
row.prop(gp_settings, "pin_draw_mode", text="")
if props:
@@ -1268,19 +2082,21 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
return
# Brush details
- if brush.gpencil_tool == 'ERASE':
+ if brush.gpencil_tool == "ERASE":
row = layout.row(align=True)
row.prop(brush, "size", text="Radius")
- row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
- row.prop(gp_settings, "use_occlude_eraser", text="", icon='XRAY')
+ row.prop(gp_settings, "use_pressure", text="", icon="STYLUS_PRESSURE")
+ row.prop(gp_settings, "use_occlude_eraser", text="", icon="XRAY")
row.prop(gp_settings, "use_default_eraser", text="")
row = layout.row(align=True)
row.prop(gp_settings, "eraser_mode", expand=True)
- if gp_settings.eraser_mode == 'SOFT':
+ if gp_settings.eraser_mode == "SOFT":
row = layout.row(align=True)
row.prop(gp_settings, "pen_strength", slider=True)
- row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
+ row.prop(
+ gp_settings, "use_strength_pressure", text="", icon="STYLUS_PRESSURE"
+ )
row = layout.row(align=True)
row.prop(gp_settings, "eraser_strength_factor")
row = layout.row(align=True)
@@ -1290,7 +2106,7 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
row.prop(settings, "show_brush", text="Display Cursor")
# FIXME: tools must use their own UI drawing!
- elif brush.gpencil_tool == 'FILL':
+ elif brush.gpencil_tool == "FILL":
use_property_split_prev = layout.use_property_split
if compact:
row = layout.row(align=True)
@@ -1311,45 +2127,47 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
else: # brush.gpencil_tool == 'DRAW/TINT':
row = layout.row(align=True)
row.prop(brush, "size", text="Radius")
- row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
+ row.prop(gp_settings, "use_pressure", text="", icon="STYLUS_PRESSURE")
if gp_settings.use_pressure and not compact:
col = layout.column()
- col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True,
- use_negative_slope=True)
+ col.template_curve_mapping(
+ gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True
+ )
row = layout.row(align=True)
row.prop(gp_settings, "pen_strength", slider=True)
- row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
+ row.prop(gp_settings, "use_strength_pressure", text="", icon="STYLUS_PRESSURE")
if gp_settings.use_strength_pressure and not compact:
col = layout.column()
- col.template_curve_mapping(gp_settings, "curve_strength", brush=True,
- use_negative_slope=True)
+ col.template_curve_mapping(
+ gp_settings, "curve_strength", brush=True, use_negative_slope=True
+ )
- if brush.gpencil_tool == 'TINT':
+ if brush.gpencil_tool == "TINT":
row = layout.row(align=True)
row.prop(gp_settings, "vertex_mode", text="Mode")
else:
row = layout.row(align=True)
- if context.region.type == 'TOOL_HEADER':
+ if context.region.type == "TOOL_HEADER":
row.prop(gp_settings, "caps_type", text="", expand=True)
else:
row.prop(gp_settings, "caps_type", text="Caps Type")
# FIXME: tools must use their own UI drawing!
if tool.idname in {
- "builtin.arc",
- "builtin.curve",
- "builtin.line",
- "builtin.box",
- "builtin.circle",
- "builtin.polyline"
+ "builtin.arc",
+ "builtin.curve",
+ "builtin.line",
+ "builtin.box",
+ "builtin.circle",
+ "builtin.polyline",
}:
settings = context.tool_settings.gpencil_sculpt
if compact:
row = layout.row(align=True)
- row.prop(settings, "use_thickness_curve", text="", icon='SPHERECURVE')
+ row.prop(settings, "use_thickness_curve", text="", icon="SPHERECURVE")
sub = row.row(align=True)
sub.active = settings.use_thickness_curve
sub.popover(
@@ -1362,7 +2180,9 @@ def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False)
sub = row.row(align=True)
if settings.use_thickness_curve:
# Curve
- layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
+ layout.template_curve_mapping(
+ settings, "thickness_primitive_curve", brush=True
+ )
def brush_basic_gpencil_sculpt_settings(layout, _context, brush, *, compact=False):
@@ -1376,7 +2196,7 @@ def brush_basic_gpencil_sculpt_settings(layout, _context, brush, *, compact=Fals
row = layout.row(align=True)
row.prop(brush, "size", slider=True)
sub = row.row(align=True)
- sub.enabled = tool not in {'GRAB', 'CLONE'}
+ sub.enabled = tool not in {"GRAB", "CLONE"}
sub.prop(gp_settings, "use_pressure", text="")
row = layout.row(align=True)
@@ -1384,22 +2204,22 @@ def brush_basic_gpencil_sculpt_settings(layout, _context, brush, *, compact=Fals
row.prop(brush, "use_pressure_strength", text="")
if compact:
- if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
+ if tool in {"THICKNESS", "STRENGTH", "PINCH", "TWIST"}:
row.separator()
row.prop(gp_settings, "direction", expand=True, text="")
else:
use_property_split_prev = layout.use_property_split
layout.use_property_split = False
- if tool in {'THICKNESS', 'STRENGTH'}:
+ if tool in {"THICKNESS", "STRENGTH"}:
layout.row().prop(gp_settings, "direction", expand=True)
- elif tool == 'PINCH':
+ elif tool == "PINCH":
row = layout.row(align=True)
- row.prop_enum(gp_settings, "direction", value='ADD', text="Pinch")
- row.prop_enum(gp_settings, "direction", value='SUBTRACT', text="Inflate")
- elif tool == 'TWIST':
+ row.prop_enum(gp_settings, "direction", value="ADD", text="Pinch")
+ row.prop_enum(gp_settings, "direction", value="SUBTRACT", text="Inflate")
+ elif tool == "TWIST":
row = layout.row(align=True)
- row.prop_enum(gp_settings, "direction", value='ADD', text="CCW")
- row.prop_enum(gp_settings, "direction", value='SUBTRACT', text="CW")
+ row.prop_enum(gp_settings, "direction", value="ADD", text="CCW")
+ row.prop_enum(gp_settings, "direction", value="SUBTRACT", text="CW")
layout.use_property_split = use_property_split_prev
@@ -1419,23 +2239,22 @@ def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=Fals
# Brush details
row = layout.row(align=True)
row.prop(brush, "size", text="Radius")
- row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
+ row.prop(gp_settings, "use_pressure", text="", icon="STYLUS_PRESSURE")
- if brush.gpencil_vertex_tool in {'DRAW', 'BLUR', 'SMEAR'}:
+ if brush.gpencil_vertex_tool in {"DRAW", "BLUR", "SMEAR"}:
row = layout.row(align=True)
row.prop(gp_settings, "pen_strength", slider=True)
- row.prop(gp_settings, "use_strength_pressure", text="", icon='STYLUS_PRESSURE')
+ row.prop(gp_settings, "use_strength_pressure", text="", icon="STYLUS_PRESSURE")
- if brush.gpencil_vertex_tool in {'DRAW', 'REPLACE'}:
+ if brush.gpencil_vertex_tool in {"DRAW", "REPLACE"}:
row = layout.row(align=True)
row.prop(gp_settings, "vertex_mode", text="Mode")
-classes = (
- VIEW3D_MT_tools_projectpaint_clone,
-)
+classes = [VIEW3D_MT_tools_projectpaint_clone, BRUSH_PT_channel_panel]
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
+
for cls in classes:
register_class(cls)
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 39684aaf161..b16983c1492 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -8,6 +8,7 @@ from bpy.types import (
from bl_ui.properties_paint_common import (
UnifiedPaintPanel,
brush_basic_texpaint_settings,
+ brush_settings_channels,
)
from bl_ui.properties_grease_pencil_common import (
AnnotationDataPanel,
@@ -236,27 +237,38 @@ class _draw_tool_settings_context_mode:
if size_owner.use_locked_size == 'SCENE':
size = "unprojected_radius"
- UnifiedPaintPanel.prop_unified(
- layout,
- context,
- brush,
- size,
- pressure_name="use_pressure_size",
- unified_name="use_unified_size",
- text="Radius",
- slider=True,
- header=True,
- )
+ if 0:
+ UnifiedPaintPanel.prop_unified(
+ layout,
+ context,
+ brush,
+ size,
+ pressure_name="use_pressure_size",
+ unified_name="use_unified_size",
+ text="Radius",
+ slider=True,
+ header=True,
+ )
+ else:
+ UnifiedPaintPanel.channel_unified(
+ layout,
+ context,
+ brush,
+ size,
+ text="Radius",
+ slider=True,
+ header=True,
+ )
# strength, use_strength_pressure
pressure_name = "use_pressure_strength" if capabilities.has_strength_pressure else None
- UnifiedPaintPanel.prop_unified(
+ UnifiedPaintPanel.channel_unified(
layout,
context,
brush,
"strength",
- pressure_name=pressure_name,
- unified_name="use_unified_strength",
+ #pressure_name=pressure_name,
+ #unified_name="use_unified_strength",
text="Strength",
header=True,
)
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index fec156580cf..0157f01a0e5 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -20,6 +20,7 @@ from bl_ui.properties_paint_common import (
brush_settings,
brush_settings_advanced,
draw_color_settings,
+ brush_settings_channels,
)
from bl_ui.utils import PresetPanel
@@ -345,6 +346,29 @@ class VIEW3D_PT_tools_brush_select(Panel, View3DPaintBrushPanel, BrushSelectPane
bl_context = ".paint_common"
bl_label = "Brushes"
+class VIEW3D_PT_tools_brush_settings_channels(Panel, View3DPaintBrushPanel):
+ bl_context = ".paint_common"
+ bl_label = "Brush Settings"
+
+ @classmethod
+ def poll(cls, context):
+ settings = cls.paint_settings(context)
+
+ ok = settings and settings.brush is not None
+
+ return ok
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.use_property_split = True
+ layout.use_property_decorate = False # No animation.
+
+ settings = self.paint_settings(context)
+ brush = settings.brush
+
+ ui_editing = context.tool_settings.brush_editor_mode
+ brush_settings_channels(layout.column(), context, brush, popover=self.is_popover, ui_editing=ui_editing)
# TODO, move to space_view3d.py
class VIEW3D_PT_tools_brush_settings(Panel, View3DPaintBrushPanel):
@@ -365,6 +389,8 @@ class VIEW3D_PT_tools_brush_settings(Panel, View3DPaintBrushPanel):
settings = self.paint_settings(context)
brush = settings.brush
+ layout.prop(context.tool_settings, "brush_editor_mode")
+
brush_settings(layout.column(), context, brush, popover=self.is_popover)
@@ -421,6 +447,55 @@ class VIEW3D_PT_tools_brush_color(Panel, View3DPaintPanel):
draw_color_settings(context, layout, brush, color_type=not context.vertex_paint_object)
+class VIEW3D_PT_tools_persistent_base_channels(Panel, View3DPaintPanel):
+ bl_context = ".paint_common"
+ #bl_parent_id = "VIEW3D_PT_tools_brush_settings_channels"
+ bl_label = "Persistent Base"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ settings = cls.paint_settings(context)
+ if not settings:
+ return False
+
+ brush = settings.brush
+
+ ch = UnifiedPaintPanel.get_channel(context, brush, "use_persistent")
+
+ capabilities = brush.sculpt_capabilities
+
+ ok = context.mode == "SCULPT"
+ ok = ok and ch and ch.show_in_workspace
+ ok = ok or capabilities.has_persistence
+
+ return ok
+
+ def draw(self, context):
+ layout = self.layout
+ settings = self.paint_settings(context)
+ brush = settings.brush
+ sculpt = context.tool_settings.sculpt
+
+ UnifiedPaintPanel.channel_unified(layout,
+ context,
+ brush,
+ "use_persistent",
+ #text="Weight By Face Area",
+ ui_editing=False)
+
+ layout.operator("sculpt.set_persistent_base")
+
+ if sculpt.has_persistent_base():
+ layout.label(text="Persistent base exists")
+ else:
+ layout.label(text="No persisent base data")
+
+class VIEW3D_PT_tools_brush_swatches_channels(Panel, View3DPaintPanel, ColorPalettePanel):
+ bl_context = ".paint_common"
+ bl_parent_id = "VIEW3D_PT_tools_brush_settings_channels"
+ bl_label = "Color Palette"
+ bl_options = {'DEFAULT_CLOSED'}
class VIEW3D_PT_tools_brush_swatches(Panel, View3DPaintPanel, ColorPalettePanel):
bl_context = ".paint_common"
@@ -2449,6 +2524,9 @@ classes = (
VIEW3D_PT_tools_grease_pencil_brush_vertex_color,
VIEW3D_PT_tools_grease_pencil_brush_vertex_palette,
VIEW3D_PT_tools_grease_pencil_brush_vertex_falloff,
+ VIEW3D_PT_tools_brush_settings_channels,
+ VIEW3D_PT_tools_persistent_base_channels,
+ VIEW3D_PT_tools_brush_swatches_channels,
)
if __name__ == "__main__": # only for live edit.
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt
index 4dd596ad93a..607a6cfe508 100644
--- a/source/blender/CMakeLists.txt
+++ b/source/blender/CMakeLists.txt
@@ -88,6 +88,7 @@ set(SRC_DNA_INC
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_workspace_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_world_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_xr_types.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_brush_channel_types.h
)
set(SRC_DNA_DEFAULTS_INC
diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h
index a763b3d12c2..ed75a69b81f 100644
--- a/source/blender/blenkernel/BKE_brush.h
+++ b/source/blender/blenkernel/BKE_brush.h
@@ -99,7 +99,11 @@ float BKE_brush_curve_strength_clamped(const struct Brush *br, float p, float le
* Uses the brush curve control to find a strength value.
*/
float BKE_brush_curve_strength(const struct Brush *br, float p, float len);
-
+float BKE_brush_curve_strength_ex(int curve_preset,
+ const struct CurveMapping *curve,
+ float p,
+ const float len,
+ const bool invert);
/* Sampling. */
/**
@@ -168,7 +172,7 @@ void BKE_brush_weight_set(const struct Scene *scene, struct Brush *brush, float
bool BKE_brush_use_locked_size(const struct Scene *scene, const struct Brush *brush);
bool BKE_brush_use_alpha_pressure(const struct Brush *brush);
-bool BKE_brush_use_size_pressure(const struct Brush *brush);
+bool BKE_brush_use_size_pressure(const struct Scene *scene, const struct Brush *brush);
bool BKE_brush_sculpt_has_secondary_color(const struct Brush *brush);
diff --git a/source/blender/blenkernel/BKE_brush_channel.h b/source/blender/blenkernel/BKE_brush_channel.h
new file mode 100644
index 00000000000..65e16afd045
--- /dev/null
+++ b/source/blender/blenkernel/BKE_brush_channel.h
@@ -0,0 +1,330 @@
+#pragma once
+#pragma once
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ * \brief New brush engine for sculpt
+ */
+
+#include "BKE_paint.h"
+#include "RNA_types.h"
+
+/*
+The new brush engine is based on command lists. These lists
+will eventually be created by a node editor.
+
+Key is the concept of BrushChannels. A brush channel is
+a logical parameter with a type, input settings (e.g. pen),
+a falloff curve, etc.
+
+Brush channels have a concept of inheritance. There is a
+BrushChannelSet (collection of channels) in ToolSettings,
+and in Brush (Brush.channels and ToolSettings.unified_channels).
+Unified properties are stored in ToolSettings.unified_properties
+as IDProperties.
+
+Note: Many API functions start with an underscore. These functions
+support compile-time property name checking. This is done via macros;
+if you call the function without the underscore you'll go through a macro
+that will transform the property name into a global variable. If that
+global variable does not exist you'll get an error.
+
+For example `BKE_brush_channelset_lookup(chset, size)` compiles down to
+`_BKE_brush_channelset_lookup(chset, BRUSH_BUILTIN_size)`
+
+*/
+
+#include "BLI_compiler_compat.h"
+#include "DNA_brush_channel_types.h"
+#include "DNA_texture_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct BrushChannel;
+struct BlendWriter;
+struct StructRNA;
+struct BlendDataReader;
+struct BlendLibReader;
+struct ID;
+struct BlendExpander;
+struct Brush;
+struct Sculpt;
+struct LibraryForeachIDData;
+struct ToolSettings;
+struct UnifiedPaintSettings;
+
+#define make_builtin_ch_name(idname) BRUSH_BUILTIN_##idname
+
+typedef void (*BrushChannelIDCallback)(void *userdata,
+ struct ID *id,
+ BrushChannelSet *chset,
+ BrushChannel *ch);
+/* TODO: clean up this struct */
+typedef struct BrushMappingDef {
+ int curve;
+ bool enabled;
+ bool inv;
+ bool inherit;
+ float min, max;
+ int blendmode;
+ float func_cutoff;
+ float factor; // if 0, will default to 1.0
+ bool no_default;
+} BrushMappingDef;
+
+typedef struct BrushMappingPreset {
+ // must match order of BRUSH_MAPPING_XXX enums
+ struct BrushMappingDef pressure, xtilt, ytilt, angle, speed, random, stroke_t;
+} BrushMappingPreset;
+
+/* input mapping data */
+typedef struct BrushMappingData {
+ float pressure, xtilt, ytilt, angle, speed, random, stroke_t;
+} BrushMappingData;
+
+#define MAX_BRUSH_ENUM_DEF 32
+
+/* copy of PropertyEnumItem only with static char arrays instead of pointers
+ for strings */
+typedef struct BrushEnumDef {
+ int value;
+ const char identifier[64];
+ char icon[32]; // don't forget when writing literals that icon here is a string, not an int!
+ const char name[64];
+ const char description[512];
+} BrushEnumDef;
+
+typedef struct BrushUIFlagDef {
+ int tools[100];
+} BrushUIFlagDef;
+
+/*
+ Defines a brush channel. Includes limits, UI data,
+ default values, etc.
+*/
+typedef struct BrushChannelType {
+ char uiname[128], idname[64], tooltip[512], category[128];
+ float min, max, soft_min, soft_max;
+ BrushMappingPreset mappings;
+
+ BrushUIFlagDef paint_mode_uiflags[PAINT_MODE_INVALID];
+
+ int type, flag, ui_flag;
+ int subtype;
+
+ bool user_defined; /* this is a user-defined channel; currently unused */
+} BrushChannelType;
+
+BrushChannelSet *BKE_brush_channelset_create();
+void BKE_brush_channelset_free(BrushChannelSet *chset);
+void BKE_brush_channelset_ensure_channels(BrushChannelSet *chset, ePaintMode mode, int tool);
+
+/* Calls BKE_brush_channelset_ensure_channels for every paint mode with a tool inside of brush. */
+void BKE_brush_channelset_ensure_all_modes(struct Brush *brush);
+
+void BKE_brush_channelset_blend_read(BrushChannelSet *chset, struct BlendDataReader *reader);
+void BKE_brush_channelset_blend_write(BrushChannelSet *chset, struct BlendWriter *writer);
+/*
+set up static type checker for BKE_brush_channel_XXX name-checking macros
+*/
+#define BRUSH_CHANNEL_DEFINE_EXTERNAL
+#include "intern/brush_channel_define.h"
+#undef BRUSH_CHANNEL_DEFINE_EXTERNAL
+
+/* Remember that calling these without the leading underscore and with no
+ * quotes around idname will perform compile-time name checking.
+ */
+BrushChannel *_BKE_brush_channelset_ensure(BrushChannelSet *chset, const char *idname);
+BrushChannel *_BKE_brush_channelset_lookup(BrushChannelSet *chset, const char *idname);
+
+bool _BKE_brush_channelset_has(BrushChannelSet *chset, const char *idname);
+
+/* Flags all channels with BRUSH_CHANNEL_NEEDS_EVALUATE so we
+ reevaluate values from RNA */
+void BKE_brush_channelset_begin(BrushChannelSet *chset, BrushChannelType *type);
+
+/* Evaluates a channel, taking unified channel inheritance into account. Result
+ * is cached in channel, to force update call BKE_brush_channelset_mark_update. */
+float _BKE_brush_eval_float(const struct Brush *br,
+ const struct Scene *scene,
+ const char *idname,
+ BrushMappingData *mapping);
+int _BKE_brush_eval_int(const struct Brush *br,
+ const struct Scene *scene,
+ const char *idname,
+ BrushMappingData *mapping);
+
+/* Get and set internal cached values in brush channels. */
+float _BKE_brush_channelset_float_get(BrushChannelSet *chset, const char *idname);
+void _BKE_brush_channelset_float_set(BrushChannelSet *chset, const char *idname, float f);
+int _BKE_brush_channelset_int_get(BrushChannelSet *chset, const char *idname);
+void _BKE_brush_channelset_int_set(BrushChannelSet *chset, const char *idname, int i);
+
+/* Get and set channels' real values from RNA. ID can be either a Brush or a Scene.
+ * If a Scene the unified properties in Scene.toolsettings->unified_properties
+ * will be used.
+ */
+int _BKE_brush_int_get(const struct ID *id, BrushChannelSet *chset, const char *idname);
+void _BKE_brush_int_set(struct ID *id, BrushChannelSet *chset, const char *idname, int i);
+float _BKE_brush_float_get(const struct ID *id, BrushChannelSet *chset, const char *idname);
+void _BKE_brush_float_set(struct ID *id, BrushChannelSet *chset, const char *idname, float f);
+
+int _BKE_brush_int_get_unified(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname);
+void _BKE_brush_int_set_unified(struct Scene *scene,
+ struct Brush *brush,
+ const char *idname,
+ int i);
+float _BKE_brush_float_get_unified(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname);
+void _BKE_brush_float_set_unified(struct Scene *scene,
+ struct Brush *brush,
+ const char *idname,
+ float i);
+
+BrushChannelSet *BKE_brush_channelset_copy(BrushChannelSet *chset);
+
+/* Create a (copied) final brush channel set with all inheritance and unified flags
+ and input mappings taken into account. */
+BrushChannelSet *BKE_brush_channelset_create_final(const struct Brush *brush,
+ const struct Scene *scene,
+ BrushMappingData *mapdata);
+
+BLI_INLINE const char *BKE_brush_mapping_type_to_typename(eBrushMappingType type)
+{
+ switch (type) {
+ case BRUSH_MAPPING_PRESSURE:
+ return "PRESSURE";
+ case BRUSH_MAPPING_ANGLE:
+ return "ANGLE";
+ case BRUSH_MAPPING_SPEED:
+ return "SPEED";
+ case BRUSH_MAPPING_XTILT:
+ return "XTILT";
+ case BRUSH_MAPPING_YTILT:
+ return "YTILT";
+ case BRUSH_MAPPING_RANDOM:
+ return "RANDOM";
+ case BRUSH_MAPPING_STROKE_T:
+ return "DISTANCE";
+ default:
+ return "Error";
+ }
+}
+
+const char *BKE_brush_channel_category_get(BrushChannel *ch);
+const void BKE_brush_channel_category_set(BrushChannel *ch, const char *category);
+bool BKE_brush_channel_inherits(const struct Brush *brush,
+ const struct ToolSettings *tool_settings,
+ BrushChannel *ch);
+BrushChannelSet *BKE_brush_channelset_get_final(const struct Brush *brush,
+ const struct ToolSettings *tool_settings);
+
+void BKE_brush_channelset_toolsettings_init(struct ToolSettings *ts);
+
+/* Get rna path for brush channel. Calling code should call MEM_SAFE_FREE on result. */
+char *BKE_brush_channel_rna_path(const ID *owner, const BrushChannel *ch);
+
+void _BKE_brush_channelset_mark_update(BrushChannelSet *chset, const char *idname);
+#define BKE_brush_channelset_mark_update(chset, idname) \
+ _BKE_brush_channelset_mark_update(chset, make_builtin_ch_name(idname))
+
+/* Ensure BrushChannel.ui_order for all the channels inside chset are rational, i.e.
+ * they go from 0 to chset->channels_num-1.
+ */
+
+void BKE_brush_channelset_ui_order_check(BrushChannelSet *chset);
+void BKE_brush_channelset_ui_order_move(BrushChannelSet *chset,
+ BrushChannel *ch,
+ int uiflag,
+ int dir);
+
+bool _BKE_brush_mapping_enabled(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname,
+ eBrushMappingType mapping_type);
+
+#define BKE_brush_mapping_enabled(scene, brush, idname, mapping_type) \
+ _BKE_brush_mapping_enabled(scene, brush, make_builtin_ch_name(idname), mapping_type)
+
+#if 0
+/* Call when active brush changes. */
+void BKE_brush_channels_update(struct Brush *active_brush, struct Scene *scene);
+#endif
+
+#define BKE_brush_channelset_ensure(chset, idname) \
+ _BKE_brush_channelset_ensure(chset, make_builtin_ch_name(idname))
+#define BKE_brush_channelset_lookup(chset, channel) \
+ _BKE_brush_channelset_lookup(chset, make_builtin_ch_name(channel))
+#define BKE_brush_channelset_has(chset, channel) \
+ _BKE_brush_channelset_has(chset, make_builtin_ch_name(channel))
+
+#define BKE_brush_int_set_unified(scene, brush, idname, i) \
+ _BKE_brush_int_set_unified(scene, brush, make_builtin_ch_name(idname), i)
+#define BKE_brush_int_get_unified(scene, brush, idname) \
+ _BKE_brush_int_get_unified(scene, brush, make_builtin_ch_name(idname))
+#define BKE_brush_float_set_unified(scene, brush, idname, f) \
+ _BKE_brush_float_set_unified(scene, brush, make_builtin_ch_name(idname), f)
+#define BKE_brush_float_get_unified(scene, brush, idname) \
+ _BKE_brush_float_get_unified(scene, brush, make_builtin_ch_name(idname))
+
+#define BKE_brush_eval_float(br, scene, channel, mapdata) \
+ _BKE_brush_eval_float(br, scene, make_builtin_ch_name(channel), mapdata)
+#define BKE_brush_eval_int(br, scene, channel, mapdata) \
+ _BKE_brush_eval_int(br, scene, make_builtin_ch_name(channel), mapdata)
+
+#define BKE_brush_channelset_float_get(chset, idname) \
+ _BKE_brush_channelset_float_get(chset, make_builtin_ch_name(idname))
+#define BKE_brush_channelset_float_set(chset, idname, f) \
+ _BKE_brush_channelset_float_set(chset, make_builtin_ch_name(idname), f)
+#define BKE_brush_channelset_int_get(chset, idname) \
+ _BKE_brush_channelset_int_get(chset, make_builtin_ch_name(idname))
+#define BKE_brush_channelset_int_set(chset, idname, f) \
+ _BKE_brush_channelset_int_set(chset, make_builtin_ch_name(idname), f)
+
+#define BKE_brush_int_get(id, chset, idname) \
+ _BKE_brush_int_get(id, chset, make_builtin_ch_name(idname))
+#define BKE_brush_int_set(id, chset, idname, f) \
+ _BKE_brush_int_set(id, chset, make_builtin_ch_name(idname), f)
+#define BKE_brush_float_get(id, chset, idname) \
+ _BKE_brush_float_get(id, chset, make_builtin_ch_name(idname))
+#define BKE_brush_float_set(id, chset, idname, f) \
+ _BKE_brush_float_set(id, chset, make_builtin_ch_name(idname), f)
+
+/* Disable optimization for a function (for debugging use only!)*/
+#ifdef __clang__
+# define ATTR_NO_OPT __attribute__((optnone))
+#elif defined(_MSC_VER)
+# define ATTR_NO_OPT __pragma(optimize("", off))
+#elif defined(__GNUC__)
+# define ATTR_NO_OPT __attribute__((optimize("O0")))
+#else
+# define ATTR_NO_OPT
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 7d43fa7e6af..784762a3f64 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -85,6 +85,7 @@ set(SRC
intern/blender_user_menu.c
intern/blendfile.c
intern/blendfile_link_append.c
+ intern/brush_channel.cc
intern/boids.c
intern/bpath.c
intern/brush.cc
@@ -338,6 +339,7 @@ set(SRC
BKE_boids.h
BKE_bpath.h
BKE_brush.h
+ BKE_brush_channel.h
BKE_bvhutils.h
BKE_cachefile.h
BKE_callbacks.h
@@ -496,6 +498,7 @@ set(SRC
nla_private.h
particle_private.h
tracking_private.h
+ intern/brush_channel_define.h
intern/CCGSubSurf.h
intern/CCGSubSurf_inline.h
intern/CCGSubSurf_intern.h
diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc
index 6f1435e90b8..30c9137629b 100644
--- a/source/blender/blenkernel/intern/brush.cc
+++ b/source/blender/blenkernel/intern/brush.cc
@@ -21,6 +21,7 @@
#include "BKE_bpath.h"
#include "BKE_brush.h"
+#include "BKE_brush_channel.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
@@ -34,6 +35,11 @@
#include "BKE_paint.h"
#include "BKE_texture.h"
+#include "RNA_access.h"
+#include "RNA_path.h"
+#include "RNA_prototypes.h"
+#include "RNA_types.h"
+
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@@ -54,6 +60,9 @@ static void brush_init_data(ID *id)
/* the default alpha falloff curve */
BKE_brush_curve_preset(brush, CURVE_PRESET_SMOOTH);
+
+ brush->channels = BKE_brush_channelset_create();
+ BKE_brush_channelset_ensure_channels(brush->channels, PAINT_MODE_INVALID, 0);
}
static void brush_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, const int flag)
@@ -100,6 +109,10 @@ static void brush_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, cons
brush_dst->curves_sculpt_settings = MEM_cnew(__func__, *(brush_src->curves_sculpt_settings));
}
+ if (brush_src->channels) {
+ brush_dst->channels = BKE_brush_channelset_copy(brush_src->channels);
+ }
+
/* enable fake user by default */
id_fake_user_set(&brush_dst->id);
}
@@ -134,6 +147,10 @@ static void brush_free_data(ID *id)
MEM_SAFE_FREE(brush->gradient);
BKE_previewimg_free(&(brush->preview));
+
+ if (brush->channels) {
+ BKE_brush_channelset_free(brush->channels);
+ }
}
static void brush_make_local(Main *bmain, ID *id, const int flags)
@@ -256,6 +273,10 @@ static void brush_blend_write(BlendWriter *writer, ID *id, const void *id_addres
if (brush->gradient) {
BLO_write_struct(writer, ColorBand, brush->gradient);
}
+
+ if (brush->channels) {
+ BKE_brush_channelset_blend_write(brush->channels, writer);
+ }
}
static void brush_blend_read_data(BlendDataReader *reader, ID *id)
@@ -337,6 +358,14 @@ static void brush_blend_read_data(BlendDataReader *reader, ID *id)
brush->preview = nullptr;
brush->icon_imbuf = nullptr;
+
+ BLO_read_data_address(reader, &brush->channels);
+
+ if (brush->channels) {
+ BKE_brush_channelset_blend_read(brush->channels, reader);
+ }
+
+ BKE_brush_channelset_ensure_all_modes(brush);
}
static void brush_blend_read_lib(BlendLibReader *reader, ID *id)
@@ -1707,6 +1736,14 @@ void BKE_brush_sculpt_reset(Brush *br)
brush_defaults(br);
BKE_brush_curve_preset(br, CURVE_PRESET_SMOOTH);
+ /* Reset brush channels */
+ if (br->channels) {
+ BKE_brush_channelset_free(br->channels);
+ }
+
+ br->channels = BKE_brush_channelset_create();
+ BKE_brush_channelset_ensure_channels(br->channels, PAINT_MODE_SCULPT, br->sculpt_tool);
+
/* Use the curve presets by default */
br->curve_preset = BRUSH_CURVE_SMOOTH;
@@ -2269,6 +2306,9 @@ void BKE_brush_color_set(struct Scene *scene, struct Brush *brush, const float c
void BKE_brush_size_set(Scene *scene, Brush *brush, int size)
{
+ BKE_brush_int_set_unified(scene, brush, size, size);
+
+#if 0
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
/* make sure range is sane */
@@ -2280,14 +2320,20 @@ void BKE_brush_size_set(Scene *scene, Brush *brush, int size)
else {
brush->size = size;
}
+#endif
}
int BKE_brush_size_get(const Scene *scene, const Brush *brush)
{
+ return BKE_brush_int_get_unified(scene, brush, size);
+ /* theoretically faster: return BKE_brush_eval_int(brush, scene, size, NULL); */
+
+#if 0
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
int size = (ups->flag & UNIFIED_PAINT_SIZE) ? ups->size : brush->size;
return size;
+#endif
}
bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush)
@@ -2298,9 +2344,16 @@ bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush)
(brush->flag & BRUSH_LOCK_SIZE);
}
-bool BKE_brush_use_size_pressure(const Brush *brush)
+bool BKE_brush_use_size_pressure(const Scene *scene, const Brush *brush)
{
- return brush->flag & BRUSH_SIZE_PRESSURE;
+ if (!BKE_brush_use_locked_size(scene, brush)) {
+ return BKE_brush_mapping_enabled(scene, brush, size, BRUSH_MAPPING_PRESSURE);
+ }
+ else {
+ return BKE_brush_mapping_enabled(scene, brush, unprojected_radius, BRUSH_MAPPING_PRESSURE);
+ }
+
+ // return brush->flag & BRUSH_SIZE_PRESSURE;
}
bool BKE_brush_use_alpha_pressure(const Brush *brush)
@@ -2348,21 +2401,27 @@ float BKE_brush_unprojected_radius_get(const Scene *scene, const Brush *brush)
void BKE_brush_alpha_set(Scene *scene, Brush *brush, float alpha)
{
- UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
+ BKE_brush_float_set_unified(scene, brush, strength, alpha);
+#if 0
+ UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
if (ups->flag & UNIFIED_PAINT_ALPHA) {
ups->alpha = alpha;
}
else {
brush->alpha = alpha;
}
+#endif
}
float BKE_brush_alpha_get(const Scene *scene, const Brush *brush)
{
+ return BKE_brush_float_get_unified(scene, brush, strength);
+#if 0
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
return (ups->flag & UNIFIED_PAINT_ALPHA) ? ups->alpha : brush->alpha;
+#endif
}
float BKE_brush_weight_get(const Scene *scene, const Brush *brush)
@@ -2558,3 +2617,54 @@ struct ImBuf *BKE_brush_gen_radial_control_imbuf(Brush *br, bool secondary, bool
return im;
}
+
+float BKE_brush_curve_strength_ex(
+ int curve_preset, const CurveMapping *curve, float p, const float len, const bool invert)
+{
+ float strength = 1.0f;
+
+ if (p >= len) {
+ return invert ? 0.0f : 1.0f;
+ }
+
+ p = p / len;
+
+ if (invert) {
+ p = 1.0f - p;
+ }
+
+ switch (curve_preset) {
+ case BRUSH_CURVE_CUSTOM:
+ strength = BKE_curvemapping_evaluateF(curve, 0, p);
+ break;
+ case BRUSH_CURVE_SHARP:
+ strength = p * p;
+ break;
+ case BRUSH_CURVE_SMOOTH:
+ strength = 3.0f * p * p - 2.0f * p * p * p;
+ break;
+ case BRUSH_CURVE_SMOOTHER:
+ strength = pow3f(p) * (p * (p * 6.0f - 15.0f) + 10.0f);
+ break;
+ case BRUSH_CURVE_ROOT:
+ strength = sqrtf(p);
+ break;
+ case BRUSH_CURVE_LIN:
+ strength = p;
+ break;
+ case BRUSH_CURVE_CONSTANT:
+ strength = 1.0f;
+ break;
+ case BRUSH_CURVE_SPHERE:
+ strength = sqrtf(2 * p - p * p);
+ break;
+ case BRUSH_CURVE_POW4:
+ strength = p * p * p * p;
+ break;
+ case BRUSH_CURVE_INVSQUARE:
+ strength = p * (2.0f - p);
+ break;
+ }
+
+ return strength;
+}
diff --git a/source/blender/blenkernel/intern/brush_channel.cc b/source/blender/blenkernel/intern/brush_channel.cc
new file mode 100644
index 00000000000..35fd62ba34d
--- /dev/null
+++ b/source/blender/blenkernel/intern/brush_channel.cc
@@ -0,0 +1,1492 @@
+#include "MEM_guardedalloc.h"
+
+#include "BLI_assert.h"
+#include "BLI_compiler_attrs.h"
+#include "BLI_compiler_compat.h"
+#include "BLI_ghash.h"
+#include "BLI_index_range.hh"
+#include "BLI_listbase.h"
+#include "BLI_map.hh"
+#include "BLI_math.h"
+#include "BLI_math_vec_types.hh"
+#include "BLI_string_ref.hh"
+#include "BLI_string_utils.h"
+#include "BLI_vector.hh"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_path.h"
+#include "RNA_prototypes.h"
+
+#include "DNA_brush_channel_types.h"
+#include "DNA_brush_enums.h"
+#include "DNA_brush_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "DNA_color_types.h"
+#include "DNA_material_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_brush_channel.h"
+#include "BKE_colortools.h"
+#include "BKE_idprop.h"
+#include "BKE_idprop.hh"
+#include "BKE_lib_id.h"
+#include "BKE_paint.h"
+#include "BKE_pbvh.h"
+#include "BLO_read_write.h"
+
+#include <string>
+#include <vector>
+
+const char builtin_brush_categories[][128] = {"Basic", "Smooth", "Color"};
+
+static BrushChannelType empty_brush_type = {"error", "error", "error", "error"};
+
+#define BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES
+#include "brush_channel_define.h"
+#undef BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES
+
+using string = std::string;
+
+using KeyString = const char *;
+using blender::float3;
+using blender::float4;
+using blender::IndexRange;
+using blender::Map;
+using blender::StringRef;
+using blender::Vector;
+
+static Map<StringRef, BrushChannelType> builtin_channels;
+
+using ChannelNameMap = Map<StringRef, BrushChannel *>;
+
+ATTR_NO_OPT static void init_builtin_brush_channels()
+{
+ struct UI {
+ int mode;
+ Vector<int> tools;
+ int uiflag;
+ bool all_tool_modes = false;
+ bool all_tools = false;
+ bool exists = true;
+
+ UI(const UI &b)
+ {
+ tools = b.tools;
+ uiflag = b.uiflag;
+ all_tool_modes = b.all_tool_modes;
+ all_tools = b.all_tools;
+ exists = b.exists;
+ mode = b.mode;
+ }
+
+ UI(int _mode, int _uiflag, int _tool) : mode(_mode), uiflag(_uiflag)
+ {
+ tools.append(_tool);
+ }
+
+ UI(int _uiflag) : uiflag(_uiflag)
+ {
+ all_tool_modes = all_tools = true;
+ }
+
+ UI(ePaintMode _mode, int _uiflag) : mode((int)_mode), uiflag(_uiflag)
+ {
+ all_tools = true;
+ }
+
+ UI(ePaintMode mode, int uiflag, int tool)
+ {
+ UI::UI((int)mode, uiflag, tool);
+ }
+
+ UI(ePaintMode _mode, int _uiflag, Vector<int> _tools)
+ : mode((int)_mode), tools(_tools), uiflag(_uiflag)
+ {
+ }
+ };
+
+ struct ChannelProp {
+ char path[512];
+ char category[64];
+ Vector<UI> extra_uiflags;
+ int flag;
+ BrushMappingPreset mappings;
+ };
+
+#ifdef BRUSH_CHANNEL_DEFINE_EXTERNAL
+# undef BRUSH_CHANNEL_DEFINE_EXTERNAL
+#endif
+
+#ifdef BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES
+# undef BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES
+#endif
+#ifdef MAKE_PROP
+# undef MAKE_PROP
+#endif
+#ifdef MAKE_PROP_EX
+# undef MAKE_PROP_EX
+#endif
+
+ //#define MAKE_PROP(idname, category, uiflags) MAKE_PROP_EX(idname, category, uiflags, 0)
+
+ //#define SHOW_CONTEXT BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU
+ //#define SHOW_WORKSPACE BRUSH_CHANNEL_SHOW_IN_WORKSPACE
+
+#define BRUSH_CHANNEL_DEFINE_INTERNAL
+ ChannelProp channel_props[] = {
+#include "brush_channel_define.h"
+ };
+
+#undef SHOW_CONTEXT
+#undef SHOW_WORKSPACE
+
+ StructRNA *srna = RNA_struct_find("Brush");
+
+ Brush dummy = {0};
+ PointerRNA _ptr = {&dummy.id}, *ptr = &_ptr;
+
+ printf("total properties: %i\n", (int)ARRAY_SIZE(channel_props));
+
+ for (int i : IndexRange(ARRAY_SIZE(channel_props))) {
+ ChannelProp &def = channel_props[i];
+ BrushChannelType type = {0};
+
+ BLI_strncpy(type.idname, def.path, sizeof(type.idname));
+ BLI_strncpy(type.category, def.category, sizeof(type.category));
+ type.flag = def.flag;
+
+ for (auto &ui : def.extra_uiflags) {
+ for (int mode = 0; mode < (int)PAINT_MODE_INVALID; mode++) {
+ if (mode != ui.mode && !ui.all_tool_modes) {
+ continue;
+ }
+
+ int uiflag = ui.uiflag ? ui.uiflag : -1;
+
+ if (ui.all_tools) {
+ for (int j = 0; j < ARRAY_SIZE(type.paint_mode_uiflags); j++) {
+ type.paint_mode_uiflags[mode].tools[j] = uiflag;
+ }
+ }
+ else {
+ for (auto tool : ui.tools) {
+ type.paint_mode_uiflags[mode].tools[tool] = uiflag;
+ }
+ }
+ }
+ }
+
+ PropertyRNA *prop = RNA_struct_type_find_property(srna, def.path);
+ BLI_assert(prop);
+
+ if (!prop) {
+ printf("%s: Missing property %s\n", __func__, def.path);
+ continue;
+ }
+
+ PropertyType prop_type = RNA_property_type(prop);
+ PropertySubType prop_subtype = RNA_property_subtype(prop);
+
+ const char *uiname = RNA_property_ui_name(prop);
+ BLI_strncpy(type.uiname, uiname, sizeof(type.uiname));
+
+ type.min = 0.0;
+ type.max = 1.0;
+ type.soft_min = 0.0;
+ type.soft_max = 1.0;
+
+ switch (prop_type) {
+ case PROP_BOOLEAN:
+ type.type = BRUSH_CHANNEL_TYPE_BOOL;
+ break;
+ case PROP_INT: {
+ int min, max, soft_min, soft_max;
+ int step;
+
+ RNA_property_int_range(nullptr, prop, &min, &max);
+ RNA_property_int_ui_range(nullptr, prop, &soft_min, &soft_max, &step);
+
+ type.min = (float)min;
+ type.max = (float)max;
+ type.soft_min = (float)soft_min;
+ type.soft_max = (float)soft_max;
+ type.type = BRUSH_CHANNEL_TYPE_INT;
+ break;
+ }
+ case PROP_FLOAT: {
+ float precision, step;
+
+ RNA_property_float_range(ptr, prop, &type.min, &type.max);
+ RNA_property_float_ui_range(ptr, prop, &type.soft_min, &type.soft_max, &step, &precision);
+
+ if (!RNA_property_array_check(prop)) {
+ type.type = BRUSH_CHANNEL_TYPE_FLOAT;
+ }
+ else {
+ int dimen = RNA_property_array_length(ptr, prop);
+
+ switch (dimen) {
+ case 3:
+ type.type = BRUSH_CHANNEL_TYPE_VEC3;
+ break;
+ case 4:
+ type.type = BRUSH_CHANNEL_TYPE_VEC4;
+ break;
+ default:
+ BLI_assert_unreachable();
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ switch (prop_subtype) {
+ case PROP_COLOR:
+ case PROP_COLOR_GAMMA:
+ type.subtype = BRUSH_CHANNEL_COLOR;
+ break;
+ case PROP_FACTOR:
+ type.subtype = BRUSH_CHANNEL_FACTOR;
+ break;
+ case PROP_PERCENTAGE:
+ type.subtype = BRUSH_CHANNEL_PERCENT;
+ break;
+ case PROP_ANGLE:
+ type.subtype = BRUSH_CHANNEL_ANGLE;
+ break;
+ case PROP_PIXEL:
+ type.subtype = BRUSH_CHANNEL_PIXEL;
+ break;
+ default:
+ break;
+ }
+
+ builtin_channels.add(strdup(type.idname), type);
+ }
+#undef BRUSH_CHANNEL_DEFINE_INTERNAL
+}
+static void check_builtin_brush_channels()
+{
+ if (builtin_channels.size() == 0) {
+ init_builtin_brush_channels();
+ }
+}
+
+ATTR_NO_OPT ChannelNameMap *get_namemap(BrushChannelSet *chset)
+{
+ return reinterpret_cast<ChannelNameMap *>(chset->channelmap);
+}
+
+BrushChannelSet *BKE_brush_channelset_create()
+{
+ BrushChannelSet *chset = MEM_cnew<BrushChannelSet>("BrushChannelSet");
+
+ chset->channels.first = chset->channels.last = nullptr;
+ chset->channels_num = 0;
+
+ ChannelNameMap *map = MEM_new<ChannelNameMap>("ChannelNameMap");
+ chset->channelmap = static_cast<void *>(map);
+
+ return chset;
+}
+
+void BKE_brush_channel_free_data(BrushChannel *ch)
+{
+ MEM_SAFE_FREE(ch->category);
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+
+ if (mp->curve.curve) {
+ BKE_curvemapping_free(mp->curve.curve);
+ }
+ }
+
+ if (ch->curve.curve) {
+ BKE_curvemapping_free(ch->curve.curve);
+ }
+}
+
+void BKE_brush_channelset_free(BrushChannelSet *chset)
+{
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ BKE_brush_channel_free_data(ch);
+ }
+
+ BLI_freelistN(&chset->channels);
+
+ MEM_delete<ChannelNameMap>(get_namemap(chset));
+ MEM_freeN(static_cast<void *>(chset));
+}
+
+static void brush_mapping_reset(BrushMapping *mp, int type)
+{
+ mp->type = type;
+ mp->premultiply_factor = 1.0f;
+ mp->min = 0.0f;
+ mp->max = 1.0f;
+ mp->factor = 1.0f;
+ mp->blendmode = MA_RAMP_MULT;
+ mp->curve.preset = BRUSH_CURVE_LIN;
+}
+
+BrushChannel *BKE_brush_channelset_add(BrushChannelSet *chset, BrushChannelType *type)
+{
+ BrushChannel *ch = MEM_cnew<BrushChannel>("BrushChannel");
+
+ BLI_strncpy(ch->idname, type->idname, sizeof(ch->idname));
+ BLI_strncpy(ch->uiname, type->uiname, sizeof(ch->uiname));
+
+ ch->def = type;
+ ch->type = type->type;
+ ch->flag = type->flag;
+
+ ch->ui_order = chset->channels_num;
+
+ BLI_addtail(&chset->channels, static_cast<void *>(ch));
+ get_namemap(chset)->add(ch->idname, ch);
+ chset->channels_num++;
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+ brush_mapping_reset(mp, i);
+ }
+
+ BKE_brush_channelset_ui_order_check(chset);
+
+ return ch;
+}
+
+void BKE_brush_channelset_begin(BrushChannelSet *chset, BrushChannelType *type)
+{
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ ch->flag |= BRUSH_CHANNEL_NEEDS_EVALUATE;
+ }
+}
+
+ATTR_NO_OPT BrushChannel *_BKE_brush_channelset_lookup(BrushChannelSet *chset, const char *idname)
+{
+ ChannelNameMap *namemap = get_namemap(chset);
+
+ return namemap->lookup(StringRef(idname));
+}
+
+bool _BKE_brush_channelset_has(BrushChannelSet *chset, const char *idname)
+{
+ return get_namemap(chset)->contains(idname);
+}
+
+const char *BKE_brush_channel_category_get(BrushChannel *ch)
+{
+ return ch->category ? ch->category : ch->def->category;
+}
+
+const void BKE_brush_channel_category_set(BrushChannel *ch, const char *category)
+{
+ if (STREQ(category, ch->def->category)) {
+ MEM_SAFE_FREE(ch->category);
+ ch->category = nullptr;
+
+ return;
+ }
+
+ ch->category = BLI_strdup(category);
+}
+
+ATTR_NO_OPT BrushChannel *_BKE_brush_channelset_ensure(BrushChannelSet *chset, const char *idname)
+{
+ if (!builtin_channels.contains(idname)) {
+ printf("channel types:\n");
+ for (StringRef key : builtin_channels.keys()) {
+ printf(" %s\n", key.data());
+ }
+
+ printf("Unknown brush channel %s\n", idname);
+ return nullptr;
+ }
+
+ ChannelNameMap *namemap = get_namemap(chset);
+ BrushChannelType &type = builtin_channels.lookup(idname);
+
+ if (!namemap->contains(type.idname)) {
+ BKE_brush_channelset_add(chset, &type);
+ }
+
+ return _BKE_brush_channelset_lookup(chset, idname);
+}
+
+void BKE_brush_channelset_ensure_all_modes(Brush *brush)
+{
+ if (!brush->channels) {
+ brush->channels = BKE_brush_channelset_create();
+ BKE_brush_channelset_ensure_channels(brush->channels, PAINT_MODE_INVALID, 0);
+ }
+
+ if (brush->sculpt_tool) {
+ BKE_brush_channelset_ensure_channels(brush->channels, PAINT_MODE_SCULPT, brush->sculpt_tool);
+ }
+
+ if (brush->vertexpaint_tool) {
+ BKE_brush_channelset_ensure_channels(
+ brush->channels, PAINT_MODE_VERTEX, brush->vertexpaint_tool);
+ }
+
+ if (brush->imagepaint_tool) {
+ BKE_brush_channelset_ensure_channels(
+ brush->channels, PAINT_MODE_TEXTURE_3D, brush->imagepaint_tool);
+ }
+
+ if (brush->curves_sculpt_tool) {
+ BKE_brush_channelset_ensure_channels(
+ brush->channels, PAINT_MODE_SCULPT_CURVES, brush->curves_sculpt_tool);
+ }
+
+ if (brush->weightpaint_tool) {
+ BKE_brush_channelset_ensure_channels(
+ brush->channels, PAINT_MODE_WEIGHT, brush->weightpaint_tool);
+ }
+}
+void BKE_brush_channelset_ensure_channels(BrushChannelSet *chset, ePaintMode mode, int tool)
+{
+ check_builtin_brush_channels();
+
+ BKE_brush_channelset_ensure(chset, size);
+ BKE_brush_channelset_ensure(chset, unprojected_radius);
+ BKE_brush_channelset_ensure(chset, strength);
+ BKE_brush_channelset_ensure(chset, spacing);
+
+ if (mode != PAINT_MODE_INVALID) {
+ for (BrushChannelType &type : builtin_channels.values()) {
+ int uiflag = type.paint_mode_uiflags[(int)mode].tools[tool];
+
+ if (uiflag) {
+ BrushChannel *ch = _BKE_brush_channelset_ensure(chset, type.idname);
+
+ if (uiflag != -1) {
+ if (!(ch->ui_flag & BRUSH_CHANNEL_SHOW_IN_HEADER_USER_SET)) {
+ ch->ui_flag &= ~BRUSH_CHANNEL_SHOW_IN_HEADER;
+ ch->ui_flag |= uiflag & BRUSH_CHANNEL_SHOW_IN_HEADER;
+ }
+ if (!(ch->ui_flag & BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU_USER_SET)) {
+ ch->ui_flag &= ~BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU;
+ ch->ui_flag |= uiflag & BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU;
+ }
+ if (!(ch->ui_flag & BRUSH_CHANNEL_SHOW_IN_WORKSPACE_USER_SET)) {
+ ch->ui_flag &= ~BRUSH_CHANNEL_SHOW_IN_WORKSPACE;
+ ch->ui_flag |= uiflag & BRUSH_CHANNEL_SHOW_IN_WORKSPACE;
+ }
+ }
+ }
+ }
+ }
+ /* Some helper lambdas */
+
+ auto _ensure = [&](const char *idname) { return _BKE_brush_channelset_ensure(chset, idname); };
+
+#ifdef ensure
+# undef ensure
+#endif
+
+#define ensure _ensure(make_builtin_ch_name(idname), ui_flag);
+
+ auto _ensure_ui = [&](const char *idname, int ui_flag) {
+ _ensure(idname);
+
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+ ch->ui_flag |= ui_flag;
+
+ return ch;
+ };
+
+#ifdef ensure_ui
+# undef ensure_ui
+#endif
+
+#define ensure_ui(idname, ui_flag) _ensure_ui(make_builtin_ch_name(idname), ui_flag)
+
+ const int SHOW_WORKSPACE = BRUSH_CHANNEL_SHOW_IN_WORKSPACE;
+ const int SHOW_CONTEXT = BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU;
+ const int SHOW_HEADER = BRUSH_CHANNEL_SHOW_IN_HEADER;
+ const int SHOW_ALL = (SHOW_WORKSPACE | SHOW_CONTEXT | SHOW_HEADER);
+
+ if (mode == PAINT_MODE_SCULPT) {
+ switch (tool) {
+ case SCULPT_TOOL_PAINT:
+ ensure_ui(wet_mix, SHOW_WORKSPACE);
+ ensure_ui(wet_persistence, SHOW_WORKSPACE);
+ ensure_ui(color, SHOW_ALL);
+ ensure_ui(secondary_color, SHOW_ALL);
+ ensure_ui(flow, SHOW_WORKSPACE | SHOW_CONTEXT);
+ ensure_ui(density, SHOW_WORKSPACE | SHOW_CONTEXT);
+ ensure_ui(tip_scale_x, SHOW_WORKSPACE | SHOW_CONTEXT);
+ break;
+ }
+ }
+
+#undef ensure
+#undef ensure_ui
+}
+
+char *BKE_brush_channel_rna_path(const ID *owner, const BrushChannel *ch)
+{
+ switch (GS(owner->name)) {
+ case ID_BR:
+ return BLI_strdup(ch->idname);
+ case ID_SCE: {
+ string path = "tool_settings.unified_properties[\"";
+
+ path += string(ch->idname) + "\"]";
+
+ return BLI_strdup(path.c_str());
+ }
+ default:
+ return BLI_strdup("");
+ }
+}
+
+void brush_channel_ensure_value(const ID *id, BrushChannel *ch)
+{
+ if (!(ch->flag & BRUSH_CHANNEL_NEEDS_EVALUATE)) {
+ return;
+ }
+
+ string path;
+ StructRNA *srna;
+
+ string rnaname = ch->idname;
+
+ if (GS(id->name) == ID_BR) {
+ path = rnaname;
+ srna = &RNA_Brush;
+ }
+ else {
+ path = "tool_settings.unified_properties[\"";
+ path += rnaname;
+ path += "\"]";
+
+ rnaname = string("[\"") + rnaname + string("\"]");
+ srna = &RNA_Scene;
+ }
+
+ ch->flag &= ~BRUSH_CHANNEL_NEEDS_EVALUATE;
+ PointerRNA ptr, ptr2;
+ PropertyRNA *prop = nullptr;
+
+ RNA_pointer_create(const_cast<ID *>(id), srna, const_cast<ID *>(id), &ptr);
+ if (RNA_path_resolve(&ptr, path.c_str(), &ptr2, &prop)) {
+ PropertyType prop_type = RNA_property_type(prop);
+
+ switch (prop_type) {
+ case PROP_FLOAT:
+ if (RNA_property_array_check(prop)) {
+ RNA_float_get_array(&ptr2, rnaname.c_str(), ch->vector);
+ }
+ else {
+ ch->fvalue = RNA_float_get(&ptr2, rnaname.c_str());
+ }
+ break;
+ case PROP_INT:
+ ch->ivalue = RNA_int_get(&ptr2, rnaname.c_str());
+ break;
+ default:
+ printf("Unknown prop type %d\n", (int)prop_type);
+ break;
+ }
+ }
+ else {
+ printf("Error looking up path %s", path.c_str());
+ return;
+ }
+}
+
+static bool channel_has_mappings(BrushChannel *ch)
+{
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ if (ch->mappings[i].flag & BRUSH_MAPPING_ENABLED) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* idx is used by vector channels */
+double BKE_brush_channel_eval_mappings(BrushChannel *ch,
+ BrushMappingData *mapdata,
+ double f,
+ int idx)
+{
+
+ if (idx == 3 && !(ch->flag & BRUSH_CHANNEL_APPLY_MAPPING_TO_ALPHA)) {
+ return f;
+ }
+
+ if (mapdata) {
+ double factor = f;
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+
+ if (!(mp->flag & BRUSH_MAPPING_ENABLED)) {
+ continue;
+ }
+
+ float inputf = ((float *)mapdata)[i] * mp->premultiply_factor;
+
+ switch ((eBrushMappingFunc)mp->mapfunc) {
+ case BRUSH_MAPFUNC_NONE:
+ break;
+ case BRUSH_MAPFUNC_SAW:
+ inputf -= floorf(inputf);
+ break;
+ case BRUSH_MAPFUNC_TENT:
+ inputf -= floorf(inputf);
+ inputf = 1.0f - fabs(inputf - 0.5f) * 2.0f;
+ break;
+ case BRUSH_MAPFUNC_COS:
+ inputf = 1.0f - (cos(inputf * (float)M_PI * 2.0f) * 0.5f + 0.5f);
+ break;
+ case BRUSH_MAPFUNC_CUTOFF:
+ /*Cutoff is meant to create a fadeout effect,
+ which requires inverting the input. To avoid
+ user confusion we just do it here instead of making
+ them check the inverse checkbox.*/
+ inputf = 1.0f - inputf;
+ CLAMP(inputf, 0.0f, mp->func_cutoff * 2.0f);
+ break;
+ case BRUSH_MAPFUNC_SQUARE:
+ inputf -= floorf(inputf);
+ inputf = inputf > mp->func_cutoff ? 1.0f : 0.0f;
+ break;
+ default:
+ break;
+ }
+
+ if (mp->flag & BRUSH_MAPPING_INVERT) {
+ inputf = 1.0f - inputf;
+ }
+
+ double f2 = BKE_brush_curve_strength_ex(
+ mp->curve.preset, mp->curve.curve, inputf, 1.0f, false);
+ f2 = mp->min + (mp->max - mp->min) * f2;
+
+ /* make sure to update blend_items in rna_brush_engine.c
+ when adding new mode implementations */
+ switch (mp->blendmode) {
+ case MA_RAMP_BLEND:
+ break;
+ case MA_RAMP_MULT:
+ f2 *= factor;
+ break;
+ case MA_RAMP_DIV:
+ f2 = factor / (f2 == 0.0f ? 0.0001f : f2);
+ break;
+ case MA_RAMP_ADD:
+ f2 += factor;
+ break;
+ case MA_RAMP_SUB:
+ f2 = factor - f2;
+ break;
+ case MA_RAMP_DIFF:
+ f2 = fabsf(factor - f2);
+ break;
+ default:
+ printf("Unsupported brush mapping blend mode for %s (%s); will mix instead\n",
+ ch->uiname,
+ ch->idname);
+ break;
+ }
+
+ factor += (f2 - factor) * mp->factor;
+ }
+
+ f = factor;
+ CLAMP(f, ch->def->min, ch->def->max);
+ }
+
+ return f;
+}
+
+static BrushChannel *brush_channel_final(const Brush *brush,
+ const Scene *scene,
+ const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+
+ if (BKE_brush_channel_inherits(brush, scene->toolsettings, ch)) {
+ ch = _BKE_brush_channelset_lookup(scene->toolsettings->unified_channels, idname);
+ brush_channel_ensure_value(&scene->id, ch);
+ }
+ else {
+ brush_channel_ensure_value(&brush->id, ch);
+ }
+
+ return ch;
+}
+
+float _BKE_brush_eval_float(const Brush *brush,
+ const Scene *scene,
+ const char *idname,
+ BrushMappingData *mapping)
+{
+ BrushChannel *ch = brush_channel_final(brush, scene, idname);
+
+ return mapping ? BKE_brush_channel_eval_mappings(ch, mapping, ch->fvalue, 0) : ch->fvalue;
+}
+
+int _BKE_brush_eval_int(const Brush *brush,
+ const Scene *scene,
+ const char *idname,
+ BrushMappingData *mapping)
+{
+ BrushChannel *ch = brush_channel_final(brush, scene, idname);
+
+ return mapping ? (int)BKE_brush_channel_eval_mappings(ch, mapping, (double)ch->ivalue, 0) :
+ ch->ivalue;
+}
+
+static void brush_channel_evaluate(const Brush *br,
+ const Scene *scene,
+ BrushChannelSet *chset,
+ const char *idname,
+ BrushMappingData *mapping)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+
+ if (!mapping) {
+ return;
+ }
+
+ switch (ch->type) {
+ case BRUSH_CHANNEL_TYPE_FLOAT:
+ ch->fvalue = BKE_brush_channel_eval_mappings(ch, mapping, ch->fvalue, 0);
+ break;
+ case BRUSH_CHANNEL_TYPE_INT:
+ case BRUSH_CHANNEL_TYPE_BOOL:
+ case BRUSH_CHANNEL_TYPE_ENUM:
+ ch->ivalue = (int)BKE_brush_channel_eval_mappings(ch, mapping, ch->ivalue, 0);
+ break;
+ case BRUSH_CHANNEL_TYPE_VEC3:
+ case BRUSH_CHANNEL_TYPE_VEC4: {
+ int size = ch->type == BRUSH_CHANNEL_TYPE_VEC3 ? 3 : 4;
+
+ for (int i = 0; i < size; i++) {
+ ch->vector[i] = (int)BKE_brush_channel_eval_mappings(ch, mapping, ch->vector[i], i);
+ break;
+ }
+ break;
+ }
+ }
+}
+
+float _BKE_brush_channelset_float_get(BrushChannelSet *chset, const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+ return ch->fvalue;
+}
+
+void _BKE_brush_channelset_float_set(BrushChannelSet *chset, const char *idname, float f)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+ ch->fvalue = f;
+}
+
+int _BKE_brush_channelset_int_get(BrushChannelSet *chset, const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+ return ch->ivalue;
+}
+
+void _BKE_brush_channelset_int_set(BrushChannelSet *chset, const char *idname, int i)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+ ch->ivalue = i;
+}
+
+bool brush_channelset_rna_path_resolve(const ID *id,
+ BrushChannelSet *chset,
+ const char *idname,
+ PointerRNA *r_ptr,
+ PropertyRNA **r_prop,
+ char **final_idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+
+ if (!ch) {
+ return false;
+ }
+
+ bool ok = true;
+
+ StructRNA *srna;
+
+ switch (GS(id->name)) {
+ case ID_SCE:
+ srna = &RNA_Scene;
+ break;
+ case ID_BR:
+ srna = &RNA_Brush;
+ break;
+ default:
+ return false;
+ }
+
+ PointerRNA id_ptr;
+ RNA_pointer_create(
+ const_cast<ID *>(id), srna, static_cast<void *>(const_cast<ID *>(id)), &id_ptr);
+
+ char *path = BKE_brush_channel_rna_path(id, ch);
+ ok = RNA_path_resolve(&id_ptr, path, r_ptr, r_prop);
+ MEM_SAFE_FREE(path);
+
+ *final_idname = static_cast<char *>(
+ MEM_mallocN(strlen(idname) + 6, "brush_channelset_rna_path_resolve.final_idname"));
+
+ if (RNA_property_is_idprop(*r_prop)) {
+ sprintf(*final_idname, "[\"%s\"]", idname);
+ }
+ else {
+ sprintf(*final_idname, "%s", idname);
+ }
+
+ return ok;
+}
+
+int _BKE_brush_int_get(const ID *id, BrushChannelSet *chset, const char *idname)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ char *final_idname;
+ int ret = 0;
+
+ if (brush_channelset_rna_path_resolve(id, chset, idname, &ptr, &prop, &final_idname)) {
+ ret = RNA_int_get(&ptr, final_idname);
+ }
+
+ MEM_SAFE_FREE(final_idname);
+ return ret;
+}
+
+void _BKE_brush_int_set(ID *id, BrushChannelSet *chset, const char *idname, int i)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ char *final_idname;
+
+ if (brush_channelset_rna_path_resolve(id, chset, idname, &ptr, &prop, &final_idname)) {
+ RNA_int_set(&ptr, final_idname, i);
+ _BKE_brush_channelset_lookup(chset, idname)->ivalue = i;
+ }
+
+ MEM_SAFE_FREE(final_idname);
+}
+
+float _BKE_brush_float_get(const ID *id, BrushChannelSet *chset, const char *idname)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ char *final_idname;
+ float ret = 0;
+
+ if (brush_channelset_rna_path_resolve(id, chset, idname, &ptr, &prop, &final_idname)) {
+ ret = RNA_float_get(&ptr, final_idname);
+ }
+
+ MEM_SAFE_FREE(final_idname);
+ return ret;
+}
+
+void _BKE_brush_float_set(ID *id, BrushChannelSet *chset, const char *idname, float f)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ char *final_idname;
+
+ if (brush_channelset_rna_path_resolve(id, chset, idname, &ptr, &prop, &final_idname)) {
+ RNA_float_set(&ptr, final_idname, f);
+ _BKE_brush_channelset_lookup(chset, idname)->fvalue = f;
+ }
+
+ MEM_SAFE_FREE(final_idname);
+}
+
+void BKE_brush_mapping_copy_data(BrushMapping *dest, BrushMapping *src)
+{
+ *dest = *src;
+
+ if (dest->curve.curve) {
+ dest->curve.curve = BKE_curvemapping_copy(dest->curve.curve);
+ BKE_curvemapping_init(dest->curve.curve);
+ }
+}
+
+void BKE_brush_mapping_free_data(BrushMapping *mp)
+{
+ if (mp->curve.curve) {
+ BKE_curvemapping_free(mp->curve.curve);
+ }
+}
+
+void BKE_brush_channel_copy_data(BrushChannel *dest, BrushChannel *src)
+{
+ BrushChannel *prev = dest->prev, *next = dest->next;
+
+ *dest = *src;
+ dest->prev = prev;
+ dest->next = next;
+
+ if (dest->category) {
+ dest->category = static_cast<char *>(MEM_dupallocN(static_cast<void *>(dest->category)));
+ }
+
+ if (dest->curve.curve) {
+ dest->curve.curve = BKE_curvemapping_copy(dest->curve.curve);
+ BKE_curvemapping_init(dest->curve.curve);
+ }
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BKE_brush_mapping_copy_data(dest->mappings + i, src->mappings + i);
+ }
+}
+
+BrushChannel *BKE_brush_channel_copy(BrushChannel *ch)
+{
+ BrushChannel *ch2 = MEM_cnew<BrushChannel>("BrushChannel");
+ BKE_brush_channel_copy_data(ch2, ch);
+ return ch2;
+}
+
+BrushChannelSet *BKE_brush_channelset_copy(BrushChannelSet *chset)
+{
+ BrushChannelSet *chset2 = BKE_brush_channelset_create();
+ ChannelNameMap *namemap2 = get_namemap(chset2);
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ BrushChannel *ch2 = BKE_brush_channel_copy(ch);
+
+ chset2->channels_num++;
+ namemap2->add(ch2->idname, ch2);
+ BLI_addtail(&chset2->channels, static_cast<void *>(ch2));
+ }
+
+ return chset2;
+}
+
+void BKE_brush_channelset_blend_read(BrushChannelSet *chset, BlendDataReader *reader)
+{
+ check_builtin_brush_channels();
+
+ BLO_read_list(reader, &chset->channels);
+
+ ChannelNameMap *namemap = MEM_new<ChannelNameMap>("ChannelNameMap");
+ chset->channelmap = static_cast<void *>(namemap);
+ chset->channels_num = 0;
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ chset->channels_num++;
+
+ namemap->add(StringRef(ch->idname), ch);
+
+ if (builtin_channels.contains(ch->idname)) {
+ ch->def = &builtin_channels.lookup(ch->idname);
+ }
+ else {
+ ch->def = &empty_brush_type;
+ }
+ /* Read user-defined category if it exists. */
+ BLO_read_data_address(reader, &ch->category);
+ BLO_read_data_address(reader, &ch->curve.curve);
+
+ if (ch->curve.curve) {
+ BKE_curvemapping_blend_read(reader, ch->curve.curve);
+ BKE_curvemapping_init(ch->curve.curve);
+ }
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+
+ BLO_read_data_address(reader, &mp->curve.curve);
+
+ if (mp->curve.curve) {
+ BKE_curvemapping_blend_read(reader, mp->curve.curve);
+ BKE_curvemapping_init(mp->curve.curve);
+ }
+ }
+ }
+}
+
+void BKE_brush_channelset_blend_write(BrushChannelSet *chset, BlendWriter *writer)
+{
+ BLO_write_struct(writer, BrushChannelSet, chset);
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ BLO_write_struct(writer, BrushChannel, ch);
+
+ if (ch->category) {
+ BLO_write_string(writer, ch->category);
+ }
+
+ if (ch->curve.curve) {
+ BKE_curvemapping_blend_write(writer, ch->curve.curve);
+ }
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+
+ if (mp->curve.curve) {
+ BKE_curvemapping_blend_write(writer, mp->curve.curve);
+ }
+ }
+ }
+}
+
+bool BKE_brush_channel_inherits(const Brush *brush,
+ const ToolSettings *tool_settings,
+ BrushChannel *ch)
+{
+ BrushChannel *scene_ch = _BKE_brush_channelset_lookup(tool_settings->unified_channels,
+ ch->idname);
+
+ if (!scene_ch) {
+ return false;
+ }
+
+ if (ch->flag & BRUSH_CHANNEL_INHERIT) {
+ return true;
+ }
+
+ if (scene_ch->flag & BRUSH_CHANNEL_FORCE_INHERIT) {
+ return !(ch->flag & BRUSH_CHANNEL_IGNORE_FORCE_INHERIT);
+ }
+
+ return false;
+}
+
+BrushChannelSet *BKE_brush_channelset_create_final(const Brush *brush,
+ const Scene *scene,
+ BrushMappingData *mapdata)
+{
+ BrushChannelSet *chset = BKE_brush_channelset_copy(brush->channels);
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ BrushChannel *ch1 = _BKE_brush_channelset_lookup(brush->channels, ch->idname);
+ BrushChannel *ch2 = _BKE_brush_channelset_lookup(scene->toolsettings->unified_channels,
+ ch->idname);
+
+ ch1->flag |= BRUSH_CHANNEL_NEEDS_EVALUATE;
+ brush_channel_ensure_value(&brush->id, ch1);
+ ch2->flag |= BRUSH_CHANNEL_NEEDS_EVALUATE;
+ brush_channel_ensure_value(&scene->id, ch2);
+
+ bool inherit = BKE_brush_channel_inherits(brush, scene->toolsettings, ch);
+
+ if (inherit) {
+ BKE_brush_channel_free_data(ch);
+ BKE_brush_channel_copy_data(ch, ch2);
+ }
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ BrushMapping *mp = ch->mappings + i;
+ BrushMapping *mp1 = ch1->mappings + i;
+ BrushMapping *mp2 = ch2->mappings + i;
+
+ if ((mp1->flag & BRUSH_MAPPING_INHERIT_NEVER) || !inherit) {
+ BKE_brush_mapping_free_data(mp);
+ BKE_brush_mapping_copy_data(mp, mp1);
+ }
+ else if ((mp1->flag & BRUSH_MAPPING_INHERIT_ALWAYS) || inherit) {
+ BKE_brush_mapping_free_data(mp);
+ BKE_brush_mapping_copy_data(mp, mp2);
+ }
+ }
+
+ ch2->flag |= BRUSH_CHANNEL_NEEDS_EVALUATE;
+
+ brush_channel_evaluate(brush, scene, chset, ch->idname, mapdata);
+ }
+
+ return chset;
+}
+
+void BKE_brush_channelset_toolsettings_init(ToolSettings *ts)
+{
+ check_builtin_brush_channels();
+
+ if (!ts->unified_properties) {
+ ts->unified_properties = IDP_New(IDP_GROUP, nullptr, "group");
+ }
+
+ if (!ts->unified_channels) {
+ ts->unified_channels = BKE_brush_channelset_create();
+ }
+
+ for (const BrushChannelType &type : builtin_channels.values()) {
+ _BKE_brush_channelset_ensure(ts->unified_channels, type.idname);
+ }
+
+ StructRNA *srna = &RNA_Brush;
+ Brush defaults = {0};
+
+ BLI_strncpy(defaults.id.name, "BRDefaults", sizeof(defaults.id.name));
+
+ defaults.sculpt_tool = SCULPT_TOOL_DRAW;
+ BKE_brush_sculpt_reset(&defaults);
+
+ PointerRNA ptr;
+ ptr.owner_id = &defaults.id;
+ ptr.data = &defaults;
+ ptr.type = &RNA_Brush;
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &ts->unified_channels->channels) {
+ IDProperty *idprop = IDP_GetPropertyFromGroup(ts->unified_properties, ch->idname);
+
+ if (!idprop) {
+ PointerRNA prop_ptr;
+ PropertyRNA *prop;
+ double default_value = 0.0;
+ float vector4[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ if (RNA_path_resolve(&ptr, ch->idname, &prop_ptr, &prop)) {
+ switch (RNA_property_type(prop)) {
+ case PROP_BOOLEAN:
+ default_value = RNA_boolean_get(&prop_ptr, ch->idname) ? 1.0 : 0.0;
+ break;
+ case PROP_INT:
+ default_value = RNA_int_get(&prop_ptr, ch->idname);
+ break;
+ case PROP_FLOAT:
+ if (RNA_property_array_check(prop)) {
+ RNA_float_get_array(&prop_ptr, ch->idname, vector4);
+ }
+ else {
+ default_value = RNA_float_get(&prop_ptr, ch->idname);
+ }
+ break;
+ case PROP_ENUM:
+ default_value = (double)RNA_enum_get(&prop_ptr, ch->idname);
+ break;
+ default:
+ break;
+ }
+ printf("found property!\n");
+ }
+
+ IDPropertyTemplate tmpl;
+ char type;
+
+ switch (ch->type) {
+ case BRUSH_CHANNEL_TYPE_FLOAT:
+ tmpl.f = (float)default_value;
+ type = IDP_FLOAT;
+ break;
+ case BRUSH_CHANNEL_TYPE_INT:
+ case BRUSH_CHANNEL_TYPE_ENUM:
+ case BRUSH_CHANNEL_TYPE_BITMASK:
+ case BRUSH_CHANNEL_TYPE_BOOL:
+ tmpl.i = (int)default_value;
+ type = IDP_INT;
+ break;
+ case BRUSH_CHANNEL_TYPE_VEC4:
+ tmpl.array.type = IDP_FLOAT;
+ tmpl.array.len = 4;
+ type = IDP_ARRAY;
+ break;
+ default:
+ printf("%s: unsupported brush channel type for unified channel %s: %d\n",
+ __func__,
+ ch->idname,
+ ch->type);
+ continue;
+ }
+
+ idprop = IDP_New(type, &tmpl, ch->idname);
+ IDP_AddToGroup(ts->unified_properties, idprop);
+
+ if (ch->type == BRUSH_CHANNEL_TYPE_VEC4) {
+ memcpy(idprop->data.pointer, static_cast<void *>(vector4), sizeof(vector4));
+ }
+ }
+
+ IDPropertyUIData *uidata = IDP_ui_data_ensure(idprop);
+
+ MEM_SAFE_FREE(uidata->description);
+
+ PropertyRNA *prop = RNA_struct_type_find_property(srna, ch->def->idname);
+ BLI_assert(prop);
+
+ uidata->description = BLI_strdup(RNA_property_description(prop));
+
+ PropertySubType prop_subtype = RNA_property_subtype(prop);
+
+ switch (ch->type) {
+ case BRUSH_CHANNEL_TYPE_FLOAT: {
+ IDPropertyUIDataFloat *uidataf = reinterpret_cast<IDPropertyUIDataFloat *>(uidata);
+
+ float min, max, soft_min, soft_max, step, precision;
+
+ RNA_property_float_range(nullptr, prop, &min, &max);
+ RNA_property_float_ui_range(nullptr, prop, &soft_min, &soft_max, &step, &precision);
+
+ uidataf->min = (float)min;
+ uidataf->max = (float)max;
+ uidataf->soft_min = (float)soft_min;
+ uidataf->soft_max = (float)soft_max;
+ uidataf->step = (float)step;
+ uidataf->precision = (int)precision;
+ break;
+ }
+ case BRUSH_CHANNEL_TYPE_INT: {
+ IDPropertyUIDataInt *uidatai = reinterpret_cast<IDPropertyUIDataInt *>(uidata);
+
+ RNA_property_int_range(nullptr, prop, &uidatai->min, &uidatai->max);
+ RNA_property_int_ui_range(
+ nullptr, prop, &uidatai->soft_min, &uidatai->soft_max, &uidatai->step);
+ break;
+ }
+ case BRUSH_CHANNEL_TYPE_ENUM:
+ case BRUSH_CHANNEL_TYPE_BITMASK:
+ break;
+ case BRUSH_CHANNEL_TYPE_BOOL: {
+ IDPropertyUIDataInt *uidatai = reinterpret_cast<IDPropertyUIDataInt *>(uidata);
+
+ uidatai->min = uidatai->soft_min = 0;
+ uidatai->max = uidatai->soft_max = 1;
+ uidatai->step = 1;
+
+ break;
+ }
+ }
+
+ uidata->rna_subtype = prop_subtype;
+ }
+
+ BKE_libblock_free_data(&defaults.id, false);
+}
+
+void _BKE_brush_channelset_mark_update(BrushChannelSet *chset, const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(chset, idname);
+
+ if (ch) {
+ ch->flag |= BRUSH_CHANNEL_NEEDS_EVALUATE;
+ }
+}
+
+void BKE_brush_channelset_ui_order_check(BrushChannelSet *chset)
+{
+ Vector<BrushChannel *> channels;
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ channels.append(ch);
+ }
+
+ auto cmp = [](const BrushChannel *ch1, const BrushChannel *ch2) {
+ return ch1->ui_order < ch2->ui_order;
+ };
+
+ std::sort(channels.begin(), channels.end(), cmp);
+
+ for (int i = 0; i < channels.size(); i++) {
+ channels[i]->ui_order = i;
+ }
+}
+
+void BKE_brush_channelset_ui_order_move(BrushChannelSet *chset,
+ BrushChannel *ch,
+ int uiflag,
+ int dir)
+{
+ Vector<BrushChannel *> channels;
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ channels.append(ch);
+ }
+
+ auto cmp = [](const BrushChannel *ch1, const BrushChannel *ch2) {
+ return ch1->ui_order < ch2->ui_order;
+ };
+
+ std::sort(channels.begin(), channels.end(), cmp);
+
+ const char *cat1 = BKE_brush_channel_category_get(ch);
+
+ for (int i = 0; i < channels.size(); i++) {
+ if (channels[i] == ch) {
+ int j = i;
+ BrushChannel *ch2 = NULL;
+ const char *cat2;
+
+ do {
+ j += dir < 0 ? -1 : 1;
+
+ if (j < 0 || j >= channels.size()) {
+ break;
+ }
+
+ ch2 = channels[j];
+ cat2 = BKE_brush_channel_category_get(ch2);
+ } while ((!(ch2->ui_flag & uiflag) || !STREQ(cat1, cat2)));
+
+ int neworder;
+
+ if (ch2) {
+ neworder = ch2->ui_order;
+ ch2->ui_order = ch->ui_order;
+ }
+ else {
+ neworder = dir < 0 ? 0 : channels.size();
+ }
+
+ ch->ui_order = neworder;
+ }
+ }
+
+ BKE_brush_channelset_ui_order_check(chset);
+}
+
+#if 0
+void BKE_brush_channels_update(Brush *active_brush, Scene *scene)
+{
+ if (!scene->toolsettings) {
+ return;
+ }
+
+ /* Sync evaluated inheritance flags */
+ LISTBASE_FOREACH (BrushChannel *, ch1, &active_brush->channels->channels) {
+ BrushChannel *ch2 = _BKE_brush_channelset_lookup(scene->toolsettings->unified_channels,
+ ch1->idname);
+
+ ch1->evaluated_flag = ch1->flag & ~(BRUSH_CHANNEL_INHERIT | BRUSH_CHANNEL_FORCE_INHERIT);
+ ch2->evaluated_flag = ch2->flag & ~(BRUSH_CHANNEL_INHERIT | BRUSH_CHANNEL_FORCE_INHERIT);
+
+ if (ch1->flag & BRUSH_CHANNEL_INHERIT) {
+ ch1->evaluated_flag |= BRUSH_CHANNEL_INHERIT;
+ ch2->evaluated_flag |= BRUSH_CHANNEL_FORCE_INHERIT;
+ }
+
+ if (ch2->flag & BRUSH_CHANNEL_FORCE_INHERIT) {
+ ch1->evaluated_flag |= BRUSH_CHANNEL_INHERIT;
+ ch2->evaluated_flag |= BRUSH_CHANNEL_FORCE_INHERIT;
+ }
+ }
+}
+#endif
+
+int _BKE_brush_int_get_unified(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+
+ if (!ch) {
+ printf("Unknown channel %s\n", idname);
+ return 0;
+ }
+
+ if (BKE_brush_channel_inherits(brush, scene->toolsettings, ch)) {
+ return _BKE_brush_int_get(&scene->id, scene->toolsettings->unified_channels, idname);
+ }
+ else {
+ return _BKE_brush_int_get(&brush->id, brush->channels, idname);
+ }
+}
+
+void _BKE_brush_int_set_unified(struct Scene *scene,
+ struct Brush *brush,
+ const char *idname,
+ int i)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+
+ if (!ch) {
+ printf("Unknown channel %s\n", idname);
+ return;
+ }
+
+ if (BKE_brush_channel_inherits(brush, scene->toolsettings, ch)) {
+ _BKE_brush_int_set(&scene->id, scene->toolsettings->unified_channels, idname, i);
+ }
+ else {
+ _BKE_brush_int_set(&brush->id, brush->channels, idname, i);
+ }
+}
+
+float _BKE_brush_float_get_unified(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+
+ if (!ch) {
+ printf("Unknown channel %s\n", idname);
+ return 0.0f;
+ }
+
+ if (BKE_brush_channel_inherits(brush, scene->toolsettings, ch)) {
+ return _BKE_brush_float_get(&scene->id, scene->toolsettings->unified_channels, idname);
+ }
+ else {
+ return _BKE_brush_float_get(&brush->id, brush->channels, idname);
+ }
+}
+
+void _BKE_brush_float_set_unified(struct Scene *scene,
+ struct Brush *brush,
+ const char *idname,
+ float f)
+{
+ BrushChannel *ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+
+ if (!ch) {
+ printf("Unknown channel %s\n", idname);
+ return;
+ }
+
+ if (BKE_brush_channel_inherits(brush, scene->toolsettings, ch)) {
+ _BKE_brush_float_set(&scene->id, scene->toolsettings->unified_channels, idname, f);
+ }
+ else {
+ _BKE_brush_float_set(&brush->id, brush->channels, idname, f);
+ }
+}
+
+static bool brush_mapping_inherits(BrushChannel *owner,
+ BrushChannel *unified,
+ BrushMapping *mapping)
+{
+ bool inherit_ch = owner->flag & BRUSH_CHANNEL_INHERIT;
+
+ if ((unified->flag & BRUSH_CHANNEL_FORCE_INHERIT) &&
+ !(owner->flag & BRUSH_CHANNEL_IGNORE_FORCE_INHERIT)) {
+ inherit_ch = true;
+ }
+
+ bool inherit = mapping->inherit_mode == BRUSH_MAPPING_INHERIT_ALWAYS;
+ inherit = inherit || (mapping->inherit_mode == BRUSH_MAPPING_INHERIT_CHANNEL && inherit_ch);
+
+ return inherit;
+}
+bool _BKE_brush_mapping_enabled(const struct Scene *scene,
+ const struct Brush *brush,
+ const char *idname,
+ eBrushMappingType mapping_type)
+{
+ BrushChannel *ch1 = _BKE_brush_channelset_lookup(brush->channels, idname);
+ BrushChannel *ch2 = _BKE_brush_channelset_lookup(scene->toolsettings->unified_channels, idname);
+
+ if (!ch1) {
+ return false;
+ }
+
+ if (brush_mapping_inherits(ch1, ch2, ch1->mappings + mapping_type)) {
+ return ch2->mappings[mapping_type].flag & BRUSH_MAPPING_ENABLED;
+ }
+ else {
+ return ch1->mappings[mapping_type].flag & BRUSH_MAPPING_ENABLED;
+ }
+}
diff --git a/source/blender/blenkernel/intern/brush_channel_define.h b/source/blender/blenkernel/intern/brush_channel_define.h
new file mode 100644
index 00000000000..4caf74d1077
--- /dev/null
+++ b/source/blender/blenkernel/intern/brush_channel_define.h
@@ -0,0 +1,235 @@
+#if defined(BRUSH_CHANNEL_DEFINE_EXTERNAL) || defined(BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES) || \
+ defined(BRUSH_CHANNEL_DEFINE_INTERNAL)
+# ifdef MAKE_PROP
+# undef MAKE_PROP
+# endif
+# ifdef MAKE_PROP_EX
+# undef MAKE_PROP_EX
+# endif
+#endif
+
+#ifdef BRUSH_CHANNEL_DEFINE_EXTERNAL
+# define MAKE_PROP_EX(idname, category, flag, ...) MAKE_PROP(idname, category, uiflags)
+# define MAKE_PROP(idname, category, ...) extern const char *BRUSH_BUILTIN_##idname;
+#elif defined(BRUSH_CHANNEL_DEFINE_INTERNAL_NAMES)
+# define MAKE_PROP_EX(idname, category, flag, ...) MAKE_PROP(idname, category)
+# define MAKE_PROP(idname, category, ...) const char *BRUSH_BUILTIN_##idname = # idname;
+#elif defined(BRUSH_CHANNEL_DEFINE_INTERNAL)
+# define MAKE_PROP(idname, category, ...) {# idname, category, {__VA_ARGS__}, 0, {}},
+# define MAKE_PROP_EX(idname, category, flag, ...) {# idname, category, {__VA_ARGS__}, flag, {}},
+
+#endif
+
+#ifdef SHOW_WORKSPACE
+# undef SHOW_WORKSPACE
+#endif
+#ifdef SHOW_CONTEXT
+# undef SHOW_CONTEXT
+#endif
+#ifdef SHOW_CONTEXT
+# undef SHOW_CONTEXT
+#endif
+#ifdef SHOW_ALL
+# undef SHOW_ALL
+#endif
+
+/*
+ UI visibility flags. Note that some brush types
+ may override these in their own channels, see BKE_brush_channelset_ensure_channels
+*/
+
+#define SHOW_WORKSPACE BRUSH_CHANNEL_SHOW_IN_WORKSPACE
+#define SHOW_CONTEXT BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU
+#define SHOW_HEADER BRUSH_CHANNEL_SHOW_IN_HEADER
+#define SHOW_ALL (SHOW_WORKSPACE | SHOW_CONTEXT | SHOW_HEADER)
+
+/* Note that channel sets for individual brush types are built in
+ * BKE_brush_channelset_ensure_channels
+ */
+
+#define SCULPT_GEO_TOOLS \
+ SCULPT_TOOL_DRAW, SCULPT_TOOL_CLAY, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_FLATTEN, \
+ SCULPT_TOOL_PINCH, SCULPT_TOOL_FILL, SCULPT_TOOL_INFLATE, SCULPT_TOOL_THUMB, \
+ SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_CREASE, SCULPT_TOOL_BLOB, SCULPT_TOOL_CLAY_STRIPS, \
+ SCULPT_TOOL_LAYER
+
+#ifdef GEOMETRY
+#undef GEOMETRY
+#endif
+
+#define GEOMETRY(flag) UI(PAINT_MODE_SCULPT, flag, {SCULPT_GEO_TOOLS})
+
+#ifdef AUTOMASKING
+# undef AUTOMASKING
+#endif
+#define AUTOMASKING UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE)
+
+#ifdef SCULPT_PAINT
+# undef SCULPT_PAINT
+#endif
+#ifdef SCULPT_CLOTHABLE_TOOLS
+# undef SCULPT_CLOTHABLE_TOOLS
+#endif
+
+#define SCULPT_CLOTHABLE_TOOLS \
+ SCULPT_TOOL_CLOTH, SCULPT_TOOL_BOUNDARY, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE
+
+#define SCULPT_PAINT UI(PAINT_MODE_SCULPT, SHOW_CONTEXT | SHOW_WORKSPACE, {SCULPT_TOOL_PAINT})
+
+MAKE_PROP(size, "Basic", UI(SHOW_ALL))
+MAKE_PROP(unprojected_radius, "Basic", UI(SHOW_ALL))
+MAKE_PROP(strength, "Basic", UI(SHOW_ALL))
+MAKE_PROP(automasking_boundary_edges_propagation_steps, "Automasking", AUTOMASKING)
+MAKE_PROP(use_automasking_boundary_edges, "Automasking", AUTOMASKING)
+MAKE_PROP(use_automasking_boundary_face_sets, "Automasking", AUTOMASKING)
+MAKE_PROP(use_automasking_face_sets, "Automasking", AUTOMASKING)
+MAKE_PROP(use_automasking_topology, "Automasking", AUTOMASKING)
+MAKE_PROP(area_radius_factor, "Basic", GEOMETRY(SHOW_WORKSPACE|SHOW_CONTEXT))
+MAKE_PROP(blur_kernel_radius, "Basic")
+MAKE_PROP(boundary_offset, "Basic", UI(PAINT_MODE_SCULPT, SHOW_ALL, {SCULPT_TOOL_BOUNDARY}))
+MAKE_PROP(clone_alpha,
+ "Basic",
+ UI(PAINT_MODE_TEXTURE_2D, SHOW_WORKSPACE),
+ UI(PAINT_MODE_TEXTURE_3D, SHOW_WORKSPACE))
+MAKE_PROP(clone_offset,
+ "Basic",
+ UI(PAINT_MODE_TEXTURE_2D, SHOW_WORKSPACE),
+ UI(PAINT_MODE_TEXTURE_3D, SHOW_WORKSPACE))
+MAKE_PROP(crease_pinch_factor, "Basic")
+MAKE_PROP(cursor_color_add, "Basic")
+MAKE_PROP(cursor_color_subtract, "Basic")
+MAKE_PROP(cursor_overlay_alpha, "Basic")
+MAKE_PROP(disconnected_distance_max, "Basic")
+MAKE_PROP(elastic_deform_volume_preservation,
+ "Basic",
+ UI(PAINT_MODE_SCULPT,
+ SHOW_WORKSPACE,
+ {SCULPT_TOOL_ELASTIC_DEFORM | SCULPT_TOOL_BOUNDARY | SCULPT_TOOL_CLOTH}))
+MAKE_PROP(falloff_angle, "Basic")
+MAKE_PROP(fill_threshold, "Basic")
+MAKE_PROP(flow, "Basic")
+MAKE_PROP(grad_spacing, "Basic")
+MAKE_PROP(hardness, "Basic")
+MAKE_PROP(height, "Basic", UI(PAINT_MODE_SCULPT, SHOW_ALL, {SCULPT_TOOL_LAYER}))
+MAKE_PROP(invert_to_scrape_fill, "Basic")
+MAKE_PROP(mask_overlay_alpha, "Basic")
+MAKE_PROP(mask_stencil_dimension, "Basic")
+MAKE_PROP(mask_stencil_pos, "Basic")
+MAKE_PROP(multiplane_scrape_angle, "Basic")
+MAKE_PROP(normal_radius_factor, "Basic")
+MAKE_PROP(normal_weight, "Basic")
+MAKE_PROP(plane_offset, "Basic")
+MAKE_PROP(plane_trim, "Basic")
+MAKE_PROP(rake_factor, "Basic")
+MAKE_PROP(sharp_threshold, "Basic")
+MAKE_PROP(show_multiplane_scrape_planes_preview, "Basic")
+MAKE_PROP(stencil_dimension, "Basic")
+MAKE_PROP(stencil_pos, "Basic")
+MAKE_PROP(surface_smooth_current_vertex, "Basic")
+MAKE_PROP(surface_smooth_iterations, "Basic")
+MAKE_PROP(surface_smooth_shape_preservation, "Basic")
+MAKE_PROP(texture_overlay_alpha, "Basic")
+MAKE_PROP(texture_sample_bias, "Basic")
+MAKE_PROP(tilt_strength_factor, "Basic")
+MAKE_PROP(tip_roundness, "Basic")
+MAKE_PROP(tip_scale_x, "Basic")
+MAKE_PROP(use_accumulate, "Basic", UI(SHOW_ALL))
+MAKE_PROP(use_adaptive_space, "Basic")
+MAKE_PROP(use_airbrush, "Basic")
+MAKE_PROP(use_alpha, "Basic")
+MAKE_PROP(use_anchor, "Basic")
+MAKE_PROP(use_connected_only, "Basic")
+MAKE_PROP(use_cursor_overlay, "Basic")
+MAKE_PROP(use_cursor_overlay_override, "Basic")
+MAKE_PROP(use_curve, "Basic")
+MAKE_PROP(use_custom_icon, "Basic")
+MAKE_PROP(use_edge_to_edge, "Basic")
+MAKE_PROP(use_frontface, "Basic")
+MAKE_PROP(use_frontface_falloff, "Basic")
+MAKE_PROP(use_grab_active_vertex, "Basic")
+MAKE_PROP(use_grab_silhouette, "Basic")
+MAKE_PROP(use_line, "Basic")
+MAKE_PROP(use_multiplane_scrape_dynamic, "Basic")
+MAKE_PROP(use_original_normal, "Basic")
+MAKE_PROP(use_original_plane, "Basic")
+MAKE_PROP(use_persistent, "Basic")
+MAKE_PROP(use_plane_trim, "Basic")
+MAKE_PROP(use_primary_overlay, "Basic")
+MAKE_PROP(use_primary_overlay_override, "Basic")
+MAKE_PROP(use_restore_mesh, "Basic")
+MAKE_PROP(use_secondary_overlay, "Basic")
+MAKE_PROP(use_secondary_overlay_override, "Basic")
+MAKE_PROP(use_smooth_stroke, "Basic", UI(SHOW_WORKSPACE))
+MAKE_PROP(use_space, "Basic", UI(SHOW_WORKSPACE))
+MAKE_PROP(use_space_attenuation, "Basic", UI(SHOW_WORKSPACE))
+MAKE_PROP(use_vertex_grease_pencil, "Basic")
+MAKE_PROP(weight, "Basic")
+MAKE_PROP(wet_paint_radius_factor, "Basic")
+MAKE_PROP(cloth_constraint_softbody_strength,
+ "Cloth",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(cloth_damping, "Cloth", UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(cloth_mass, "Cloth", UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(cloth_sim_falloff,
+ "Cloth",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(cloth_sim_limit,
+ "Cloth",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(use_cloth_collision,
+ "Cloth",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(use_cloth_pin_simulation_boundary,
+ "Cloth",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_CLOTHABLE_TOOLS}))
+MAKE_PROP(color,
+ "Paint",
+ UI(PAINT_MODE_SCULPT, SHOW_ALL, {SCULPT_TOOL_PAINT}),
+ UI(PAINT_MODE_TEXTURE_2D, SHOW_ALL),
+ UI(PAINT_MODE_TEXTURE_3D, SHOW_ALL),
+ UI(PAINT_MODE_VERTEX, SHOW_ALL))
+MAKE_PROP(density, "Paint", SCULPT_PAINT)
+MAKE_PROP(rate, "Paint")
+MAKE_PROP(secondary_color,
+ "Paint",
+ UI(PAINT_MODE_SCULPT, SHOW_ALL, {SCULPT_TOOL_PAINT}),
+ UI(PAINT_MODE_TEXTURE_2D, SHOW_ALL),
+ UI(PAINT_MODE_TEXTURE_3D, SHOW_ALL),
+ UI(PAINT_MODE_VERTEX, SHOW_ALL))
+MAKE_PROP(use_paint_antialiasing, "Paint", UI(PAINT_MODE_TEXTURE_2D, SHOW_WORKSPACE))
+MAKE_PROP(use_paint_weight, "Paint")
+MAKE_PROP(wet_mix, "Paint", SCULPT_PAINT)
+MAKE_PROP(wet_persistence, "Paint", SCULPT_PAINT)
+MAKE_PROP(pose_ik_segments, "Pose", UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_TOOL_POSE}))
+MAKE_PROP(pose_offset, "Pose", UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_TOOL_POSE}))
+MAKE_PROP(pose_smooth_iterations,
+ "Pose",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_TOOL_POSE}))
+MAKE_PROP(use_pose_ik_anchored, "Pose", UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_TOOL_POSE}))
+MAKE_PROP(use_pose_lock_rotation,
+ "Pose",
+ UI(PAINT_MODE_SCULPT, SHOW_WORKSPACE, {SCULPT_TOOL_POSE}))
+MAKE_PROP(auto_smooth_factor, "Smooth", GEOMETRY(SHOW_WORKSPACE | SHOW_CONTEXT))
+MAKE_PROP(topology_rake_factor, "Smooth", GEOMETRY(SHOW_WORKSPACE))
+MAKE_PROP(dash_ratio, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(dash_samples, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(jitter, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(jitter_absolute, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(smooth_stroke_factor, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(smooth_stroke_radius, "Stroke", UI(SHOW_WORKSPACE))
+MAKE_PROP(spacing, "Stroke", UI(SHOW_WORKSPACE))
+
+
+
+// tst
+#undef MAKE_PROP
+#undef MAKE_PROP_EX
+#undef SHOW_WORKSPACE
+#undef SHOW_HEADER
+#undef SHOW_CONTEXT
+#undef SHOW_ALL
+#undef AUTOMASKING
+#undef SCULPT_GEO_TOOLS
+#undef SCULPT_PAINT
+#undef SCULPT_CLOTHABLE_TOOLS
+#undef GEOMETRY
diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc
index bd26075f81f..3870c6a677f 100644
--- a/source/blender/blenkernel/intern/scene.cc
+++ b/source/blender/blenkernel/intern/scene.cc
@@ -56,6 +56,7 @@
#include "BKE_animsys.h"
#include "BKE_armature.h"
#include "BKE_bpath.h"
+#include "BKE_brush_channel.h"
#include "BKE_cachefile.h"
#include "BKE_collection.h"
#include "BKE_colortools.h"
@@ -162,6 +163,7 @@ static void scene_init_data(ID *id)
CURVEMAP_SLOPE_POS_NEG);
scene->toolsettings = DNA_struct_default_alloc(ToolSettings);
+ BKE_brush_channelset_toolsettings_init(scene->toolsettings);
scene->toolsettings->autokey_mode = uchar(U.autokey_mode);
@@ -1108,6 +1110,10 @@ static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_addres
BKE_screen_view3d_shading_blend_write(writer, &sce->display.shading);
+ if (sce->toolsettings && sce->toolsettings->unified_channels) {
+ BKE_brush_channelset_blend_write(sce->toolsettings->unified_channels, writer);
+ }
+
/* Freed on `do_versions()`. */
BLI_assert(sce->layer_properties == nullptr);
}
@@ -1165,6 +1171,16 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
BLO_read_data_address(reader, &sce->toolsettings);
if (sce->toolsettings) {
+ BLO_read_data_address(reader, &sce->toolsettings->unified_channels);
+
+ BLO_read_data_address(reader, &sce->toolsettings->unified_properties);
+ IDP_BlendDataRead(reader, &sce->toolsettings->unified_properties);
+
+ if (sce->toolsettings->unified_channels) {
+ BKE_brush_channelset_blend_read(sce->toolsettings->unified_channels, reader);
+ }
+
+ BKE_brush_channelset_toolsettings_init(sce->toolsettings);
/* Reset last_location and last_hit, so they are not remembered across sessions. In some files
* these are also NaN, which could lead to crashes in painting. */
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
index 52f2ddc6550..ab05e9ccab7 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc
@@ -125,7 +125,7 @@ struct CombOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
index a44499ce133..aa201913cd9 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc
@@ -102,7 +102,7 @@ struct DeleteOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_pos_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
index 78e2d55e6b9..ae3c84ba512 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc
@@ -555,7 +555,7 @@ struct DensitySubtractOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
brush_pos_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
index 2624d70ccf7..c26b4738410 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
@@ -288,7 +288,7 @@ struct CurvesEffectOperationExecutor {
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
index 61e2559f303..3e0e6c87aec 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -39,7 +39,9 @@ struct StrokeExtension {
ReportList *reports = nullptr;
};
-float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension);
+float brush_radius_factor(const Scene &scene,
+ const Brush &brush,
+ const StrokeExtension &stroke_extension);
float brush_radius_get(const Scene &scene,
const Brush &brush,
const StrokeExtension &stroke_extension);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index 6366da310b6..d2b45466f2b 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -79,9 +79,11 @@ using blender::bke::CurvesGeometry;
/** \name * SCULPT_CURVES_OT_brush_stroke
* \{ */
-float brush_radius_factor(const Brush &brush, const StrokeExtension &stroke_extension)
+float brush_radius_factor(const Scene &scene,
+ const Brush &brush,
+ const StrokeExtension &stroke_extension)
{
- if (BKE_brush_use_size_pressure(&brush)) {
+ if (BKE_brush_use_size_pressure(&scene, &brush)) {
return stroke_extension.pressure;
}
return 1.0f;
@@ -91,7 +93,7 @@ float brush_radius_get(const Scene &scene,
const Brush &brush,
const StrokeExtension &stroke_extension)
{
- return BKE_brush_size_get(&scene, &brush) * brush_radius_factor(brush, stroke_extension);
+ return BKE_brush_size_get(&scene, &brush) * brush_radius_factor(scene, brush, stroke_extension);
}
float brush_strength_factor(const Brush &brush, const StrokeExtension &stroke_extension)
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
index 3e43b1a6361..41691841e60 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_pinch.cc
@@ -98,7 +98,7 @@ struct PinchOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = BKE_brush_alpha_get(ctx_.scene, brush_);
invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
index b4e949106e7..1eefe0e24b6 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_puff.cc
@@ -98,7 +98,7 @@ struct PuffOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
brush_pos_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
index cc5a5e7ae8a..8c97cd10fb8 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_selection_paint.cc
@@ -88,7 +88,7 @@ struct SelectionPaintOperationExecutor {
brush_ = BKE_paint_brush_for_read(&ctx_.scene->toolsettings->curves_sculpt->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = BKE_brush_alpha_get(ctx_.scene, brush_);
brush_pos_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
index ae89bc1c58b..db4f2d22c4b 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_slide.cc
@@ -154,7 +154,7 @@ struct SlideOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
curve_factors_ = get_curves_selection(*curves_id_orig_);
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
index 37a7f1c83ed..aeaad6a4f3a 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_smooth.cc
@@ -74,7 +74,7 @@ struct SmoothOperationExecutor {
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
brush_pos_re_ = stroke_extension.mouse_position;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
index 67757ce5f4a..aa1ebf5ff16 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc
@@ -112,7 +112,7 @@ struct SnakeHookOperatorExecutor {
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
- brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
+ brush_radius_factor_ = brush_radius_factor(*ctx_.scene, *brush_, stroke_extension);
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape);
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index 71ec444098e..de696e889b5 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -524,7 +524,8 @@ static int project_brush_radius(ViewContext *vc, float radius, const float locat
/* Draw an overlay that shows what effect the brush's texture will
* have on brush strength. */
-static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
+static bool paint_draw_tex_overlay(Scene *scene,
+ UnifiedPaintSettings *ups,
Brush *brush,
ViewContext *vc,
int x,
@@ -568,7 +569,7 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
GPU_matrix_translate_2f(-x, -y);
/* Scale based on tablet pressure. */
- if (primary && ups->stroke_active && BKE_brush_use_size_pressure(brush)) {
+ if (primary && ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) {
const float scale = ups->size_pressure_value;
GPU_matrix_translate_2f(x, y);
GPU_matrix_scale_2f(scale, scale);
@@ -669,8 +670,13 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
/* Draw an overlay that shows what effect the brush's texture will
* have on brush strength. */
-static bool paint_draw_cursor_overlay(
- UnifiedPaintSettings *ups, Brush *brush, ViewContext *vc, int x, int y, float zoom)
+static bool paint_draw_cursor_overlay(Scene *scene,
+ UnifiedPaintSettings *ups,
+ Brush *brush,
+ ViewContext *vc,
+ int x,
+ int y,
+ float zoom)
{
rctf quad;
/* Check for overlay mode. */
@@ -705,7 +711,7 @@ static bool paint_draw_cursor_overlay(
}
/* Scale based on tablet pressure. */
- if (ups->stroke_active && BKE_brush_use_size_pressure(brush)) {
+ if (ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) {
do_pop = true;
GPU_matrix_push();
GPU_matrix_translate_2fv(center);
@@ -751,7 +757,8 @@ static bool paint_draw_cursor_overlay(
return true;
}
-static bool paint_draw_alpha_overlay(UnifiedPaintSettings *ups,
+static bool paint_draw_alpha_overlay(Scene *scene,
+ UnifiedPaintSettings *ups,
Brush *brush,
ViewContext *vc,
int x,
@@ -778,22 +785,24 @@ static bool paint_draw_alpha_overlay(UnifiedPaintSettings *ups,
/* Colored overlay should be drawn separately. */
if (col) {
if (!(flags & PAINT_OVERLAY_OVERRIDE_PRIMARY)) {
- alpha_overlay_active = paint_draw_tex_overlay(ups, brush, vc, x, y, zoom, mode, true, true);
+ alpha_overlay_active = paint_draw_tex_overlay(
+ scene, ups, brush, vc, x, y, zoom, mode, true, true);
}
if (!(flags & PAINT_OVERLAY_OVERRIDE_SECONDARY)) {
alpha_overlay_active = paint_draw_tex_overlay(
- ups, brush, vc, x, y, zoom, mode, false, false);
+ scene, ups, brush, vc, x, y, zoom, mode, false, false);
}
if (!(flags & PAINT_OVERLAY_OVERRIDE_CURSOR)) {
- alpha_overlay_active = paint_draw_cursor_overlay(ups, brush, vc, x, y, zoom);
+ alpha_overlay_active = paint_draw_cursor_overlay(scene, ups, brush, vc, x, y, zoom);
}
}
else {
if (!(flags & PAINT_OVERLAY_OVERRIDE_PRIMARY) && (mode != PAINT_MODE_WEIGHT)) {
- alpha_overlay_active = paint_draw_tex_overlay(ups, brush, vc, x, y, zoom, mode, false, true);
+ alpha_overlay_active = paint_draw_tex_overlay(
+ scene, ups, brush, vc, x, y, zoom, mode, false, true);
}
if (!(flags & PAINT_OVERLAY_OVERRIDE_CURSOR)) {
- alpha_overlay_active = paint_draw_cursor_overlay(ups, brush, vc, x, y, zoom);
+ alpha_overlay_active = paint_draw_cursor_overlay(scene, ups, brush, vc, x, y, zoom);
}
}
@@ -1006,7 +1015,7 @@ static void paint_cursor_update_unprojected_radius(UnifiedPaintSettings *ups,
unprojected_radius = paint_calc_object_space_radius(vc, location, projected_radius);
/* Scale 3D brush radius by pressure. */
- if (ups->stroke_active && BKE_brush_use_size_pressure(brush)) {
+ if (ups->stroke_active && BKE_brush_use_size_pressure(vc->scene, brush)) {
unprojected_radius *= ups->size_pressure_value;
}
@@ -1401,7 +1410,7 @@ static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext)
immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha);
/* Draw brush outline. */
- if (pcontext->ups->stroke_active && BKE_brush_use_size_pressure(pcontext->brush)) {
+ if (pcontext->ups->stroke_active && BKE_brush_use_size_pressure(pcontext->scene, pcontext->brush)) {
imm_draw_circle_wire_2d(pcontext->pos,
pcontext->translation[0],
pcontext->translation[1],
@@ -1841,7 +1850,8 @@ static void paint_cursor_update_rake_rotation(PaintCursorContext *pcontext)
static void paint_cursor_check_and_draw_alpha_overlays(PaintCursorContext *pcontext)
{
- pcontext->alpha_overlay_drawn = paint_draw_alpha_overlay(pcontext->ups,
+ pcontext->alpha_overlay_drawn = paint_draw_alpha_overlay(pcontext->scene,
+ pcontext->ups,
pcontext->brush,
&pcontext->vc,
pcontext->x,
diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h
index c6fe7ed3072..b489dec864d 100644
--- a/source/blender/editors/sculpt_paint/paint_intern.h
+++ b/source/blender/editors/sculpt_paint/paint_intern.h
@@ -370,6 +370,7 @@ void paint_sample_color(
void paint_stroke_operator_properties(struct wmOperatorType *ot);
void BRUSH_OT_curve_preset(struct wmOperatorType *ot);
+void BRUSH_OT_curve_preset_load(struct wmOperatorType *ot);
void PAINT_OT_face_select_linked(struct wmOperatorType *ot);
void PAINT_OT_face_select_linked_pick(struct wmOperatorType *ot);
diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c
index b78c60e7964..3563fa772a7 100644
--- a/source/blender/editors/sculpt_paint/paint_ops.c
+++ b/source/blender/editors/sculpt_paint/paint_ops.c
@@ -1378,6 +1378,78 @@ static void BRUSH_OT_stencil_reset_transform(wmOperatorType *ot)
ot->srna, "mask", 0, "Modify Mask Stencil", "Modify either the primary or mask stencil");
}
+static bool change_channel_order_poll(bContext *C)
+{
+ Paint *paint = BKE_paint_get_active_from_context(C);
+
+ if (!paint) {
+ return false;
+ }
+
+ Brush *brush = BKE_paint_brush(paint);
+ return brush != NULL;
+}
+
+static int change_channel_order_exec(bContext *C, wmOperator *op)
+{
+ Paint *paint = BKE_paint_get_active_from_context(C);
+ Brush *brush = BKE_paint_brush(paint);
+
+ char idname[BRUSH_CHANNEL_MAX_IDNAME];
+ RNA_string_get(op->ptr, "channel", idname);
+
+ if (!_BKE_brush_channelset_has(brush->channels, idname)) {
+ static char error[128];
+
+ sprintf(error, "Invalid brush channel \"%s\"", idname);
+ BKE_reportf(op->reports, RPT_ERROR_INVALID_INPUT, error);
+ return OPERATOR_CANCELLED;
+ }
+
+ BrushChannel *dest_ch = _BKE_brush_channelset_lookup(brush->channels, idname);
+ int neworder = dest_ch->ui_order;
+
+ BKE_brush_channelset_ui_order_check(brush->channels);
+ char filterkey[64];
+ int uiflag = 0;
+
+ RNA_string_get(op->ptr, "filterkey", filterkey);
+ if (STREQ(filterkey, "show_in_workspace")) {
+ uiflag = BRUSH_CHANNEL_SHOW_IN_WORKSPACE;
+ }
+ else if (STREQ(filterkey, "show_in_header")) {
+ uiflag = BRUSH_CHANNEL_SHOW_IN_HEADER;
+ }
+ else if (STREQ(filterkey, "show_in_context_menu")) {
+ uiflag = BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU;
+ }
+
+ int dir = RNA_int_get(op->ptr, "direction");
+
+ BKE_brush_channelset_ui_order_move(brush->channels, dest_ch, uiflag, dir);
+
+ return OPERATOR_FINISHED;
+}
+
+void BRUSH_OT_change_channel_order(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Change Brush Channel Order";
+ ot->description = "Move brush channels up or down";
+ ot->idname = "BRUSH_OT_change_channel_order";
+
+ /* api callbacks */
+ ot->exec = change_channel_order_exec;
+ ot->poll = change_channel_order_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_string(ot->srna, "channel", "", BRUSH_CHANNEL_MAX_IDNAME, "Brush Channel", "");
+ RNA_def_int(ot->srna, "direction", 1, -1, 1, "Direction", "", -1, 1);
+ RNA_def_string(ot->srna, "filterkey", NULL, 64, "filterkey", "filterkey");
+}
+
/**************************** registration **********************************/
void ED_operatormacros_paint(void)
@@ -1422,6 +1494,7 @@ void ED_operatortypes_paint(void)
WM_operatortype_append(BRUSH_OT_add_gpencil);
WM_operatortype_append(BRUSH_OT_scale_size);
WM_operatortype_append(BRUSH_OT_curve_preset);
+ WM_operatortype_append(BRUSH_OT_curve_preset_load);
WM_operatortype_append(BRUSH_OT_reset);
WM_operatortype_append(BRUSH_OT_stencil_control);
WM_operatortype_append(BRUSH_OT_stencil_fit_image_aspect);
@@ -1486,6 +1559,8 @@ void ED_operatortypes_paint(void)
WM_operatortype_append(PAINT_OT_mask_lasso_gesture);
WM_operatortype_append(PAINT_OT_mask_box_gesture);
WM_operatortype_append(PAINT_OT_mask_line_gesture);
+
+ WM_operatortype_append(BRUSH_OT_change_channel_order);
}
void ED_keymap_paint(wmKeyConfig *keyconf)
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index bc4c6dc4148..deb76bff06d 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -14,6 +14,7 @@
#include "PIL_time.h"
+#include "DNA_brush_channel_types.h"
#include "DNA_brush_types.h"
#include "DNA_curve_types.h"
#include "DNA_object_types.h"
@@ -22,6 +23,7 @@
#include "RNA_access.h"
#include "BKE_brush.h"
+#include "BKE_brush_channel.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_curve.h"
@@ -312,6 +314,7 @@ static bool paint_brush_update(bContext *C,
copy_v2_v2(ups->last_rake, mouse);
copy_v2_v2(ups->tex_mouse, mouse);
copy_v2_v2(ups->mask_tex_mouse, mouse);
+
stroke->cached_size_pressure = pressure;
ups->do_linear_conversion = false;
@@ -346,7 +349,7 @@ static bool paint_brush_update(bContext *C,
ups->pixel_radius = BKE_brush_size_get(scene, brush);
ups->initial_pixel_radius = BKE_brush_size_get(scene, brush);
- if (BKE_brush_use_size_pressure(brush) && paint_supports_dynamic_size(brush, mode)) {
+ if (BKE_brush_use_size_pressure(scene, brush) && paint_supports_dynamic_size(brush, mode)) {
ups->pixel_radius *= stroke->cached_size_pressure;
}
@@ -535,7 +538,7 @@ static void paint_brush_stroke_add_step(
if (tablet && (pressure >= 0.99f) &&
((pop->s.brush->flag & BRUSH_SPACING_PRESSURE) ||
BKE_brush_use_alpha_pressure(pop->s.brush) ||
- BKE_brush_use_size_pressure(pop->s.brush))) {
+ BKE_brush_use_size_pressure(scene, pop->s.brush))) {
return;
}
@@ -548,7 +551,7 @@ static void paint_brush_stroke_add_step(
if (tablet && (pressure < 0.0002f) &&
((pop->s.brush->flag & BRUSH_SPACING_PRESSURE) ||
BKE_brush_use_alpha_pressure(pop->s.brush) ||
- BKE_brush_use_size_pressure(pop->s.brush))) {
+ BKE_brush_use_size_pressure(scene, pop->s.brush))) {
return;
}
#endif
@@ -677,7 +680,20 @@ static float paint_space_stroke_spacing(bContext *C,
ePaintMode mode = BKE_paintmode_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
float size_clamp = 0.0f;
- float size = BKE_brush_size_get(scene, stroke->brush) * size_pressure;
+ // float size = BKE_brush_size_get(scene, stroke->brush) * size_pressure;
+
+ BrushMappingData mapdata = {0};
+ mapdata.pressure = size_pressure;
+
+#if 1
+ BKE_brush_channelset_mark_update(scene->toolsettings->unified_channels, size);
+ BKE_brush_channelset_mark_update(brush->channels, size);
+ BKE_brush_channelset_mark_update(scene->toolsettings->unified_channels, unprojected_radius);
+ BKE_brush_channelset_mark_update(brush->channels, unprojected_radius);
+#endif
+
+ float size = BKE_brush_eval_int(brush, scene, size, &mapdata);
+
if (paint_stroke_use_scene_spacing(brush, mode)) {
if (!BKE_brush_use_locked_size(scene, brush)) {
float last_object_space_position[3];
@@ -686,7 +702,8 @@ static float paint_space_stroke_spacing(bContext *C,
size_clamp = paint_calc_object_space_radius(&stroke->vc, last_object_space_position, size);
}
else {
- size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * size_pressure;
+ // size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * size_pressure;
+ size_clamp = BKE_brush_eval_float(brush, scene, unprojected_radius, &mapdata);
}
}
else {
@@ -772,7 +789,7 @@ static float paint_space_stroke_spacing_variable(bContext *C,
float dpressure,
float length)
{
- if (BKE_brush_use_size_pressure(stroke->brush)) {
+ if (BKE_brush_use_size_pressure(scene, stroke->brush)) {
/* use pressure to modify size. set spacing so that at 100%, the circles
* are aligned nicely with no overlap. for this the spacing needs to be
* the average of the previous and next size. */
diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c
index ce4a5151a20..3d54683ef48 100644
--- a/source/blender/editors/sculpt_paint/paint_utils.c
+++ b/source/blender/editors/sculpt_paint/paint_utils.c
@@ -8,6 +8,8 @@
#include <math.h>
#include <stdlib.h>
+#include "MEM_guardedalloc.h"
+
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -34,6 +36,7 @@
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
#include "BKE_report.h"
+#include "BKE_colortools.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@@ -41,6 +44,7 @@
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_prototypes.h"
+#include "RNA_path.h"
#include "GPU_framebuffer.h"
#include "GPU_matrix.h"
@@ -609,6 +613,130 @@ void BRUSH_OT_curve_preset(wmOperatorType *ot)
BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */
}
+
+/**
+ * Attempt to retrieve the rna pointer/property from an rna path.
+ *
+ * \return 0 for failure, 1 for success, and also 1 if property is not set.
+ */
+static int curvemapping_preset_get_path(
+ PointerRNA *ctx_ptr, wmOperator *op, const char *name, PointerRNA *r_ptr, PropertyRNA **r_prop)
+{
+ PropertyRNA *unused_prop;
+
+ /* get an rna string path from the operator's properties */
+ char *str;
+ if (!(str = RNA_string_get_alloc(op->ptr, name, NULL, 0, NULL))) {
+ return 1;
+ }
+
+ if (str[0] == '\0') {
+ if (r_prop) {
+ *r_prop = NULL;
+ }
+ MEM_freeN(str);
+ return 1;
+ }
+
+ if (!r_prop) {
+ r_prop = &unused_prop;
+ }
+
+ /* get rna from path */
+ if (!RNA_path_resolve(ctx_ptr, str, r_ptr, r_prop)) {
+ MEM_freeN(str);
+
+ BKE_reportf(op->reports, RPT_ERROR, "Could not resolve path '%s'", name);
+ return 0;
+ }
+
+ if (*r_prop) {
+ PropertyType prop_type = RNA_property_type(*r_prop);
+ if (!*r_prop || prop_type != PROP_POINTER) {
+ MEM_freeN(str);
+ BKE_reportf(op->reports, RPT_ERROR, "Property from path '%s' is not a curve", name);
+ return 0;
+ }
+
+ if (RNA_property_pointer_type(r_ptr, *r_prop) != &RNA_CurveMapping) {
+ MEM_freeN(str);
+ BKE_reportf(op->reports, RPT_ERROR, "Property from path '%s' is not a curve", name);
+ return 0;
+ }
+ }
+ else if (r_ptr->type != &RNA_CurveMapping) {
+ MEM_freeN(str);
+ BKE_reportf(op->reports, RPT_ERROR, "Property from path '%s' is not a curve", name);
+ return 0;
+ }
+
+ /* success */
+ MEM_freeN(str);
+ return 1;
+}
+
+static int curvemapping_preset_exec(bContext *C, wmOperator *op)
+{
+ PointerRNA ctx_ptr;
+ RNA_pointer_create(NULL, &RNA_Context, C, &ctx_ptr);
+
+ PointerRNA ptr;
+ PropertyRNA *prop;
+
+ if (!curvemapping_preset_get_path(&ctx_ptr, op, "path", &ptr, &prop)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ CurveMapping *cumap = prop ? RNA_property_pointer_get(&ptr, prop).data : ptr.data;
+ int preset = RNA_enum_get(op->ptr, "shape");
+
+ cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE;
+ cumap->preset = preset;
+
+ CurveMap *cuma = cumap->cm;
+
+ int slope = RNA_boolean_get(op->ptr, "invert") ? CURVEMAP_SLOPE_NEGATIVE :
+ CURVEMAP_SLOPE_POSITIVE;
+
+ BKE_curvemap_reset(cuma, &cumap->clipr, cumap->preset, slope);
+ BKE_curvemapping_changed(cumap, false);
+
+ return OPERATOR_FINISHED;
+}
+
+static bool curvemapping_preset_poll(bContext *C)
+{
+ return true;
+}
+
+void BRUSH_OT_curve_preset_load(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+ static const EnumPropertyItem prop_shape_items[] = {
+ {CURVE_PRESET_SHARP, "SHARP", 0, "Sharp", ""},
+ {CURVE_PRESET_SMOOTH, "SMOOTH", 0, "Smooth", ""},
+ {CURVE_PRESET_MAX, "MAX", 0, "Max", ""},
+ {CURVE_PRESET_LINE, "LINE", 0, "Line", ""},
+ {CURVE_PRESET_ROUND, "ROUND", 0, "Round", ""},
+ {CURVE_PRESET_ROOT, "ROOT", 0, "Root", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ ot->name = "Preset";
+ ot->description = "Load Curve Preset";
+ ot->idname = "BRUSH_OT_curve_preset_load";
+
+ ot->exec = curvemapping_preset_exec;
+ ot->poll = curvemapping_preset_poll;
+
+ prop = RNA_def_enum(ot->srna, "shape", prop_shape_items, CURVE_PRESET_SMOOTH, "Mode", "");
+ RNA_def_property_translation_context(prop,
+ BLT_I18NCONTEXT_ID_CURVE_LEGACY); /* Abusing id_curve :/ */
+
+ prop = RNA_def_string(ot->srna, "path", NULL, 256, "rna path", "RNA path to curve mapping");
+ prop = RNA_def_boolean(ot->srna, "invert", false, "invert", "Invert curve");
+}
+
/* face-select ops */
static int paint_select_linked_exec(bContext *C, wmOperator *UNUSED(op))
{
diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc
index acd8b1a6bb1..cc3f94d06b5 100644
--- a/source/blender/editors/sculpt_paint/paint_vertex.cc
+++ b/source/blender/editors/sculpt_paint/paint_vertex.cc
@@ -1691,7 +1691,7 @@ static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, P
BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius);
}
- if (BKE_brush_use_size_pressure(brush) &&
+ if (BKE_brush_use_size_pressure(scene, brush) &&
paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) {
cache->radius = cache->initial_radius * cache->pressure;
}
@@ -1869,7 +1869,7 @@ static void get_brush_alpha_data(const Scene *scene,
float *r_brush_alpha_pressure)
{
*r_brush_size_pressure = BKE_brush_size_get(scene, brush) *
- (BKE_brush_use_size_pressure(brush) ? ss->cache->pressure : 1.0f);
+ (BKE_brush_use_size_pressure(scene, brush) ? ss->cache->pressure : 1.0f);
*r_brush_alpha_value = BKE_brush_alpha_get(scene, brush);
*r_brush_alpha_pressure = (BKE_brush_use_alpha_pressure(brush) ? ss->cache->pressure : 1.0f);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index 95192114429..17f564c3051 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -13,9 +13,12 @@
#include "BLI_ghash.h"
#include "BLI_gsqueue.h"
#include "BLI_math.h"
+#include "BLI_rand.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
+#include "PIL_time.h"
+
#include "DNA_brush_types.h"
#include "DNA_customdata_types.h"
#include "DNA_mesh_types.h"
@@ -26,6 +29,7 @@
#include "BKE_attribute.h"
#include "BKE_brush.h"
+#include "BKE_brush_channel.h"
#include "BKE_ccg.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
@@ -2294,7 +2298,8 @@ void SCULPT_calc_area_normal_and_center(
* values pull vertices, negative values push. Uses tablet pressure and a
* special multiplier found experimentally to scale the strength factor.
*/
-static float brush_strength(const Sculpt *sd,
+static float brush_strength(const SculptSession *ss,
+ const Sculpt *sd,
const StrokeCache *cache,
const float feather,
const UnifiedPaintSettings *ups,
@@ -2304,10 +2309,12 @@ static float brush_strength(const Sculpt *sd,
const Brush *brush = BKE_paint_brush((Paint *)&sd->paint);
/* Primary strength input; square it to make lower values more sensitive. */
- const float root_alpha = BKE_brush_alpha_get(scene, brush);
+ // float root_alpha = BKE_brush_alpha_get(scene, brush);
+
+ float root_alpha = BKE_brush_channelset_float_get(ss->cache->channels, strength);
+
const float alpha = root_alpha * root_alpha;
const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f;
- const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f;
const float pen_flip = cache->pen_flip ? -1.0f : 1.0f;
const float invert = cache->invert ? -1.0f : 1.0f;
float overlap = ups->overlap_factor;
@@ -2319,112 +2326,109 @@ static float brush_strength(const Sculpt *sd,
flip = 1.0f;
}
- /* Pressure final value after being tweaked depending on the brush. */
- float final_pressure;
-
switch (brush->sculpt_tool) {
case SCULPT_TOOL_CLAY:
- final_pressure = pow4f(pressure);
+ // pressure = pow4f(pressure);
overlap = (1.0f + overlap) / 2.0f;
- return 0.25f * alpha * flip * final_pressure * overlap * feather;
+ return 0.25f * alpha * flip * overlap * feather;
case SCULPT_TOOL_DRAW:
case SCULPT_TOOL_DRAW_SHARP:
case SCULPT_TOOL_LAYER:
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
case SCULPT_TOOL_DISPLACEMENT_ERASER:
- return alpha * pressure * overlap * feather;
+ return alpha * overlap * feather;
case SCULPT_TOOL_CLOTH:
if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
/* Grab deform uses the same falloff as a regular grab brush. */
return root_alpha * feather;
}
else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) {
- return root_alpha * feather * pressure * overlap;
+ return root_alpha * feather * overlap;
}
else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) {
/* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over
* the same vertices. */
- return 0.1f * alpha * flip * pressure * overlap * feather;
+ return 0.1f * alpha * flip * overlap * feather;
}
else {
/* Multiply by 10 by default to get a larger range of strength depending on the size of the
* brush and object. */
- return 10.0f * alpha * flip * pressure * overlap * feather;
+ return 10.0f * alpha * flip * overlap * feather;
}
case SCULPT_TOOL_DRAW_FACE_SETS:
- return alpha * pressure * overlap * feather;
+ return alpha * overlap * feather;
case SCULPT_TOOL_SLIDE_RELAX:
- return alpha * pressure * overlap * feather * 2.0f;
+ return alpha * overlap * feather * 2.0f;
case SCULPT_TOOL_PAINT:
- final_pressure = pressure * pressure;
- return final_pressure * overlap * feather;
+ // pressure = pressure * pressure;
+ return overlap * feather;
case SCULPT_TOOL_SMEAR:
case SCULPT_TOOL_DISPLACEMENT_SMEAR:
- return alpha * pressure * overlap * feather;
+ return alpha * overlap * feather;
case SCULPT_TOOL_CLAY_STRIPS:
/* Clay Strips needs less strength to compensate the curve. */
- final_pressure = powf(pressure, 1.5f);
- return alpha * flip * final_pressure * overlap * feather * 0.3f;
+ // pressure = powf(pressure, 1.5f);
+ return alpha * flip * overlap * feather * 0.3f;
case SCULPT_TOOL_CLAY_THUMB:
- final_pressure = pressure * pressure;
- return alpha * flip * final_pressure * overlap * feather * 1.3f;
+ // pressure = pressure * pressure;
+ return alpha * flip * overlap * feather * 1.3f;
case SCULPT_TOOL_MASK:
overlap = (1.0f + overlap) / 2.0f;
switch ((BrushMaskTool)brush->mask_tool) {
case BRUSH_MASK_DRAW:
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
case BRUSH_MASK_SMOOTH:
- return alpha * pressure * feather;
+ return alpha * feather;
}
BLI_assert_msg(0, "Not supposed to happen");
return 0.0f;
case SCULPT_TOOL_CREASE:
case SCULPT_TOOL_BLOB:
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
case SCULPT_TOOL_INFLATE:
if (flip > 0.0f) {
- return 0.250f * alpha * flip * pressure * overlap * feather;
+ return 0.250f * alpha * flip * overlap * feather;
}
else {
- return 0.125f * alpha * flip * pressure * overlap * feather;
+ return 0.125f * alpha * flip * overlap * feather;
}
case SCULPT_TOOL_MULTIPLANE_SCRAPE:
overlap = (1.0f + overlap) / 2.0f;
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
case SCULPT_TOOL_FLATTEN:
if (flip > 0.0f) {
overlap = (1.0f + overlap) / 2.0f;
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
}
else {
/* Reduce strength for DEEPEN, PEAKS, and CONTRAST. */
- return 0.5f * alpha * flip * pressure * overlap * feather;
+ return 0.5f * alpha * flip * overlap * feather;
}
case SCULPT_TOOL_SMOOTH:
- return flip * alpha * pressure * feather;
+ return flip * alpha * feather;
case SCULPT_TOOL_PINCH:
if (flip > 0.0f) {
- return alpha * flip * pressure * overlap * feather;
+ return alpha * flip * overlap * feather;
}
else {
- return 0.25f * alpha * flip * pressure * overlap * feather;
+ return 0.25f * alpha * flip * overlap * feather;
}
case SCULPT_TOOL_NUDGE:
overlap = (1.0f + overlap) / 2.0f;
- return alpha * pressure * overlap * feather;
+ return alpha * overlap * feather;
case SCULPT_TOOL_THUMB:
- return alpha * pressure * feather;
+ return alpha * feather;
case SCULPT_TOOL_SNAKE_HOOK:
return root_alpha * feather;
@@ -2433,7 +2437,7 @@ static float brush_strength(const Sculpt *sd,
return root_alpha * feather;
case SCULPT_TOOL_ROTATE:
- return alpha * pressure * feather;
+ return alpha * feather;
case SCULPT_TOOL_ELASTIC_DEFORM:
case SCULPT_TOOL_POSE:
@@ -4022,7 +4026,7 @@ static void do_symmetrical_brush_actions(Sculpt *sd,
float feather = calc_symmetry_feather(sd, ss->cache);
- cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings);
+ cache->bstrength = brush_strength(ss, sd, cache, feather, ups, paint_mode_settings);
cache->symmetry = symm;
/* `symm` is a bit combination of XYZ -
@@ -4285,6 +4289,8 @@ static void sculpt_update_cache_invariants(
ss->cache = cache;
+ cache->rng = BLI_rng_new((int)(PIL_check_seconds_timer() * 10000.0));
+
/* Set scaling adjustment. */
max_scale = 0.0f;
for (int i = 0; i < 3; i++) {
@@ -4639,7 +4645,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
sculpt_rake_data_update(&cache->rake_data, grab_location);
}
-static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush)
+ATTR_NO_OPT static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush)
{
cache->paint_brush.hardness = brush->hardness;
if (brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) {
@@ -4725,6 +4731,34 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
}
}
+ if (!cache->first_time && cache->radius != 0.0f) {
+ float mouse[2];
+ RNA_float_get_array(ptr, "mouse", mouse);
+
+ sub_v2_v2(mouse, cache->mouse);
+
+ cache->speed = len_v2(mouse) / cache->radius;
+ cache->angle = atan2(mouse[1], mouse[0]);
+ }
+
+ BrushMappingData mapdata;
+
+ mapdata.pressure = RNA_float_get(ptr, "pressure");
+ mapdata.xtilt = RNA_float_get(ptr, "x_tilt");
+ mapdata.ytilt = RNA_float_get(ptr, "y_tilt");
+ mapdata.stroke_t = cache->stroke_distance;
+ mapdata.random = BLI_rng_get_float(cache->rng);
+ mapdata.angle = cache->angle;
+ mapdata.speed = cache->speed;
+
+ printf("pressure: %.4f\n", mapdata.pressure);
+
+ if (cache->channels) {
+ BKE_brush_channelset_free(cache->channels);
+ }
+ cache->channels = BKE_brush_channelset_create_final(brush, scene, &mapdata);
+ cache->mapdata = mapdata;
+
/* Clay stabilized pressure. */
if (brush->sculpt_tool == SCULPT_TOOL_CLAY_THUMB) {
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) {
@@ -4742,7 +4776,8 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
}
}
- if (BKE_brush_use_size_pressure(brush) &&
+#if 0
+ if (BKE_brush_use_size_pressure(scene, brush) &&
paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) {
cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius);
cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get(
@@ -4752,6 +4787,19 @@ static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, Po
cache->radius = cache->initial_radius;
cache->dyntopo_pixel_radius = ups->initial_pixel_radius;
}
+#else
+ if (!BKE_brush_use_locked_size(scene, brush)) {
+ int radius = BKE_brush_channelset_int_get(cache->channels, size);
+ cache->radius = paint_calc_object_space_radius(
+ cache->vc, cache->true_initial_location, radius);
+
+ printf("radius: %d\n", radius);
+ }
+ else {
+ cache->radius = BKE_brush_channelset_float_get(cache->channels, unprojected_radius);
+ }
+
+#endif
sculpt_update_cache_paint_variants(cache, brush);
@@ -5200,7 +5248,7 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
ED_paint_tool_update_sticky_shading_color(C, ob);
}
-static void sculpt_restore_mesh(Sculpt *sd, Object *ob)
+static void sculpt_restore_mesh(Scene *scene, Sculpt *sd, Object *ob)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
@@ -5214,7 +5262,7 @@ static void sculpt_restore_mesh(Sculpt *sd, Object *ob)
/* Restore the mesh before continuing with anchored stroke. */
if ((brush->flag & BRUSH_ANCHORED) ||
(ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM) &&
- BKE_brush_use_size_pressure(brush)) ||
+ BKE_brush_use_size_pressure(scene, brush)) ||
(brush->flag & BRUSH_DRAG_DOT)) {
SculptUndoNode *unode = SCULPT_undo_get_first_node();
@@ -5508,7 +5556,7 @@ static void sculpt_stroke_update_step(bContext *C,
SCULPT_stroke_modifiers_check(C, ob, brush);
sculpt_update_cache_variants(C, sd, ob, itemptr);
- sculpt_restore_mesh(sd, ob);
+ sculpt_restore_mesh(CTX_data_scene(C), sd, ob);
if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) {
float object_space_constant_detail = 1.0f / (sd->constant_detail * mat4_to_scale(ob->obmat));
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index bf47b64d176..878932bce6d 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -7,6 +7,7 @@
#pragma once
+#include "DNA_brush_channel_types.h"
#include "DNA_brush_types.h"
#include "DNA_key_types.h"
#include "DNA_listBase.h"
@@ -14,6 +15,7 @@
#include "DNA_scene_types.h"
#include "DNA_vec_types.h"
+#include "BKE_brush_channel.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
#include "BLI_bitmap.h"
@@ -35,6 +37,8 @@ struct Object;
struct SculptUndoNode;
struct bContext;
struct PaintModeSettings;
+struct RNG;
+struct BrushChannelSet;
struct wmKeyConfig;
struct wmOperator;
struct wmOperatorType;
@@ -485,6 +489,7 @@ typedef struct FilterCache {
typedef struct StrokeCache {
/* Invariants */
float initial_radius;
+ float initial_raw_radius;
float scale[3];
int flag;
float clip_tolerance[3];
@@ -646,6 +651,11 @@ typedef struct StrokeCache {
rcti current_r; /* current redraw rectangle */
int stroke_id;
+
+ struct BrushChannelSet *channels;
+ struct RNG *rng;
+ float angle, speed;
+ BrushMappingData mapdata;
} StrokeCache;
/* -------------------------------------------------------------------- */
diff --git a/source/blender/makesdna/DNA_brush_channel_types.h b/source/blender/makesdna/DNA_brush_channel_types.h
new file mode 100644
index 00000000000..09c6a899051
--- /dev/null
+++ b/source/blender/makesdna/DNA_brush_channel_types.h
@@ -0,0 +1,206 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2011 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup DNA
+ *
+ * Structs used for the sculpt brush system
+ */
+#pragma once
+
+#include "DNA_color_types.h"
+#include "DNA_listBase.h"
+
+#include "BLI_assert.h"
+
+struct GHash;
+
+typedef struct BrushCurve {
+ CurveMapping *curve;
+
+ /** curve preset, see eBrushCurvePreset.
+ Note: this differs from BrushMappingDef's preset field
+ */
+ int preset;
+ char preset_slope_negative;
+ char _pad[3];
+} BrushCurve;
+
+/* Input mapping struct. An input mapping transform
+ stroke inputs intos outputs. Inputs can be device
+ events (like pen pressure/tilt) or synethesize
+ (cumulative stroke distance, random, etc).
+
+ Inspired by Krita.
+*/
+typedef struct BrushMapping {
+ /* note that we use a curve cache (see BKE_curvemapping_cache)
+ and copy on write semantics. BrushChannels are copied
+ extensively (mostly to cache input mappings and resolve
+ channel inheritance), to the point that copying the
+ channel curves was a problem.
+
+ */
+
+ BrushCurve curve;
+
+ float factor;
+ int blendmode; /* blendmode, a subset of the MA_BLEND_XXX enums*/
+
+ int flag, type;
+
+ float min, max;
+ float premultiply_factor; /** factor to premultiply input data with */
+
+ int mapfunc; /** mapping function, see eBrushMappingFunc. Most are periodic. */
+
+ /** threshold for BRUSH_MAPFUNC_CUTOFF and BRUSH_MAPFUNC_SQUARE mapping functions */
+ float func_cutoff;
+
+ /** controls whether this channel should inherit from scene defaults,
+ * see eBrushMappingInheritMode */
+ char inherit_mode, _pad[3];
+} BrushMapping;
+
+typedef struct BrushChannel {
+ struct BrushChannel *next, *prev;
+
+ char idname[64]; /* The RNA property name */
+ char uiname[64]; /** user-friendly name */
+ char *category; /** category; if NULL, def->category will be used */
+
+ struct BrushChannelType *def; /* Brush channel definition */
+
+ /*
+ Cached channel values.
+ */
+ float fvalue; /** floating point value */
+ int ivalue; /** stores integer, boolean, enum and bitmasks */
+ float vector[4]; /* stores 3 and 4 component vectors */
+
+ /* For curve channels. */
+ BrushCurve curve;
+
+ /* Input device mappings */
+ BrushMapping mappings[7]; /* dimension should always be BRUSH_MAPPING_MAX */
+
+ short type; /** eBrushChannelType */
+ short ui_order;
+ int flag; /** eBrushChannelFlag */
+ int ui_flag, evaluated_flag;
+ int active_mapping, _pad[1];
+} BrushChannel;
+
+typedef struct BrushChannelSet {
+ ListBase channels;
+ int channels_num, _pad[1];
+
+ void *channelmap; /** idname -> channel map. */
+} BrushChannelSet;
+
+#define BRUSH_CHANNEL_MAX_IDNAME sizeof(((BrushChannel){0}).idname)
+
+/* BrushMapping->flag */
+typedef enum eBrushMappingFlags {
+ BRUSH_MAPPING_ENABLED = 1 << 0,
+ BRUSH_MAPPING_INVERT = 1 << 1,
+ BRUSH_MAPPING_UI_EXPANDED = 1 << 2,
+} eBrushMappingFlags;
+
+/* BrushMapping->inherit_mode */
+typedef enum eBrushMappingInheritMode {
+ BRUSH_MAPPING_INHERIT_NEVER,
+ BRUSH_MAPPING_INHERIT_ALWAYS,
+ /* Use channel's inheritance mode. */
+ BRUSH_MAPPING_INHERIT_CHANNEL
+} eBrushMappingInheritMode;
+
+/* BrushMapping->mapfunc */
+typedef enum eBrushMappingFunc {
+ BRUSH_MAPFUNC_NONE,
+ BRUSH_MAPFUNC_SAW,
+ BRUSH_MAPFUNC_TENT,
+ BRUSH_MAPFUNC_COS,
+ BRUSH_MAPFUNC_CUTOFF,
+ BRUSH_MAPFUNC_SQUARE, /* square wave */
+} eBrushMappingFunc;
+
+/* Input device mapping types. */
+typedef enum eBrushMappingType {
+ BRUSH_MAPPING_PRESSURE = 0,
+ BRUSH_MAPPING_XTILT = 1,
+ BRUSH_MAPPING_YTILT = 2,
+ BRUSH_MAPPING_ANGLE = 3,
+ BRUSH_MAPPING_SPEED = 4,
+ BRUSH_MAPPING_RANDOM = 5,
+ BRUSH_MAPPING_STROKE_T = 6,
+ BRUSH_MAPPING_MAX = 7 // see BrushChannel.mappings
+} eBrushMappingType;
+
+BLI_STATIC_ASSERT(offsetof(BrushChannel, type) - offsetof(BrushChannel, mappings) ==
+ sizeof(BrushMapping) * BRUSH_MAPPING_MAX,
+ "BrushChannel.mappings must == BRUSH_MAPPING_MAX");
+
+typedef enum eBrushChannelFlag {
+ BRUSH_CHANNEL_INHERIT = 1 << 0,
+ BRUSH_CHANNEL_INHERIT_IF_UNSET = 1 << 1,
+ BRUSH_CHANNEL_NO_MAPPINGS = 1 << 2,
+ BRUSH_CHANNEL_UI_EXPANDED = 1 << 3,
+ BRUSH_CHANNEL_APPLY_MAPPING_TO_ALPHA = 1 << 4,
+ BRUSH_CHANNEL_NEEDS_EVALUATE = 1 << 5,
+
+ /* Set in scene channels; forces inheritance on brush properties. */
+ BRUSH_CHANNEL_FORCE_INHERIT = 1 << 6,
+
+ /* Set in local brush channels; ignores BRUSH_CHANNEL_FORCE_INHERIT. */
+ BRUSH_CHANNEL_IGNORE_FORCE_INHERIT = 1 << 7,
+} eBrushChannelFlag;
+
+typedef enum eBrushChannelUIFlag {
+ BRUSH_CHANNEL_SHOW_IN_WORKSPACE = 1 << 0,
+ /* Has user overriden this, used for version patching. */
+ BRUSH_CHANNEL_SHOW_IN_WORKSPACE_USER_SET = 1 << 1,
+ BRUSH_CHANNEL_SHOW_IN_HEADER = 1 << 2,
+ BRUSH_CHANNEL_SHOW_IN_HEADER_USER_SET = 1 << 3,
+ BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU = 1 << 4,
+ BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU_USER_SET = 1 << 5,
+} eBrushChannelUIFlag;
+
+// BrushChannelType->type
+typedef enum eBrushChannelType {
+ BRUSH_CHANNEL_TYPE_FLOAT = 1 << 0,
+ BRUSH_CHANNEL_TYPE_INT = 1 << 1,
+ BRUSH_CHANNEL_TYPE_ENUM = 1 << 2,
+ BRUSH_CHANNEL_TYPE_BITMASK = 1 << 3,
+ BRUSH_CHANNEL_TYPE_BOOL = 1 << 4,
+ BRUSH_CHANNEL_TYPE_VEC3 = 1 << 5,
+ BRUSH_CHANNEL_TYPE_VEC4 = 1 << 6,
+ BRUSH_CHANNEL_TYPE_CURVE = 1 << 7
+} eBrushChannelType;
+
+/* clang-format off */
+typedef enum eBrushChannelSubType {
+ BRUSH_CHANNEL_NONE,
+ BRUSH_CHANNEL_COLOR,
+ BRUSH_CHANNEL_FACTOR,
+ BRUSH_CHANNEL_PERCENT,
+ BRUSH_CHANNEL_PIXEL,
+ BRUSH_CHANNEL_ANGLE
+} eBrushChannelSubType;
+/* clang-format on */
diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h
index 8b889e17762..621fcc6e748 100644
--- a/source/blender/makesdna/DNA_brush_enums.h
+++ b/source/blender/makesdna/DNA_brush_enums.h
@@ -473,6 +473,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_BOUNDARY = 30,
SCULPT_TOOL_DISPLACEMENT_ERASER = 31,
SCULPT_TOOL_DISPLACEMENT_SMEAR = 32,
+ SCULPT_TOOL_MAX,
} eBrushSculptTool;
/** #Brush.uv_sculpt_tool */
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index a415122579e..48b41fe7083 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -20,6 +20,7 @@ struct CurveMapping;
struct Image;
struct MTex;
struct Material;
+struct BrushChannelSet;
typedef struct BrushClone {
/** Image for clone tool. */
@@ -393,6 +394,8 @@ typedef struct Brush {
float automasking_cavity_factor;
struct CurveMapping *automasking_cavity_curve;
+
+ struct BrushChannelSet *channels;
} Brush;
/* Struct to hold palette colors for sorting. */
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 28359038be5..1776376b8ce 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -46,6 +46,7 @@ struct SceneCollection;
struct World;
struct bGPdata;
struct bNodeTree;
+struct BrushChannelSet;
/* ************************************************************* */
/* Scene Data */
@@ -1578,6 +1579,8 @@ typedef struct ToolSettings {
/* Unified Paint Settings */
struct UnifiedPaintSettings unified_paint_settings;
+ struct BrushChannelSet *unified_channels;
+ IDProperty *unified_properties;
struct CurvePaintSettings curve_paint_settings;
@@ -1594,6 +1597,8 @@ typedef struct ToolSettings {
struct SequencerToolSettings *sequencer_tool_settings;
+ char brush_editor_mode;
+ char _pad9[7];
} ToolSettings;
/* *************************************************************** */
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index f028b199098..a4830dfed36 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -22,6 +22,7 @@ set(DEFSRC
rna_attribute.c
rna_boid.c
rna_brush.c
+ rna_brush_channels.c
rna_cachefile.c
rna_camera.c
rna_cloth.c
diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c
index 3592ecd84c8..aef585c6767 100644
--- a/source/blender/makesrna/intern/makesrna.c
+++ b/source/blender/makesrna/intern/makesrna.c
@@ -4496,6 +4496,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_asset.c", NULL, RNA_def_asset},
{"rna_boid.c", NULL, RNA_def_boid},
{"rna_brush.c", NULL, RNA_def_brush},
+ {"rna_brush_channels.c", NULL, RNA_def_brush_channels},
{"rna_cachefile.c", NULL, RNA_def_cachefile},
{"rna_camera.c", "rna_camera_api.c", RNA_def_camera},
{"rna_cloth.c", NULL, RNA_def_cloth},
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 137020ce3f8..e4bc3b3ffd3 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -23,6 +23,8 @@
#include "RNA_define.h"
#include "RNA_enum_types.h"
+#include "BKE_brush_channel.h"
+
#include "rna_internal.h"
#include "IMB_imbuf.h"
@@ -785,6 +787,8 @@ static void rna_Brush_set_size(PointerRNA *ptr, int value)
/* scale unprojected radius so it stays consistent with brush size */
BKE_brush_scale_unprojected_radius(&brush->unprojected_radius, value, brush->size);
brush->size = value;
+
+ BKE_brush_channelset_mark_update(brush->channels, size);
}
static void rna_Brush_use_gradient_set(PointerRNA *ptr, int value)
@@ -3541,6 +3545,16 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Gradient", "");
RNA_def_property_update(prop, 0, "rna_Brush_update");
+ prop = RNA_def_property(srna, "channels", PROP_COLLECTION, 0);
+ RNA_def_property_collection_sdna(prop, NULL, "channels", NULL);
+ RNA_def_property_struct_type(prop, "BrushChannel");
+ RNA_def_property_ui_text(prop, "Channels", "");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+ // RNA_def_property_clear_flag(prop, PROP_PTR_NO_OWNERSHIP);
+ RNA_def_property_override_flag(
+ prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY | PROPOVERRIDE_LIBRARY_INSERTION);
+ RNA_def_brush_channelset(brna, prop, "channels", "Brush");
+
/* gradient source */
prop = RNA_def_property(srna, "gradient_stroke_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_gradient_items);
diff --git a/source/blender/makesrna/intern/rna_brush_channels.c b/source/blender/makesrna/intern/rna_brush_channels.c
new file mode 100644
index 00000000000..09d2c412f3f
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_brush_channels.c
@@ -0,0 +1,890 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup RNA
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "DNA_ID_enums.h"
+#include "DNA_brush_channel_types.h"
+#include "DNA_brush_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_listBase.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_texture_types.h"
+#include "DNA_workspace_types.h"
+
+#include "BLI_assert.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "rna_internal.h"
+
+#include "IMB_imbuf.h"
+
+#include "BKE_brush.h"
+#include "BKE_brush_channel.h"
+#include "BKE_colortools.h"
+
+#include "WM_types.h"
+
+static EnumPropertyItem null_enum[2] = {{0, "null", ICON_NONE, "null"}, {0, NULL, 0, NULL, NULL}};
+
+#ifdef RNA_RUNTIME
+
+# include "RNA_access.h"
+
+# if 0
+static void rna_brushchannel_update(Scene *scene, Brush *brush)
+{
+}
+
+void rna_BrushChannel_update(Main *bmain, Scene *scene, PointerRNA *ptr)
+{
+ switch (GS(ptr->owner_id->name)) {
+ case ID_SCE: {
+ ToolSettings *ts = scene->toolsettings;
+ Paint *paints[] = {
+ &ts->sculpt,
+ &ts->
+ };
+ int paints_num = ARRAY_SIZE(paints);
+
+ break;
+ }
+ case ID_BR: {
+ Brush *brush = (Brush *)ptr->owner_id;
+ rna_brushchannel_update(scene, brush);
+ break;
+ }
+ default:
+ printf("%s: Invalid PointerRNA, unexpected owner_id \"%s\"\n", __func__, ptr->owner_id->name);
+ return;
+ }
+}
+# endif
+
+void rna_BrushChannel_update_tooltip(PointerRNA *ptr, const char *propname)
+{
+ BrushChannel *ch = (BrushChannel *)ptr->data;
+ PropertyRNA *prop = RNA_struct_find_property(ptr, propname);
+
+ if (prop) {
+ if (prop->description) {
+ // MEM_SAFE_FREE(prop->description);
+ }
+
+ prop->description = ch->def->tooltip;
+ }
+}
+
+BrushChannelSet *rna_BrushChannelSet_get_set(struct PointerRNA *ptr)
+{
+ BrushChannelSet *chset;
+ ID *id = ptr->owner_id;
+
+ switch (GS(id->name)) {
+ case ID_BR:
+ chset = ((Brush *)id)->channels;
+ break;
+ case ID_SCE: {
+ Scene *scene = (Scene *)id;
+
+ if (!scene->toolsettings) {
+ return NULL;
+ }
+
+ chset = scene->toolsettings->unified_channels;
+ break;
+ }
+ default:
+ return NULL;
+ }
+
+ return chset;
+}
+
+int rna_BrushChannelSet_channels_begin(CollectionPropertyIterator *iter, struct PointerRNA *ptr)
+{
+ BrushChannelSet *chset = rna_BrushChannelSet_get_set(ptr);
+
+ if (!chset) {
+ return 0;
+ }
+
+ rna_iterator_listbase_begin(iter, &chset->channels, NULL);
+
+ return 1;
+}
+
+static BrushChannel *get_paired_radius_channel(PointerRNA *rna)
+{
+ BrushChannel *ch = rna->data;
+
+ bool is_radius = STREQ(ch->idname, "size");
+ bool is_unproj = STREQ(ch->idname, "unprojected_radius");
+
+ /*
+ the way the old brush system split view and scene
+ radii but presented them as the same to the user,
+ and also to parts of the API is proving difficult
+ to disentangle. . . - joeedh
+ */
+ if ((is_radius || is_unproj) && rna->owner_id) {
+ BrushChannelSet *chset = NULL;
+
+ if (GS(rna->owner_id->name) == ID_SCE) {
+ Scene *scene = ((Scene *)rna->owner_id);
+
+ if (scene->toolsettings && scene->toolsettings->sculpt) {
+ chset = scene->toolsettings->unified_channels;
+ }
+ }
+ else if (GS(rna->owner_id->name) == ID_BR) {
+ chset = ((Brush *)rna->owner_id)->channels;
+ }
+
+ if (!chset) {
+ return NULL;
+ }
+
+ return is_radius ? BKE_brush_channelset_lookup(chset, unprojected_radius) :
+ BKE_brush_channelset_lookup(chset, size);
+ }
+
+ return NULL;
+}
+
+static void rna_brushchannel_paired_flag_set(PointerRNA *rna, int flag, bool value)
+{
+ BrushChannel *ch = (BrushChannel *)rna->data;
+ BrushChannel *ch2 = get_paired_radius_channel(rna);
+
+ if (value) {
+ ch->flag |= flag;
+
+ if (ch2) {
+ ch2->flag |= flag;
+ }
+ }
+ else {
+ ch->flag &= ~flag;
+
+ if (ch2) {
+ ch2->flag &= ~flag;
+ }
+ }
+}
+
+bool rna_brushchannel_paired_flag_get(PointerRNA *rna, int flag)
+{
+ BrushChannel *ch = (BrushChannel *)rna->data;
+
+ return ch->flag & flag;
+}
+
+void rna_BrushChannel_inherit_set(PointerRNA *rna, bool value)
+{
+ rna_brushchannel_paired_flag_set(rna, BRUSH_CHANNEL_INHERIT, value);
+}
+
+bool rna_BrushChannel_inherit_get(PointerRNA *rna)
+{
+ return rna_brushchannel_paired_flag_get(rna, BRUSH_CHANNEL_INHERIT);
+}
+
+void rna_BrushChannel_unified_set(PointerRNA *rna, bool value)
+{
+ rna_brushchannel_paired_flag_set(rna, BRUSH_CHANNEL_FORCE_INHERIT, value);
+}
+
+bool rna_BrushChannel_unified_get(PointerRNA *rna)
+{
+ return rna_brushchannel_paired_flag_get(rna, BRUSH_CHANNEL_FORCE_INHERIT);
+}
+
+void rna_BrushChannel_disable_unified_set(PointerRNA *rna, bool value)
+{
+ rna_brushchannel_paired_flag_set(rna, BRUSH_CHANNEL_IGNORE_FORCE_INHERIT, value);
+}
+
+bool rna_BrushChannel_disable_unified_get(PointerRNA *rna)
+{
+ return rna_brushchannel_paired_flag_get(rna, BRUSH_CHANNEL_IGNORE_FORCE_INHERIT);
+}
+
+PointerRNA rna_BrushMapping_curve_get(PointerRNA *ptr)
+{
+ BrushMapping *mapping = (BrushMapping *)ptr->data;
+
+ return rna_pointer_inherit_refine(ptr, &RNA_BrushCurve, &mapping->curve);
+}
+
+PointerRNA rna_BrushCurve_curve_get(PointerRNA *ptr)
+{
+ BrushCurve *curve = (BrushCurve *)ptr->data;
+
+ if (!curve->curve) {
+ curve->curve = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
+ rctf bounds = {0.0f, 1.0f, 0.0f, 1.0f};
+ BKE_curvemap_reset(curve->curve->cm, &bounds, CURVE_PRESET_LINE, CURVEMAP_SLOPE_POSITIVE);
+ }
+
+ return rna_pointer_inherit_refine(ptr, &RNA_CurveMapping, curve->curve);
+}
+
+bool rna_BrushMapping_inherit_get(PointerRNA *ptr)
+{
+ BrushMapping *mp = (BrushMapping *)ptr->data;
+
+ return mp->inherit_mode;
+}
+
+void rna_BrushMapping_inherit_set(PointerRNA *ptr, bool val)
+{
+ BrushMapping *mp = (BrushMapping *)ptr->data;
+
+ if (val) {
+ mp->inherit_mode = BRUSH_MAPPING_INHERIT_ALWAYS;
+ }
+ else {
+ mp->inherit_mode = BRUSH_MAPPING_INHERIT_NEVER;
+ }
+}
+
+int rna_BrushChannel_mappings_begin(CollectionPropertyIterator *iter, struct PointerRNA *ptr)
+{
+ BrushChannel *ch = ptr->data;
+
+ rna_iterator_array_begin(
+ iter, ch->mappings, sizeof(BrushMapping), BRUSH_MAPPING_MAX, false, NULL);
+
+ return 1;
+}
+
+int rna_BrushChannel_mappings_lookupstring(PointerRNA *rna, const char *key, PointerRNA *r_ptr)
+{
+ BrushChannel *ch = (BrushChannel *)rna->data;
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ if (STREQ(key, BKE_brush_mapping_type_to_typename(i))) {
+ if (r_ptr) {
+ *r_ptr = rna_pointer_inherit_refine(rna, &RNA_BrushMapping, ch->mappings + i);
+ }
+
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int rna_BrushChannel_mappings_length(PointerRNA *ptr)
+{
+ return BRUSH_MAPPING_MAX;
+}
+
+int lookup_icon_id(const char *icon)
+{
+ const EnumPropertyItem *item = rna_enum_icon_items;
+ int i = 0;
+
+ while (item->identifier) {
+ if (STREQ(item->identifier, icon)) {
+ return i;
+ }
+
+ item++;
+ i++;
+ }
+
+ return ICON_NONE;
+}
+
+static int rna_enum_check_separator(CollectionPropertyIterator *UNUSED(iter), void *data)
+{
+ EnumPropertyItem *item = (EnumPropertyItem *)data;
+
+ return (item->identifier[0] == 0);
+}
+
+char *rna_BrushChannel_rnapath(const PointerRNA *ptr)
+{
+ BrushChannel *ch = (BrushChannel *)ptr->data;
+
+ if (!ptr->owner_id) {
+ return NULL;
+ }
+
+ if (GS(ptr->owner_id->name) == ID_BR) {
+ return BLI_sprintfN("channels[\"%s\"]", ch->idname);
+ }
+ else if (GS(ptr->owner_id->name) == ID_SCE) {
+ return BLI_sprintfN("tool_settings.unified_channels[\"%s\"]", ch->idname);
+ }
+ else {
+ return NULL;
+ }
+}
+
+char *rna_BrushMapping_rnapath(const PointerRNA *ptr)
+{
+ BrushMapping *mp = (BrushMapping *)ptr->data;
+
+ if (!ptr->owner_id) {
+ return NULL;
+ }
+
+ BrushChannelSet *chset;
+
+ switch (GS(ptr->owner_id->name)) {
+ case ID_SCE: {
+ Scene *scene = (Scene *)ptr->owner_id;
+ chset = scene->toolsettings->unified_channels;
+ break;
+ }
+ case ID_BR: {
+ Brush *brush = (Brush *)ptr->owner_id;
+ chset = brush->channels;
+ break;
+ }
+ default:
+ BLI_assert_unreachable();
+ return NULL;
+ }
+
+ BrushChannel *active_ch = NULL;
+
+ LISTBASE_FOREACH (BrushChannel *, ch, &chset->channels) {
+ if (active_ch) {
+ break;
+ }
+
+ for (int i = 0; i < BRUSH_MAPPING_MAX; i++) {
+ if (mp == ch->mappings + i) {
+ active_ch = ch;
+ break;
+ }
+ }
+ }
+
+ if (!active_ch) {
+ BLI_assert_unreachable();
+ return NULL;
+ }
+
+ switch (GS(ptr->owner_id->name)) {
+ case ID_SCE:
+ return BLI_sprintfN("tool_settings.unified_channels[\"%s\"].mappings[\"%s\"]",
+ active_ch->idname,
+ BKE_brush_mapping_type_to_typename(mp->type));
+ case ID_BR:
+ return BLI_sprintfN("channels[\"%s\"].mappings[\"%s\"]",
+ active_ch->idname,
+ BKE_brush_mapping_type_to_typename(mp->type));
+ default:
+ BLI_assert_unreachable();
+ return NULL;
+ }
+}
+
+void rna_BrushChannelSet_ensure(ID *id, const char *idname)
+{
+ PointerRNA ptr;
+
+ ptr.owner_id = id;
+ ptr.data = NULL;
+ ptr.type = NULL;
+
+ BrushChannelSet *chset = rna_BrushChannelSet_get_set(&ptr);
+
+ if (chset) {
+ _BKE_brush_channelset_ensure(chset, idname);
+ }
+}
+
+int rna_BrushChannelSet_length(PointerRNA *ptr)
+{
+ BrushChannelSet *chset = rna_BrushChannelSet_get_set(ptr);
+ // BrushChannelSet *chset = (BrushChannelSet *)ptr->data;
+
+ return chset->channels_num;
+}
+
+void rna_BrushChannel_category_get(PointerRNA *ptr, char *value)
+{
+ strcpy(value, BKE_brush_channel_category_get((BrushChannel *)ptr->data));
+}
+
+int rna_BrushChannel_category_length(PointerRNA *ptr)
+{
+ return strlen(BKE_brush_channel_category_get((BrushChannel *)ptr->data));
+}
+
+void rna_BrushChannel_rna_path_get(PointerRNA *ptr, char *value)
+{
+ strcpy(value, BKE_brush_channel_rna_path(ptr->owner_id, (BrushChannel *)ptr->data));
+}
+
+int rna_BrushChannel_rna_path_length(PointerRNA *ptr)
+{
+ return strlen(BKE_brush_channel_rna_path(ptr->owner_id, (BrushChannel *)ptr->data));
+}
+
+int rna_BrushChannel_factor_value_editable(PointerRNA *ptr, const char **r_info)
+{
+ return 1;
+}
+
+bool rna_BrushChannel_get_is_color(PointerRNA *ptr)
+{
+ BrushChannel *ch = (BrushChannel *)ptr->data;
+
+ return ch && ch->def ? ch->def->subtype == BRUSH_CHANNEL_COLOR : false;
+}
+
+void rna_BrushChannel_category_set(PointerRNA *ptr, const char *value)
+{
+ BKE_brush_channel_category_set((BrushChannel *)ptr->data, value);
+}
+
+static void channel_uiflag_set(PointerRNA *ptr, bool value, int flag, int user_set_flag)
+{
+ BrushChannel *ch = (BrushChannel *)ptr->data;
+
+ if (value) {
+ ch->ui_flag |= flag;
+ }
+ else {
+ ch->ui_flag &= ~flag;
+ }
+
+ ch->ui_flag |= user_set_flag;
+}
+
+void rna_BrushChannel_show_in_header_set(PointerRNA *ptr, bool value)
+{
+ channel_uiflag_set(
+ ptr, value, BRUSH_CHANNEL_SHOW_IN_HEADER, BRUSH_CHANNEL_SHOW_IN_HEADER_USER_SET);
+}
+
+void rna_BrushChannel_show_in_workspace_set(PointerRNA *ptr, bool value)
+{
+ channel_uiflag_set(
+ ptr, value, BRUSH_CHANNEL_SHOW_IN_WORKSPACE, BRUSH_CHANNEL_SHOW_IN_WORKSPACE_USER_SET);
+}
+
+void rna_BrushChannel_show_in_context_menu_set(PointerRNA *ptr, bool value)
+{
+ channel_uiflag_set(
+ ptr, value, BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU, BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU_USER_SET);
+}
+
+#else
+
+static EnumPropertyItem mapping_type_items[] = {
+ {BRUSH_MAPPING_PRESSURE, "PRESSURE", ICON_NONE, "Pressure"},
+ {BRUSH_MAPPING_XTILT, "XTILT", ICON_NONE, "X Tilt"},
+ {BRUSH_MAPPING_YTILT, "YTILT", ICON_NONE, "Y Tilt"},
+ {BRUSH_MAPPING_ANGLE, "ANGLE", ICON_NONE, "Angle"},
+ {BRUSH_MAPPING_SPEED, "SPEED", ICON_NONE, "Speed"},
+ {BRUSH_MAPPING_RANDOM, "RANDOM", ICON_NONE, "Random"},
+ {BRUSH_MAPPING_STROKE_T, "DISTANCE", ICON_NONE, "Distance"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+void RNA_def_brush_mapping(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "BrushMapping", NULL);
+ RNA_def_struct_sdna(srna, "BrushMapping");
+ RNA_def_struct_ui_text(srna, "Brush Mapping", "Brush Mapping");
+ RNA_def_struct_path_func(srna, "rna_BrushMapping_rnapath");
+
+ prop = RNA_def_property(srna, "factor", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "factor");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Factor", "Mapping factor");
+
+ prop = RNA_def_property(srna, "premultiply", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "premultiply_factor");
+ RNA_def_property_range(prop, -100000, 100000);
+ RNA_def_property_ui_range(prop, -100, 100, 0.01, 3);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Pre-Multiply", "Multiply input data by this amount");
+
+ prop = RNA_def_property(srna, "func_cutoff", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_float_sdna(prop, NULL, "func_cutoff");
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Cutoff", "Cutoff for square and cutoff modes");
+
+ prop = RNA_def_property(srna, "min", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "min");
+ RNA_def_property_range(prop, -100000, 100000);
+ RNA_def_property_ui_range(prop, -2.0, 2.0, 0.001, 3);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Min", "");
+
+ prop = RNA_def_property(srna, "max", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "max");
+ RNA_def_property_range(prop, -100000, 100000);
+ RNA_def_property_ui_range(prop, -2.0, 2.0, 0.001, 3);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Max", "");
+
+ static EnumPropertyItem inherit_mode_items[] = {
+ {BRUSH_MAPPING_INHERIT_NEVER, "NEVER", ICON_NONE, "Never", "Never use unified settings."},
+ {BRUSH_MAPPING_INHERIT_ALWAYS,
+ "ALWAYS",
+ ICON_NONE,
+ "Always",
+ "Always use settings from unified channel."},
+ {BRUSH_MAPPING_INHERIT_CHANNEL,
+ "USE_CHANNEL",
+ ICON_NONE,
+ "If Enabled",
+ "Use unified settings if enabled in channel that owns this mapping."},
+ {0, NULL, 0, NULL, NULL}};
+
+ prop = RNA_def_property(srna, "inherit_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "inherit_mode");
+ RNA_def_property_enum_items(prop, inherit_mode_items);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+
+ prop = RNA_def_property(srna, "inherit", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "inherit_mode", BRUSH_MAPPING_INHERIT_ALWAYS);
+ RNA_def_property_ui_text(prop, "Inherit", "Inherit from scene channel");
+ RNA_def_property_boolean_funcs(
+ prop, "rna_BrushMapping_inherit_get", "rna_BrushMapping_inherit_set");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+
+ prop = RNA_def_property(srna, "curve", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "BrushCurve");
+ RNA_def_property_ui_text(prop, "Curve Sensitivity", "Curve used for the sensitivity");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_pointer_funcs(prop, "rna_BrushMapping_curve_get", NULL, NULL, NULL);
+
+ prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "type");
+ RNA_def_property_enum_items(prop, mapping_type_items);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE | PROP_ANIMATABLE);
+ RNA_def_property_ui_text(prop, "Type", "Channel Type");
+
+ prop = RNA_def_property(srna, "enabled", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_MAPPING_ENABLED);
+ RNA_def_property_ui_icon(prop, ICON_STYLUS_PRESSURE, 0);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Enabled", "Input Mapping Is Enabled");
+
+ prop = RNA_def_property(srna, "invert", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_MAPPING_INVERT);
+ RNA_def_property_ui_icon(prop, ICON_ARROW_LEFTRIGHT, 0);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Enabled", "Input Mapping Is Enabled");
+
+ static EnumPropertyItem blend_items[] = {
+ {MA_RAMP_BLEND, "MIX", ICON_NONE, "Mix", ""},
+ {MA_RAMP_MULT, "MULTIPLY", ICON_NONE, "Multiply", ""},
+ {MA_RAMP_DIV, "DIVIDE", ICON_NONE, "Divide", ""},
+ {MA_RAMP_ADD, "ADD", ICON_NONE, "Add", ""},
+ {MA_RAMP_SUB, "SUBTRACT", ICON_NONE, "Subtract", ""},
+ {MA_RAMP_DIFF, "DIFFERENCE", ICON_NONE, "Difference", ""},
+ {0, NULL, 0, NULL, NULL}};
+ prop = RNA_def_property(srna, "blendmode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, blend_items);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Blend Mode", "Input mapping blend mode");
+
+ static EnumPropertyItem mapfunc_items[] = {
+ {BRUSH_MAPFUNC_NONE, "NONE", ICON_NONE, "None", "Pass data through unmodified"},
+ {BRUSH_MAPFUNC_SQUARE, "SQUARE", ICON_NONE, "Square", "Square wave"},
+ {BRUSH_MAPFUNC_SAW, "SAW", ICON_NONE, "Saw", "Sawtooth wave"},
+ {BRUSH_MAPFUNC_TENT, "TENT", ICON_NONE, "Tent", "Tent wave"},
+ {BRUSH_MAPFUNC_COS, "COS", ICON_NONE, "Cos", "Cosine wave"},
+ {BRUSH_MAPFUNC_CUTOFF, "CUTOFF", ICON_NONE, "Cutoff", "Inverts data and cuts off at 1.0"},
+ {0, NULL, 0, NULL, NULL}};
+
+ prop = RNA_def_property(srna, "mapfunc", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mapfunc_items);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Function", "Input data function");
+
+ prop = RNA_def_property(srna, "ui_expanded", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_MAPPING_UI_EXPANDED);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Expanded", "View advanced properties");
+}
+
+extern BrushChannelType *brush_builtin_channels;
+extern const int builtin_channel_len;
+
+EnumPropertyItem channel_types[] = {{BRUSH_CHANNEL_TYPE_FLOAT, "FLOAT", ICON_NONE, "Float"},
+ {BRUSH_CHANNEL_TYPE_INT, "INT", ICON_NONE, "Int"},
+ {BRUSH_CHANNEL_TYPE_ENUM, "ENUM", ICON_NONE, "Enum"},
+ {BRUSH_CHANNEL_TYPE_BITMASK, "BITMASK", ICON_NONE, "Bitmask"},
+ {BRUSH_CHANNEL_TYPE_BOOL, "BOOL", ICON_NONE, "Boolean"},
+ {BRUSH_CHANNEL_TYPE_VEC3, "VEC3", ICON_NONE, "Color3"},
+ {BRUSH_CHANNEL_TYPE_VEC4, "VEC4", ICON_NONE, "Color4"},
+ {BRUSH_CHANNEL_TYPE_CURVE, "CURVE", ICON_NONE, "Curve"},
+ {0, NULL, 0, NULL, NULL}};
+
+// getting weird link errors here
+// extern const EnumPropertyItem brush_curve_preset_items[];
+
+static const EnumPropertyItem brush_curve_preset_items[] = {
+ {BRUSH_CURVE_CUSTOM, "CUSTOM", ICON_RNDCURVE, "Custom", ""},
+ {BRUSH_CURVE_SMOOTH, "SMOOTH", ICON_SMOOTHCURVE, "Smooth", ""},
+ {BRUSH_CURVE_SMOOTHER, "SMOOTHER", ICON_SMOOTHCURVE, "Smoother", ""},
+ {BRUSH_CURVE_SPHERE, "SPHERE", ICON_SPHERECURVE, "Sphere", ""},
+ {BRUSH_CURVE_ROOT, "ROOT", ICON_ROOTCURVE, "Root", ""},
+ {BRUSH_CURVE_SHARP, "SHARP", ICON_SHARPCURVE, "Sharp", ""},
+ {BRUSH_CURVE_LIN, "LIN", ICON_LINCURVE, "Linear", ""},
+ {BRUSH_CURVE_POW4, "POW4", ICON_SHARPCURVE, "Sharper", ""},
+ {BRUSH_CURVE_INVSQUARE, "INVSQUARE", ICON_INVERSESQUARECURVE, "Inverse Square", ""},
+ {BRUSH_CURVE_CONSTANT, "CONSTANT", ICON_NOCURVE, "Constant", ""},
+ {0, NULL, 0, NULL, NULL},
+};
+
+void RNA_def_brush_curve(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "BrushCurve", NULL);
+ RNA_def_struct_sdna(srna, "BrushCurve");
+ RNA_def_struct_ui_text(srna, "Brush Curve", "Brush Curve");
+
+ prop = RNA_def_property(srna, "curve_preset", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, "BrushCurve", "preset");
+ RNA_def_property_enum_items(prop, brush_curve_preset_items);
+ RNA_def_property_ui_text(prop, "Curve Pre set", "");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "curve", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "CurveMapping");
+ RNA_def_property_ui_text(prop, "Curve Sensitivity", "Curve used for the sensitivity");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_pointer_funcs(prop, "rna_BrushCurve_curve_get", NULL, NULL, NULL);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "preset_slope_negative", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Negative Slope", "");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+}
+
+void RNA_def_brush_channel(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "BrushChannel", NULL);
+ RNA_def_struct_sdna(srna, "BrushChannel");
+ RNA_def_struct_ui_text(srna, "Brush Channel", "Brush Channel");
+ RNA_def_struct_path_func(srna, "rna_BrushChannel_rnapath");
+ // RNA_def_struct_refine_func(srna, "rna_BrushChannel_refine");
+
+ prop = RNA_def_property(srna, "idname", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, "BrushChannel", "idname");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE | PROP_ANIMATABLE);
+
+ RNA_def_struct_name_property(srna, prop);
+
+ prop = RNA_def_property(srna, "rna_path", PROP_STRING, PROP_NONE);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE | PROP_ANIMATABLE);
+ RNA_def_property_string_funcs(
+ prop, "rna_BrushChannel_rna_path_get", "rna_BrushChannel_rna_path_length", NULL);
+
+ prop = RNA_def_property(srna, "category", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_funcs(prop,
+ "rna_BrushChannel_category_get",
+ "rna_BrushChannel_category_length",
+ "rna_BrushChannel_category_set");
+
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, "BrushChannel", "uiname");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE | PROP_ANIMATABLE);
+ RNA_def_property_ui_text(prop, "Name", "Channel name");
+
+ prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, "BrushChannel", "type");
+ RNA_def_property_enum_items(prop, channel_types);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE | PROP_ANIMATABLE);
+ RNA_def_property_ui_text(prop, "Type", "Value Type");
+
+ prop = RNA_def_property(srna, "ui_order", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, "BrushChannel", "ui_order");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(prop, "Ordering", "Order of brush channel in panels and the header");
+
+ prop = RNA_def_property(srna, "inherit", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Inherit", "Inherit from scene defaults");
+ RNA_def_property_boolean_funcs(
+ prop, "rna_BrushChannel_inherit_get", "rna_BrushChannel_inherit_set");
+
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ prop = RNA_def_property(srna, "disable_unified", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop,
+ "Disable Unified",
+ "Don't allow unified settings for this channel. Local brush setting.");
+ RNA_def_property_boolean_funcs(
+ prop, "rna_BrushChannel_disable_unified_get", "rna_BrushChannel_disable_unified_set");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "unified", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Unified", "Force brushes to use default properties");
+ RNA_def_property_boolean_funcs(
+ prop, "rna_BrushChannel_unified_get", "rna_BrushChannel_unified_set");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "show_in_header", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, "BrushChannel", "ui_flag", BRUSH_CHANNEL_SHOW_IN_HEADER);
+ RNA_def_property_ui_text(prop, "In Header", "Show in header");
+ RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_boolean_funcs(prop, NULL, "rna_BrushChannel_show_in_header_set");
+
+ prop = RNA_def_property(srna, "show_in_workspace", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, "BrushChannel", "ui_flag", BRUSH_CHANNEL_SHOW_IN_WORKSPACE);
+ RNA_def_property_ui_text(prop, "In Workspace", "Show in workspace");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_boolean_funcs(prop, NULL, "rna_BrushChannel_show_in_workspace_set");
+
+ prop = RNA_def_property(srna, "show_in_context_menu", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(
+ prop, "BrushChannel", "ui_flag", BRUSH_CHANNEL_SHOW_IN_CONTEXT_MENU);
+ RNA_def_property_ui_text(prop, "In Workspace", "Show in workspace");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_boolean_funcs(prop, NULL, "rna_BrushChannel_show_in_context_menu_set");
+
+ prop = RNA_def_property(srna, "is_color", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Is Color", "Is this channel a color");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_boolean_funcs(prop, "rna_BrushChannel_get_is_color", NULL);
+
+ prop = RNA_def_property(srna, "ui_expanded", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, "BrushChannel", "flag", BRUSH_CHANNEL_UI_EXPANDED);
+ RNA_def_property_ui_text(prop, "Expanded", "View advanced properties");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "inherit_if_unset", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, "BrushChannel", "flag", BRUSH_CHANNEL_INHERIT_IF_UNSET);
+ RNA_def_property_ui_text(prop, "Combine", "Combine with default settings");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "mappings", PROP_COLLECTION, PROP_NONE);
+ // RNA_def_property_collection_sdna(prop, "BrushChannel", "mappings", NULL);
+ RNA_def_property_collection_funcs(prop,
+ "rna_BrushChannel_mappings_begin",
+ "rna_iterator_array_next",
+ "rna_iterator_array_end",
+ "rna_iterator_array_get",
+ "rna_BrushChannel_mappings_length",
+ NULL,
+ "rna_BrushChannel_mappings_lookupstring",
+ NULL);
+ RNA_def_property_struct_type(prop, "BrushMapping");
+
+ prop = RNA_def_property(srna, "curve", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "BrushCurve");
+ RNA_def_property_ui_text(prop, "Curve", "Curve");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+
+ prop = RNA_def_property(srna, "active_mapping", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mapping_type_items);
+ RNA_def_property_enum_sdna(prop, NULL, "active_mapping");
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_ui_text(prop, "Active Mapping", "");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+}
+
+void RNA_def_brush_channelset(BlenderRNA *brna,
+ PropertyRNA *cprop,
+ const char *propname,
+ const char *type_prefix)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+ FunctionRNA *func;
+ PropertyRNA *parm;
+
+ char buf[256], *name;
+ sprintf(buf, "%sBrushChannels", type_prefix);
+ name = strdup(buf);
+
+ RNA_def_property_srna(cprop, name);
+
+ srna = RNA_def_struct(brna, name, NULL);
+ RNA_def_struct_sdna(srna, "BrushChannelSet");
+ RNA_def_struct_ui_text(srna, "Brush Channels", "Collection of brush channels");
+
+ // srna = RNA_def_struct(brna, "BrushChannelSet", NULL);
+ // RNA_def_struct_sdna(srna, "BrushChannelSet");
+ // RNA_def_struct_ui_text(srna, "Channel Set", "Brush Channel Collection");
+
+ func = RNA_def_function(srna, "ensure", "rna_BrushChannelSet_ensure");
+ RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_NO_SELF);
+
+ parm = RNA_def_string(func, "channel", NULL, 64, "Channel", "");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+
+ // RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+
+ // prop = RNA_def_property(srna, "channels", PROP_COLLECTION, PROP_NONE);
+ prop = cprop;
+
+ RNA_def_property_collection_sdna(prop, NULL, propname, NULL);
+ RNA_def_property_collection_funcs(prop,
+ "rna_BrushChannelSet_channels_begin",
+ "rna_iterator_listbase_next",
+ "rna_iterator_listbase_end",
+ "rna_iterator_listbase_get",
+ "rna_BrushChannelSet_length",
+ NULL,
+ NULL,
+ NULL);
+ RNA_def_property_struct_type(prop, "BrushChannel");
+
+ RNA_def_property_clear_flag(prop, PROP_PTR_NO_OWNERSHIP);
+ RNA_def_property_flag(prop, PROP_THICK_WRAP | PROP_DYNAMIC);
+ RNA_def_property_override_flag(
+ prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY | PROPOVERRIDE_LIBRARY_INSERTION);
+}
+
+void RNA_def_brush_channels(BlenderRNA *brna)
+{
+ RNA_def_brush_curve(brna);
+ RNA_def_brush_mapping(brna);
+ RNA_def_brush_channel(brna);
+}
+#endif
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index ea829e5cd86..f437f3b698f 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -31,6 +31,7 @@ struct Object;
struct ReportList;
struct SDNA;
struct ViewLayer;
+struct BrushChannel;
/* Data structures used during define */
@@ -146,6 +147,11 @@ void RNA_def_attribute(struct BlenderRNA *brna);
void RNA_def_asset(struct BlenderRNA *brna);
void RNA_def_boid(struct BlenderRNA *brna);
void RNA_def_brush(struct BlenderRNA *brna);
+void RNA_def_brush_channels(struct BlenderRNA *brna);
+void RNA_def_brush_channelset(BlenderRNA *brna,
+ PropertyRNA *cprop,
+ const char *propname,
+ const char *type_prefix);
void RNA_def_cachefile(struct BlenderRNA *brna);
void RNA_def_camera(struct BlenderRNA *brna);
void RNA_def_cloth(struct BlenderRNA *brna);
@@ -412,6 +418,14 @@ char *rna_TextureSlot_path(const struct PointerRNA *ptr);
char *rna_Node_ImageUser_path(const struct PointerRNA *ptr);
char *rna_CameraBackgroundImage_image_or_movieclip_user_path(const struct PointerRNA *ptr);
+int rna_BrushChannelSet_channels_assignint(struct PointerRNA *ptr,
+ int key,
+ const struct PointerRNA *assign_ptr);
+int rna_BrushChannelSet_channels_begin(struct CollectionPropertyIterator *iter,
+ struct PointerRNA *ptr);
+int rna_BrushChannelSet_length(struct PointerRNA *rna);
+void rna_BrushChannelSet_ensure(struct ID *id, const char *idname);
+
/* Set U.is_dirty and redraw. */
/**
diff --git a/source/blender/makesrna/intern/rna_path.cc b/source/blender/makesrna/intern/rna_path.cc
index e3898bbd682..ec5812cc4c5 100644
--- a/source/blender/makesrna/intern/rna_path.cc
+++ b/source/blender/makesrna/intern/rna_path.cc
@@ -346,14 +346,15 @@ static bool rna_path_parse_array_index(const char **path,
*
* \return \a true on success, \a false if the path is somehow invalid.
*/
-static bool rna_path_parse(const PointerRNA *ptr,
- const char *path,
- PointerRNA *r_ptr,
- PropertyRNA **r_prop,
- int *r_index,
- PointerRNA *r_item_ptr,
- ListBase *r_elements,
- const bool eval_pointer)
+#include "BKE_brush_channel.h"
+ATTR_NO_OPT static bool rna_path_parse(const PointerRNA *ptr,
+ const char *path,
+ PointerRNA *r_ptr,
+ PropertyRNA **r_prop,
+ int *r_index,
+ PointerRNA *r_item_ptr,
+ ListBase *r_elements,
+ const bool eval_pointer)
{
BLI_assert(r_item_ptr == nullptr || !eval_pointer);
PropertyRNA *prop;
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 58455ff7de5..b1a2c1243b8 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -29,6 +29,7 @@
#include "BLT_translation.h"
#include "BKE_armature.h"
+#include "BKE_brush_channel.h"
#include "BKE_editmesh.h"
#include "BKE_idtype.h"
#include "BKE_paint.h"
@@ -2202,6 +2203,15 @@ static void rna_UnifiedPaintSettings_update(bContext *C, PointerRNA *UNUSED(ptr)
WM_main_add_notifier(NC_BRUSH | NA_EDITED, br);
}
+static int rna_UnifiedPaintSettings_size_get(PointerRNA *ptr)
+{
+ Scene *scene = (Scene*)ptr->owner_id;
+ PointerRNA prop_ptr;
+
+ RNA_pointer_create(&scene->id, &RNA_UnifiedBrushProperties, scene->toolsettings, &prop_ptr);
+ return RNA_int_get(&prop_ptr, "[\"size\"]");
+}
+
static void rna_UnifiedPaintSettings_size_set(PointerRNA *ptr, int value)
{
UnifiedPaintSettings *ups = ptr->data;
@@ -2209,6 +2219,16 @@ static void rna_UnifiedPaintSettings_size_set(PointerRNA *ptr, int value)
/* scale unprojected radius so it stays consistent with brush size */
BKE_brush_scale_unprojected_radius(&ups->unprojected_radius, value, ups->size);
ups->size = value;
+
+ Scene *scene = (Scene *)ptr->owner_id;
+ PointerRNA prop_ptr;
+
+ RNA_pointer_create(
+ &scene->id, &RNA_UnifiedBrushProperties, scene->toolsettings, &prop_ptr);
+ RNA_int_set(&prop_ptr, "[\"size\"]", ups->size);
+ RNA_float_set(&prop_ptr, "[\"unprojected_radius\"]", ups->unprojected_radius);
+
+ BKE_brush_channelset_mark_update(scene->toolsettings->unified_channels, size);
}
static void rna_UnifiedPaintSettings_unprojected_radius_set(PointerRNA *ptr, float value)
@@ -2776,6 +2796,41 @@ static void rna_FFmpegSettings_codec_update(Main *UNUSED(bmain),
}
# endif
+static IDProperty **rna_UnifiedBrushProperties_idprops(PointerRNA *ptr)
+{
+ ToolSettings *ts = ptr->data;
+ return &ts->unified_properties;
+}
+
+static PointerRNA rna_ToolSettings_unified_properties_get(PointerRNA *ptr)
+{
+ PointerRNA ret = *ptr;
+
+ ret.type = &RNA_UnifiedBrushProperties;
+
+ return ret;
+}
+
+bool rna_UnifiedPaintSettings_use_unified_size_get(PointerRNA *ptr)
+{
+ Scene *scene = (Scene *)ptr->owner_id;
+ BrushChannel *ch = BKE_brush_channelset_lookup(scene->toolsettings->unified_channels, size);
+
+ return ch->flag & BRUSH_CHANNEL_FORCE_INHERIT;
+}
+
+void rna_UnifiedPaintSettings_use_unified_size_set(PointerRNA *ptr, bool val)
+{
+ Scene *scene = (Scene *)ptr->owner_id;
+ BrushChannel *ch = BKE_brush_channelset_lookup(scene->toolsettings->unified_channels, size);
+
+ if (val) {
+ ch->flag |= BRUSH_CHANNEL_FORCE_INHERIT;
+ }
+ else {
+ ch->flag &= ~BRUSH_CHANNEL_FORCE_INHERIT;
+ }
+}
#else
/* Grease Pencil Interpolation tool settings */
@@ -2912,6 +2967,17 @@ static void rna_def_view3d_cursor(BlenderRNA *brna)
prop, "rna_View3DCursor_matrix_get", "rna_View3DCursor_matrix_set", NULL);
}
+static void rna_def_unified_brush_properties(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+ srna = RNA_def_struct(brna, "UnifiedBrushProperties", NULL);
+
+ /* The struct has custom properties, but no pointer properties to other IDs! */
+ RNA_def_struct_idprops_func(srna, "rna_UnifiedBrushProperties_idprops");
+ RNA_def_struct_flag(srna, STRUCT_NO_DATABLOCK_IDPROPERTIES); /* Mandatory! */
+}
+
static void rna_def_tool_settings(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3053,6 +3119,28 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_struct_path_func(srna, "rna_ToolSettings_path");
RNA_def_struct_ui_text(srna, "Tool Settings", "");
+ rna_def_unified_brush_properties(brna);
+
+ prop = RNA_def_property(srna, "brush_editor_mode", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "brush_editor_mode", 1);
+ RNA_def_property_ui_text(prop, "Brush Editor Mode", "");
+
+ prop = RNA_def_property(srna, "unified_properties", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "UnifiedBrushProperties");
+ RNA_def_property_ui_text(prop, "Unified Brush Property Storage", "");
+ RNA_def_property_pointer_funcs(
+ prop, "rna_ToolSettings_unified_properties_get", NULL, NULL, NULL);
+
+ prop = RNA_def_property(srna, "unified_channels", PROP_COLLECTION, 0);
+ RNA_def_property_collection_sdna(prop, NULL, "unified_channels", NULL);
+ RNA_def_property_struct_type(prop, "BrushChannel");
+ RNA_def_property_ui_text(prop, "Channels", "");
+ // RNA_def_property_clear_flag(prop, PROP_PTR_NO_OWNERSHIP);
+ RNA_def_property_override_flag(
+ prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY | PROPOVERRIDE_LIBRARY_INSERTION);
+ RNA_def_property_ui_text(prop, "Unified Brush Channels", "");
+ RNA_def_brush_channelset(brna, prop, "unified_channels", "Scene");
+
prop = RNA_def_property(srna, "sculpt", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Sculpt");
RNA_def_property_ui_text(prop, "Sculpt", "");
@@ -3872,6 +3960,10 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna)
/* high-level flags to enable or disable unified paint settings */
prop = RNA_def_property(srna, "use_unified_size", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", UNIFIED_PAINT_SIZE);
+ RNA_def_property_boolean_funcs(prop,
+ "rna_UnifiedPaintSettings_use_unified_size_get",
+ "rna_UnifiedPaintSettings_use_unified_size_set");
+
RNA_def_property_ui_text(prop,
"Use Unified Radius",
"Instead of per-brush radius, the radius is shared across brushes");
@@ -3896,7 +3988,7 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna)
/* unified paint settings that override the equivalent settings
* from the active brush */
prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL);
- RNA_def_property_int_funcs(prop, NULL, "rna_UnifiedPaintSettings_size_set", NULL);
+ RNA_def_property_int_funcs(prop, "rna_UnifiedPaintSettings_size_get", "rna_UnifiedPaintSettings_size_set", NULL);
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_range(prop, 1, MAX_BRUSH_PIXEL_RADIUS * 10);
RNA_def_property_ui_range(prop, 1, MAX_BRUSH_PIXEL_RADIUS, 1, -1);