diff options
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); |