Design: https://projects.blender.org/blender/blender/issues/126032 The brush assets project merged all brush based tools into a single, "Brush" tool. After feedback, we want to bring back some of the previous brush based tools. For example in grease pencil draw mode, not having an eraser or fill tool available, and having to go through all the brush assets instead made the workflow more cumbersome, and features less discoverable (an eraser tool in the toolbar is quite easy to find, a brush deep down in the asset library may not be). With this commit we can add back some tools for specific brush types in sculpt & paint modes. The follow up commit will start with the eraser and fill tools for grease pencil draw mode, but more tools in other modes are expected to follow. For every brush type that has a tool in the toolbar, the last used brush is remembered. This is the biggest part of the changes here. Brush asset popups will only show the brushes supported by the active tool for now. The permanent asset shelf region displays all brushes. Activating a brush that isn't compatible with the current tool will also activate the general "Brush" tool, but while the brush tool is active we never switch to another one (e.g. activating an eraser brush will keep the "Brush" tool active). All this might change after further feedback. Pull Request: https://projects.blender.org/blender/blender/pulls/125449
1823 lines
66 KiB
Python
1823 lines
66 KiB
Python
# SPDX-FileCopyrightText: 2012-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
from bpy.types import Menu
|
|
|
|
|
|
class BrushAssetShelf:
|
|
bl_options = {'DEFAULT_VISIBLE', 'NO_ASSET_DRAG', 'STORE_ENABLED_CATALOGS_IN_PREFERENCES'}
|
|
bl_activate_operator = "BRUSH_OT_asset_activate"
|
|
bl_default_preview_size = 48
|
|
brush_type_prop = None
|
|
tool_prop = None
|
|
mode_prop = None
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return hasattr(context, "object") and context.object and context.object.mode == cls.mode
|
|
|
|
@classmethod
|
|
def brush_type_poll(cls, context, asset):
|
|
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
|
tool = ToolSelectPanelHelper.tool_active_from_context(context)
|
|
|
|
if not tool or tool.brush_type == 'ANY':
|
|
return True
|
|
if not cls.brush_type_prop or not cls.tool_prop:
|
|
return True
|
|
|
|
asset_brush_type = asset.metadata.get(cls.brush_type_prop)
|
|
# Asset metadata doesn't store a brush type. Only show it when the tool doesn't require a
|
|
# certain brush type.
|
|
if asset_brush_type is None:
|
|
return False
|
|
brush_type_items = bpy.types.Brush.bl_rna.properties[cls.tool_prop].enum_items
|
|
|
|
return brush_type_items[asset_brush_type].identifier == tool.brush_type
|
|
|
|
@classmethod
|
|
def asset_poll(cls, asset):
|
|
if asset.id_type != 'BRUSH':
|
|
return False
|
|
if cls.mode_prop and not asset.metadata.get(cls.mode_prop, False):
|
|
return False
|
|
|
|
context = bpy.context
|
|
|
|
is_asset_shelf_region = context.region and context.region.type == 'ASSET_SHELF'
|
|
# Show all brushes in the permanent asset shelf region. Otherwise filter out brushes that
|
|
# are incompatible with the tool.
|
|
if not is_asset_shelf_region and not cls.brush_type_poll(context, asset):
|
|
return False
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def get_active_asset(cls):
|
|
# Only show active highlight when using the brush tool.
|
|
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
|
tool = ToolSelectPanelHelper.tool_active_from_context(bpy.context)
|
|
if not tool or not tool.use_brushes:
|
|
return None
|
|
|
|
paint_settings = UnifiedPaintPanel.paint_settings(bpy.context)
|
|
return paint_settings.brush_asset_reference if paint_settings else None
|
|
|
|
@classmethod
|
|
def draw_context_menu(self, context, asset, layout):
|
|
# Currently this menu adds operators that deal with the affected brush and don't take the
|
|
# asset into account. Luckily that is okay for now, since right clicking in the grid view
|
|
# also activates the item.
|
|
layout.menu_contents("VIEW3D_MT_brush_context_menu")
|
|
|
|
@staticmethod
|
|
def get_shelf_name_from_context(context):
|
|
mode_map = {
|
|
'SCULPT': "VIEW3D_AST_brush_sculpt",
|
|
'PAINT_VERTEX': "VIEW3D_AST_brush_vertex_paint",
|
|
'PAINT_WEIGHT': "VIEW3D_AST_brush_weight_paint",
|
|
'PAINT_TEXTURE': "VIEW3D_AST_brush_texture_paint",
|
|
'PAINT_2D': "IMAGE_AST_brush_paint",
|
|
'PAINT_GPENCIL': "VIEW3D_AST_brush_gpencil_paint",
|
|
'SCULPT_GPENCIL': "VIEW3D_AST_brush_gpencil_sculpt",
|
|
'WEIGHT_GPENCIL': "VIEW3D_AST_brush_gpencil_weight",
|
|
'VERTEX_GPENCIL': "VIEW3D_AST_brush_gpencil_vertex",
|
|
'PAINT_GREASE_PENCIL': "VIEW3D_AST_brush_gpencil_paint",
|
|
'SCULPT_GREASE_PENCIL': "VIEW3D_AST_brush_gpencil_sculpt",
|
|
'WEIGHT_GREASE_PENCIL': "VIEW3D_AST_brush_gpencil_weight",
|
|
'VERTEX_GREASE_PENCIL': "VIEW3D_AST_brush_gpencil_vertex",
|
|
'SCULPT_CURVES': "VIEW3D_AST_brush_sculpt_curves",
|
|
}
|
|
mode = UnifiedPaintPanel.get_brush_mode(context)
|
|
if not mode:
|
|
return None
|
|
|
|
return mode_map[mode]
|
|
|
|
@staticmethod
|
|
def draw_popup_selector(layout, context, brush, show_name=True):
|
|
preview_icon_id = brush.preview.icon_id if brush and brush.preview else 0
|
|
|
|
shelf_name = BrushAssetShelf.get_shelf_name_from_context(context)
|
|
if not shelf_name:
|
|
return
|
|
|
|
layout.template_asset_shelf_popover(
|
|
shelf_name,
|
|
name=brush.name if (brush and show_name) else None,
|
|
icon='BRUSH_DATA' if not preview_icon_id else 'NONE',
|
|
icon_value=preview_icon_id,
|
|
)
|
|
|
|
|
|
class UnifiedPaintPanel:
|
|
# subclass must set
|
|
# bl_space_type = 'IMAGE_EDITOR'
|
|
# bl_region_type = 'UI'
|
|
|
|
@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."""
|
|
mode = context.mode
|
|
|
|
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:
|
|
# If there is no active tool, then there can't be an active brush.
|
|
return None
|
|
|
|
if not tool.use_brushes:
|
|
return None
|
|
|
|
space_data = context.space_data
|
|
tool_settings = context.tool_settings
|
|
|
|
if space_data:
|
|
space_type = space_data.type
|
|
if space_type == 'IMAGE_EDITOR':
|
|
return 'PAINT_2D'
|
|
elif space_type in {'VIEW_3D', 'PROPERTIES'}:
|
|
if mode == 'PAINT_TEXTURE':
|
|
if tool_settings.image_paint:
|
|
return mode
|
|
else:
|
|
return None
|
|
return mode
|
|
return None
|
|
|
|
@staticmethod
|
|
def paint_settings(context):
|
|
tool_settings = context.tool_settings
|
|
|
|
mode = UnifiedPaintPanel.get_brush_mode(context)
|
|
|
|
# 3D paint settings
|
|
if mode == 'SCULPT':
|
|
return tool_settings.sculpt
|
|
elif mode == 'PAINT_VERTEX':
|
|
return tool_settings.vertex_paint
|
|
elif mode == 'PAINT_WEIGHT':
|
|
return tool_settings.weight_paint
|
|
elif mode == 'PAINT_TEXTURE':
|
|
return tool_settings.image_paint
|
|
elif mode == 'PARTICLE':
|
|
return tool_settings.particle_edit
|
|
# 2D paint settings
|
|
elif mode == 'PAINT_2D':
|
|
return tool_settings.image_paint
|
|
# Grease Pencil settings
|
|
elif mode == 'PAINT_GPENCIL':
|
|
return tool_settings.gpencil_paint
|
|
elif mode == 'SCULPT_GPENCIL':
|
|
return tool_settings.gpencil_sculpt_paint
|
|
elif mode == 'WEIGHT_GPENCIL':
|
|
return tool_settings.gpencil_weight_paint
|
|
elif mode == 'VERTEX_GPENCIL':
|
|
return tool_settings.gpencil_vertex_paint
|
|
elif mode == 'PAINT_GREASE_PENCIL':
|
|
return tool_settings.gpencil_paint
|
|
elif mode == 'SCULPT_CURVES':
|
|
return tool_settings.curves_sculpt
|
|
elif mode == 'PAINT_GREASE_PENCIL':
|
|
return tool_settings.gpencil_paint
|
|
elif mode == 'SCULPT_GREASE_PENCIL':
|
|
return tool_settings.gpencil_sculpt_paint
|
|
elif mode == 'WEIGHT_GREASE_PENCIL':
|
|
return tool_settings.gpencil_weight_paint
|
|
elif mode == 'VERTEX_GREASE_PENCIL':
|
|
return tool_settings.gpencil_vertex_paint
|
|
return None
|
|
|
|
@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
|
|
if unified_name and getattr(ups, unified_name):
|
|
prop_owner = ups
|
|
|
|
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_color(parent, context, brush, prop_name, *, text=None):
|
|
ups = context.tool_settings.unified_paint_settings
|
|
prop_owner = ups if ups.use_unified_color else brush
|
|
parent.prop(prop_owner, prop_name, text=text)
|
|
|
|
@staticmethod
|
|
def prop_unified_color_picker(parent, context, brush, prop_name, value_slider=True):
|
|
ups = context.tool_settings.unified_paint_settings
|
|
prop_owner = ups if ups.use_unified_color else brush
|
|
parent.template_color_picker(prop_owner, prop_name, value_slider=value_slider)
|
|
|
|
|
|
### Classes to let various paint modes' panels share code, by sub-classing these classes. ###
|
|
class BrushPanel(UnifiedPaintPanel):
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return cls.get_brush_mode(context) is not None
|
|
|
|
|
|
class BrushSelectPanel(BrushPanel):
|
|
bl_label = "Brush Asset"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
settings = self.paint_settings(context)
|
|
if settings is None:
|
|
return
|
|
|
|
brush = settings.brush
|
|
|
|
row = layout.row()
|
|
|
|
col = row.column(align=True)
|
|
BrushAssetShelf.draw_popup_selector(col, context, brush, show_name=False)
|
|
if brush:
|
|
col.prop(brush, "name", text="")
|
|
|
|
if brush is None:
|
|
return
|
|
|
|
col = row.column()
|
|
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
|
|
|
|
|
|
class ColorPalettePanel(BrushPanel):
|
|
bl_label = "Color Palette"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not super().poll(context):
|
|
return False
|
|
|
|
settings = cls.paint_settings(context)
|
|
brush = settings.brush
|
|
|
|
if context.space_data.type == 'IMAGE_EDITOR' or context.image_paint_object:
|
|
capabilities = brush.image_paint_capabilities
|
|
return capabilities.has_color
|
|
|
|
elif context.vertex_paint_object:
|
|
capabilities = brush.vertex_paint_capabilities
|
|
return capabilities.has_color
|
|
|
|
elif context.sculpt_object:
|
|
capabilities = brush.sculpt_capabilities
|
|
return capabilities.has_color
|
|
return False
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
settings = self.paint_settings(context)
|
|
|
|
layout.template_ID(settings, "palette", new="palette.new")
|
|
if settings.palette:
|
|
layout.template_palette(settings, "palette", color=True)
|
|
|
|
|
|
class ClonePanel(BrushPanel):
|
|
bl_label = "Clone"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not super().poll(context):
|
|
return False
|
|
|
|
settings = cls.paint_settings(context)
|
|
|
|
mode = cls.get_brush_mode(context)
|
|
if mode == 'PAINT_TEXTURE':
|
|
brush = settings.brush
|
|
return brush.image_tool == 'CLONE'
|
|
return False
|
|
|
|
def draw_header(self, context):
|
|
settings = self.paint_settings(context)
|
|
self.layout.prop(settings, "use_clone_layer", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
settings = self.paint_settings(context)
|
|
|
|
layout.active = settings.use_clone_layer
|
|
|
|
ob = context.active_object
|
|
col = layout.column()
|
|
|
|
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",
|
|
rows=2,
|
|
)
|
|
|
|
mat = ob.active_material
|
|
if mat:
|
|
col.label(text="Source Clone Slot")
|
|
col.template_list(
|
|
"TEXTURE_UL_texpaintslots", "",
|
|
mat, "texture_paint_slots",
|
|
mat, "paint_clone_slot",
|
|
rows=2,
|
|
)
|
|
|
|
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)
|
|
|
|
|
|
class TextureMaskPanel(BrushPanel):
|
|
bl_label = "Texture Mask"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
brush = context.tool_settings.image_paint.brush
|
|
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)
|
|
|
|
# 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':
|
|
layout.operator("brush.stencil_fit_image_aspect").mask = True
|
|
layout.operator("brush.stencil_reset_transform").mask = True
|
|
|
|
col = layout.column()
|
|
col.prop(brush, "use_pressure_masking", text="Pressure Masking")
|
|
# angle and texture_angle_source
|
|
if mask_tex_slot.has_texture_angle:
|
|
col = layout.column()
|
|
col.prop(mask_tex_slot, "angle", text="Angle")
|
|
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:
|
|
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")
|
|
|
|
# scale and offset
|
|
col.prop(mask_tex_slot, "offset")
|
|
col.prop(mask_tex_slot, "scale")
|
|
|
|
|
|
class StrokePanel(BrushPanel):
|
|
bl_label = "Stroke"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
bl_ui_units_x = 13
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
mode = self.get_brush_mode(context)
|
|
settings = self.paint_settings(context)
|
|
brush = settings.brush
|
|
|
|
col = layout.column()
|
|
|
|
col.prop(brush, "stroke_method")
|
|
col.separator()
|
|
|
|
if brush.use_anchor:
|
|
col.prop(brush, "use_edge_to_edge", text="Edge to Edge")
|
|
|
|
if brush.use_airbrush:
|
|
col.prop(brush, "rate", text="Rate", slider=True)
|
|
|
|
if brush.use_space:
|
|
row = col.row(align=True)
|
|
row.prop(brush, "spacing", text="Spacing")
|
|
row.prop(brush, "use_pressure_spacing", toggle=True, text="")
|
|
|
|
if brush.use_line or brush.use_curve:
|
|
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 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:
|
|
col.separator()
|
|
col.template_ID(brush, "paint_curve", new="paintcurve.new")
|
|
col.operator("paintcurve.draw")
|
|
col.separator()
|
|
|
|
if brush.use_space or brush.use_line or brush.use_curve:
|
|
col.separator()
|
|
row = col.row(align=True)
|
|
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':
|
|
col.separator()
|
|
row = col.row(align=True)
|
|
if brush.jitter_unit == 'BRUSH':
|
|
row.prop(brush, "jitter", slider=True)
|
|
else:
|
|
row.prop(brush, "jitter_absolute")
|
|
row.prop(brush, "use_pressure_jitter", toggle=True, text="")
|
|
col.row().prop(brush, "jitter_unit", expand=True)
|
|
|
|
col.separator()
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"input_samples",
|
|
unified_name="use_unified_input_samples",
|
|
slider=True,
|
|
)
|
|
|
|
|
|
class SmoothStrokePanel(BrushPanel):
|
|
bl_label = "Stabilize Stroke"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not super().poll(context):
|
|
return False
|
|
settings = cls.paint_settings(context)
|
|
brush = settings.brush
|
|
if brush.brush_capabilities.has_smooth_stroke:
|
|
return True
|
|
return False
|
|
|
|
def draw_header(self, context):
|
|
settings = self.paint_settings(context)
|
|
brush = settings.brush
|
|
|
|
self.layout.use_property_split = False
|
|
self.layout.prop(brush, "use_smooth_stroke", text=self.bl_label if self.is_popover else "")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
settings = self.paint_settings(context)
|
|
brush = settings.brush
|
|
|
|
col = layout.column()
|
|
col.active = brush.use_smooth_stroke
|
|
col.prop(brush, "smooth_stroke_radius", text="Radius", slider=True)
|
|
col.prop(brush, "smooth_stroke_factor", text="Factor", slider=True)
|
|
|
|
|
|
class FalloffPanel(BrushPanel):
|
|
bl_label = "Falloff"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
if not super().poll(context):
|
|
return False
|
|
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':
|
|
brush = settings.brush
|
|
if brush.curves_sculpt_tool in {'ADD', 'DELETE'}:
|
|
return False
|
|
return True
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
settings = self.paint_settings(context)
|
|
mode = self.get_brush_mode(context)
|
|
brush = settings.brush
|
|
|
|
if brush is None:
|
|
return
|
|
|
|
col = layout.column(align=True)
|
|
row = col.row(align=True)
|
|
row.prop(brush, "curve_preset", text="")
|
|
|
|
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'
|
|
|
|
show_fallof_shape = False
|
|
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':
|
|
show_fallof_shape = True
|
|
|
|
if show_fallof_shape:
|
|
col.separator()
|
|
row = col.row(align=True)
|
|
row.use_property_split = True
|
|
row.use_property_decorate = False
|
|
row.prop(brush, "falloff_shape", expand=True)
|
|
|
|
|
|
class DisplayPanel(BrushPanel):
|
|
bl_label = "Brush Cursor"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw_header(self, context):
|
|
settings = self.paint_settings(context)
|
|
if settings and not self.is_popover:
|
|
self.layout.prop(settings, "show_brush", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
mode = self.get_brush_mode(context)
|
|
settings = self.paint_settings(context)
|
|
brush = settings.brush
|
|
tex_slot = brush.texture_slot
|
|
tex_slot_mask = brush.mask_texture_slot
|
|
|
|
if self.is_popover:
|
|
row = layout.row(align=True)
|
|
row.use_property_split = False
|
|
row.prop(settings, "show_brush", text="Display Cursor")
|
|
|
|
col = layout.column()
|
|
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:
|
|
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',
|
|
)
|
|
|
|
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", text="", toggle=True,
|
|
icon='HIDE_OFF' if brush.use_primary_overlay else 'HIDE_ON',
|
|
)
|
|
|
|
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", text="", toggle=True,
|
|
icon='HIDE_OFF' if brush.use_secondary_overlay else 'HIDE_ON',
|
|
)
|
|
|
|
|
|
class VIEW3D_MT_tools_projectpaint_clone(Menu):
|
|
bl_label = "Clone Layer"
|
|
|
|
def draw(self, context):
|
|
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.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 """
|
|
|
|
mode = UnifiedPaintPanel.get_brush_mode(context)
|
|
|
|
### Draw simple settings unique to each paint mode. ###
|
|
brush_shared_settings(layout, context, brush, popover)
|
|
|
|
# Sculpt Mode #
|
|
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:
|
|
layout.prop(brush, "tilt_strength_factor", slider=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "hardness", slider=True)
|
|
row.prop(brush, "invert_hardness_pressure", text="")
|
|
row.prop(brush, "use_hardness_pressure", text="")
|
|
|
|
# auto_smooth_factor and use_inverse_smooth_pressure
|
|
if capabilities.has_auto_smooth:
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"auto_smooth_factor",
|
|
pressure_name="use_inverse_smooth_pressure",
|
|
slider=True,
|
|
)
|
|
|
|
# topology_rake_factor
|
|
if (
|
|
capabilities.has_topology_rake and
|
|
context.sculpt_object.use_dynamic_topology_sculpting
|
|
):
|
|
layout.prop(brush, "topology_rake_factor", slider=True)
|
|
|
|
# normal_weight
|
|
if capabilities.has_normal_weight:
|
|
layout.prop(brush, "normal_weight", slider=True)
|
|
|
|
# crease_pinch_factor
|
|
if capabilities.has_pinch_factor:
|
|
text = "Pinch"
|
|
if sculpt_tool in {'BLOB', 'SNAKE_HOOK'}:
|
|
text = "Magnify"
|
|
layout.prop(brush, "crease_pinch_factor", slider=True, text=text)
|
|
|
|
# rake_factor
|
|
if capabilities.has_rake_factor:
|
|
layout.prop(brush, "rake_factor", slider=True)
|
|
|
|
# plane_offset, use_offset_pressure, use_plane_trim, plane_trim
|
|
if capabilities.has_plane_offset:
|
|
layout.separator()
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"plane_offset",
|
|
pressure_name="use_offset_pressure",
|
|
slider=True,
|
|
)
|
|
|
|
row = layout.row(heading="Plane Trim")
|
|
row.prop(brush, "use_plane_trim", text="")
|
|
sub = row.row()
|
|
sub.active = brush.use_plane_trim
|
|
sub.prop(brush, "plane_trim", slider=True, text="")
|
|
|
|
layout.separator()
|
|
|
|
# height
|
|
if capabilities.has_height:
|
|
layout.prop(brush, "height", slider=True, text="Height")
|
|
|
|
# use_persistent, set_persistent_base
|
|
if capabilities.has_persistence:
|
|
layout.separator()
|
|
layout.prop(brush, "use_persistent")
|
|
layout.operator("sculpt.set_persistent_base")
|
|
layout.separator()
|
|
|
|
if capabilities.has_color:
|
|
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="")
|
|
row.separator()
|
|
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':
|
|
row = layout.row()
|
|
row.prop(brush, "tip_roundness")
|
|
|
|
row = layout.row()
|
|
row.prop(brush, "tip_scale_x")
|
|
|
|
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':
|
|
layout.separator()
|
|
layout.prop(brush, "snake_hook_deform_type")
|
|
layout.separator()
|
|
|
|
elif sculpt_tool == 'POSE':
|
|
layout.separator()
|
|
layout.prop(brush, "deform_target")
|
|
layout.separator()
|
|
layout.prop(brush, "pose_deform_type")
|
|
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'}:
|
|
layout.prop(brush, "pose_ik_segments")
|
|
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")
|
|
layout.prop(brush, "disconnected_distance_max")
|
|
|
|
layout.separator()
|
|
|
|
elif sculpt_tool == 'CLOTH':
|
|
layout.separator()
|
|
layout.prop(brush, "cloth_simulation_area_type")
|
|
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':
|
|
layout.prop(brush, "use_cloth_pin_simulation_boundary")
|
|
|
|
layout.separator()
|
|
layout.prop(brush, "cloth_deform_type")
|
|
layout.prop(brush, "cloth_force_falloff_type")
|
|
layout.separator()
|
|
layout.prop(brush, "cloth_mass")
|
|
layout.prop(brush, "cloth_damping")
|
|
layout.prop(brush, "cloth_constraint_softbody_strength")
|
|
layout.separator()
|
|
layout.prop(brush, "use_cloth_collision")
|
|
layout.separator()
|
|
|
|
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':
|
|
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':
|
|
layout.prop(brush, "use_grab_active_vertex")
|
|
layout.prop(brush, "use_grab_silhouette")
|
|
|
|
elif sculpt_tool == 'PAINT':
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "flow")
|
|
row.prop(brush, "invert_flow_pressure", text="")
|
|
row.prop(brush, "use_flow_pressure", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "wet_mix")
|
|
row.prop(brush, "invert_wet_mix_pressure", text="")
|
|
row.prop(brush, "use_wet_mix_pressure", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "wet_persistence")
|
|
row.prop(brush, "invert_wet_persistence_pressure", text="")
|
|
row.prop(brush, "use_wet_persistence_pressure", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "wet_paint_radius_factor")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "density")
|
|
row.prop(brush, "invert_density_pressure", text="")
|
|
row.prop(brush, "use_density_pressure", text="")
|
|
|
|
row = layout.row()
|
|
row.prop(brush, "tip_roundness")
|
|
|
|
row = layout.row()
|
|
row.prop(brush, "tip_scale_x")
|
|
|
|
elif sculpt_tool == 'SMEAR':
|
|
col = layout.column()
|
|
col.prop(brush, "smear_deform_type")
|
|
|
|
elif sculpt_tool == 'BOUNDARY':
|
|
layout.prop(brush, "deform_target")
|
|
layout.separator()
|
|
col = layout.column()
|
|
col.prop(brush, "boundary_deform_type")
|
|
col.prop(brush, "boundary_falloff_type")
|
|
col.prop(brush, "boundary_offset")
|
|
|
|
elif sculpt_tool == 'TOPOLOGY':
|
|
col = layout.column()
|
|
col.prop(brush, "slide_deform_type")
|
|
|
|
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':
|
|
col = layout.column()
|
|
col.prop(brush, "smooth_deform_type")
|
|
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':
|
|
col = layout.column()
|
|
col.prop(brush, "smear_deform_type")
|
|
|
|
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'}:
|
|
capabilities = brush.image_paint_capabilities
|
|
|
|
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':
|
|
layout.row().prop(brush, "gradient_fill_mode", expand=True)
|
|
|
|
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, "use_length_interpolate", text="Length")
|
|
col.prop(brush.curves_sculpt_settings, "use_radius_interpolate", text="Radius")
|
|
col.prop(brush.curves_sculpt_settings, "use_shape_interpolate", text="Shape")
|
|
col.prop(brush.curves_sculpt_settings, "use_point_count_interpolate", text="Point Count")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_length_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "curve_length", text="Length")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_radius_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "curve_radius", text="Radius")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_point_count_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "points_per_curve", text="Points")
|
|
|
|
if brush.curves_sculpt_tool == 'DENSITY':
|
|
col = layout.column()
|
|
col.prop(brush.curves_sculpt_settings, "density_add_attempts", text="Count Max")
|
|
col = layout.column(heading="Interpolate", align=True)
|
|
col.prop(brush.curves_sculpt_settings, "use_length_interpolate", text="Length")
|
|
col.prop(brush.curves_sculpt_settings, "use_radius_interpolate", text="Radius")
|
|
col.prop(brush.curves_sculpt_settings, "use_shape_interpolate", text="Shape")
|
|
col.prop(brush.curves_sculpt_settings, "use_point_count_interpolate", text="Point Count")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_length_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "curve_length", text="Length")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_radius_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "curve_radius", text="Radius")
|
|
|
|
col = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.use_point_count_interpolate
|
|
col.prop(brush.curves_sculpt_settings, "points_per_curve", text="Points")
|
|
|
|
elif brush.curves_sculpt_tool == 'GROW_SHRINK':
|
|
layout.prop(brush.curves_sculpt_settings, "use_uniform_scale")
|
|
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. """
|
|
|
|
mode = UnifiedPaintPanel.get_brush_mode(context)
|
|
|
|
### Determine which settings to draw. ###
|
|
blend_mode = False
|
|
size = False
|
|
size_mode = False
|
|
strength = False
|
|
strength_pressure = False
|
|
weight = False
|
|
direction = False
|
|
|
|
# 3D and 2D Texture Paint #
|
|
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':
|
|
size_mode = True
|
|
if not popover:
|
|
size = True
|
|
strength = True
|
|
strength_pressure = brush.sculpt_capabilities.has_strength_pressure
|
|
direction = not brush.sculpt_capabilities.has_direction
|
|
|
|
# Vertex Paint #
|
|
if mode == 'PAINT_VERTEX':
|
|
if not popover:
|
|
blend_mode = True
|
|
size = True
|
|
strength = True
|
|
strength_pressure = True
|
|
|
|
# Weight Paint #
|
|
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':
|
|
blend_mode = True
|
|
|
|
# Sculpt Curves #
|
|
if mode == 'SCULPT_CURVES':
|
|
size = True
|
|
strength = True
|
|
direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
|
|
|
|
# Grease Pencil #
|
|
if mode == 'PAINT_GREASE_PENCIL':
|
|
size_mode = True
|
|
size = True
|
|
strength = True
|
|
|
|
# Grease Pencil #
|
|
if mode == 'SCULPT_GREASE_PENCIL':
|
|
size = True
|
|
strength = True
|
|
|
|
### Draw settings. ###
|
|
ups = context.scene.tool_settings.unified_paint_settings
|
|
|
|
if blend_mode:
|
|
layout.prop(brush, "blend", text="Blend")
|
|
layout.separator()
|
|
|
|
if weight:
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"weight",
|
|
unified_name="use_unified_weight",
|
|
slider=True,
|
|
)
|
|
|
|
size_owner = ups if ups.use_unified_size else brush
|
|
size_prop = "size"
|
|
if size_mode and (size_owner.use_locked_size == 'SCENE'):
|
|
size_prop = "unprojected_radius"
|
|
if size or size_mode:
|
|
if size:
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
size_prop,
|
|
unified_name="use_unified_size",
|
|
pressure_name="use_pressure_size",
|
|
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(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"strength",
|
|
unified_name="use_unified_strength",
|
|
pressure_name=pressure_name,
|
|
slider=True,
|
|
)
|
|
layout.separator()
|
|
|
|
if direction:
|
|
layout.row().prop(brush, "direction", expand=True)
|
|
|
|
|
|
def brush_settings_advanced(layout, context, brush, popover=False):
|
|
"""Draw advanced brush settings for Sculpt, Texture/Vertex/Weight Paint modes."""
|
|
|
|
mode = UnifiedPaintPanel.get_brush_mode(context)
|
|
|
|
# In the popover we want to combine advanced brush settings with non-advanced brush settings.
|
|
if popover:
|
|
brush_settings(layout, context, brush, popover=True)
|
|
layout.separator()
|
|
layout.label(text="Advanced")
|
|
|
|
# These options are shared across many modes.
|
|
use_accumulate = False
|
|
use_frontface = False
|
|
|
|
if mode == 'SCULPT':
|
|
layout.prop(brush, "sculpt_tool")
|
|
layout.separator()
|
|
|
|
capabilities = brush.sculpt_capabilities
|
|
use_accumulate = capabilities.has_accumulate
|
|
use_frontface = True
|
|
|
|
col = layout.column(heading="Auto-Masking", align=True)
|
|
|
|
col.prop(brush, "use_automasking_topology", text="Topology")
|
|
col.prop(brush, "use_automasking_face_sets", text="Face Sets")
|
|
|
|
layout.separator()
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(brush, "use_automasking_boundary_edges", text="Mesh Boundary")
|
|
col.prop(brush, "use_automasking_boundary_face_sets", text="Face Sets Boundary")
|
|
|
|
if brush.use_automasking_boundary_edges or brush.use_automasking_boundary_face_sets:
|
|
col = layout.column()
|
|
col.use_property_split = False
|
|
split = col.split(factor=0.4)
|
|
col = split.column()
|
|
split.prop(brush, "automasking_boundary_edges_propagation_steps")
|
|
|
|
layout.separator()
|
|
|
|
col = layout.column(align=True)
|
|
row = col.row()
|
|
row.prop(brush, "use_automasking_cavity", text="Cavity")
|
|
|
|
is_cavity_active = brush.use_automasking_cavity or brush.use_automasking_cavity_inverted
|
|
|
|
if is_cavity_active:
|
|
props = row.operator("sculpt.mask_from_cavity", text="Create Mask")
|
|
props.settings_source = 'BRUSH'
|
|
|
|
col.prop(brush, "use_automasking_cavity_inverted", text="Cavity (inverted)")
|
|
|
|
if is_cavity_active:
|
|
col = layout.column(align=True)
|
|
col.prop(brush, "automasking_cavity_factor", text="Factor")
|
|
col.prop(brush, "automasking_cavity_blur_steps", text="Blur")
|
|
|
|
col = layout.column()
|
|
col.prop(brush, "use_automasking_custom_cavity_curve", text="Custom Curve")
|
|
|
|
if brush.use_automasking_custom_cavity_curve:
|
|
col.template_curve_mapping(brush, "automasking_cavity_curve")
|
|
|
|
layout.separator()
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(brush, "use_automasking_view_normal", text="View Normal")
|
|
|
|
if brush.use_automasking_view_normal:
|
|
col.prop(brush, "use_automasking_view_occlusion", text="Occlusion")
|
|
subcol = col.column(align=True)
|
|
subcol.active = not brush.use_automasking_view_occlusion
|
|
subcol.prop(brush, "automasking_view_normal_limit", text="Limit")
|
|
subcol.prop(brush, "automasking_view_normal_falloff", text="Falloff")
|
|
|
|
col = layout.column()
|
|
col.prop(brush, "use_automasking_start_normal", text="Area Normal")
|
|
|
|
if brush.use_automasking_start_normal:
|
|
col = layout.column(align=True)
|
|
col.prop(brush, "automasking_start_normal_limit", text="Limit")
|
|
col.prop(brush, "automasking_start_normal_falloff", text="Falloff")
|
|
|
|
layout.separator()
|
|
|
|
# sculpt plane settings
|
|
if capabilities.has_sculpt_plane:
|
|
layout.prop(brush, "sculpt_plane")
|
|
col = layout.column(heading="Original", align=True)
|
|
col.prop(brush, "use_original_normal", text="Normal")
|
|
col.prop(brush, "use_original_plane", text="Plane")
|
|
layout.separator()
|
|
|
|
elif mode == 'SCULPT_GREASE_PENCIL':
|
|
tool = brush.gpencil_sculpt_tool
|
|
gp_settings = brush.gpencil_settings
|
|
|
|
col = layout.column(heading="Affect", align=True)
|
|
col.prop(gp_settings, "use_edit_position", text="Position")
|
|
col.prop(gp_settings, "use_edit_strength", text="Strength")
|
|
col.prop(gp_settings, "use_edit_thickness", text="Thickness")
|
|
col.prop(gp_settings, "use_edit_uv", text="UV")
|
|
|
|
# 3D and 2D Texture Paint.
|
|
elif mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
|
|
layout.prop(brush, "image_tool")
|
|
layout.separator()
|
|
|
|
capabilities = brush.image_paint_capabilities
|
|
use_accumulate = capabilities.has_accumulate
|
|
|
|
if mode == 'PAINT_2D':
|
|
layout.prop(brush, "use_paint_antialiasing")
|
|
else:
|
|
layout.prop(brush, "use_alpha")
|
|
|
|
# Tool specific settings
|
|
if brush.image_tool == 'SOFTEN':
|
|
layout.separator()
|
|
layout.row().prop(brush, "direction", expand=True)
|
|
layout.prop(brush, "sharp_threshold")
|
|
if mode == 'PAINT_2D':
|
|
layout.prop(brush, "blur_kernel_radius")
|
|
layout.prop(brush, "blur_mode")
|
|
|
|
elif brush.image_tool == 'MASK':
|
|
layout.prop(brush, "weight", text="Mask Value", slider=True)
|
|
|
|
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':
|
|
layout.prop(brush, "vertex_tool")
|
|
layout.separator()
|
|
|
|
layout.prop(brush, "use_alpha")
|
|
if brush.vertex_tool != 'SMEAR':
|
|
use_accumulate = True
|
|
use_frontface = True
|
|
|
|
# Weight Paint
|
|
elif mode == 'PAINT_WEIGHT':
|
|
layout.prop(brush, "weight_tool")
|
|
layout.separator()
|
|
|
|
if brush.weight_tool != 'SMEAR':
|
|
use_accumulate = True
|
|
use_frontface = True
|
|
|
|
# Sculpt Curves
|
|
elif mode == 'SCULPT_CURVES':
|
|
layout.prop(brush, "curves_sculpt_tool")
|
|
|
|
# Draw shared settings.
|
|
if use_accumulate:
|
|
layout.prop(brush, "use_accumulate")
|
|
|
|
if use_frontface:
|
|
layout.prop(brush, "use_frontface", text="Front Faces Only")
|
|
|
|
# Brush modes
|
|
header, panel = layout.panel("modes", default_closed=True)
|
|
header.label(text="Modes")
|
|
if panel:
|
|
panel.use_property_split = True
|
|
panel.use_property_decorate = False
|
|
|
|
col = panel.column(align=True)
|
|
col.prop(brush, "use_paint_sculpt", text="Sculpt")
|
|
col.prop(brush, "use_paint_uv_sculpt", text="UV Sculpt")
|
|
col.prop(brush, "use_paint_vertex", text="Vertex Paint")
|
|
col.prop(brush, "use_paint_weight", text="Weight Paint")
|
|
col.prop(brush, "use_paint_image", text="Texture Paint")
|
|
col.prop(brush, "use_paint_sculpt_curves", text="Sculpt Curves")
|
|
|
|
if len(brush.icon_filepath) > 0:
|
|
header, panel = layout.panel("legacy", default_closed=True)
|
|
header.label(text="Legacy Icon")
|
|
if panel:
|
|
panel.label(text="Brush icons have moved to the asset preview image", icon='ERROR')
|
|
panel.prop(brush, "use_custom_icon")
|
|
panel.prop(brush, "icon_filepath")
|
|
|
|
|
|
def draw_color_settings(context, layout, brush, color_type=False):
|
|
"""Draw color wheel and gradient settings."""
|
|
ups = context.scene.tool_settings.unified_paint_settings
|
|
|
|
if color_type:
|
|
row = layout.row()
|
|
row.use_property_split = 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)
|
|
|
|
row = layout.row(align=True)
|
|
UnifiedPaintPanel.prop_unified_color(row, context, brush, "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')
|
|
# 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':
|
|
UnifiedPaintPanel.prop_unified(
|
|
col,
|
|
context,
|
|
brush,
|
|
"secondary_color",
|
|
unified_name="use_unified_color",
|
|
text="Background Color",
|
|
header=True,
|
|
)
|
|
|
|
col.prop(brush, "gradient_stroke_mode", text="Gradient Mapping")
|
|
if brush.gradient_stroke_mode in {'SPACING_REPEAT', 'SPACING_CLAMP'}:
|
|
col.prop(brush, "grad_spacing")
|
|
|
|
|
|
# Used in both the View3D toolbar and texture properties
|
|
def brush_texture_settings(layout, brush, sculpt):
|
|
tex_slot = brush.texture_slot
|
|
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
# map_mode
|
|
layout.prop(tex_slot, "map_mode", text="Mapping")
|
|
|
|
layout.separator()
|
|
|
|
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")
|
|
|
|
# angle and texture_angle_source
|
|
if tex_slot.has_texture_angle:
|
|
col = layout.column()
|
|
col.prop(tex_slot, "angle", text="Angle")
|
|
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 sculpt:
|
|
if brush.sculpt_capabilities.has_random_texture_angle:
|
|
col.prop(tex_slot, "use_random", text="Random")
|
|
if tex_slot.use_random:
|
|
col.prop(tex_slot, "random_angle", text="Random Angle")
|
|
else:
|
|
col.prop(tex_slot, "use_random", text="Random")
|
|
if tex_slot.use_random:
|
|
col.prop(tex_slot, "random_angle", text="Random Angle")
|
|
|
|
# scale and offset
|
|
layout.prop(tex_slot, "offset")
|
|
layout.prop(tex_slot, "scale")
|
|
|
|
if sculpt:
|
|
# texture_sample_bias
|
|
layout.prop(brush, "texture_sample_bias", slider=True, text="Sample Bias")
|
|
|
|
if brush.sculpt_tool == 'DRAW':
|
|
col = layout.column()
|
|
col.active = tex_slot.map_mode == 'AREA_PLANE'
|
|
col.prop(brush, "use_color_as_displacement", text="Vector Displacement")
|
|
|
|
|
|
def brush_mask_texture_settings(layout, brush):
|
|
mask_tex_slot = brush.mask_texture_slot
|
|
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
# 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':
|
|
layout.operator("brush.stencil_fit_image_aspect").mask = True
|
|
layout.operator("brush.stencil_reset_transform").mask = True
|
|
|
|
col = layout.column()
|
|
col.prop(brush, "use_pressure_masking", text="Pressure Masking")
|
|
# angle and texture_angle_source
|
|
if mask_tex_slot.has_texture_angle:
|
|
col = layout.column()
|
|
col.prop(mask_tex_slot, "angle", text="Angle")
|
|
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:
|
|
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")
|
|
|
|
# scale and offset
|
|
col.prop(mask_tex_slot, "offset")
|
|
col.prop(mask_tex_slot, "scale")
|
|
|
|
|
|
def brush_basic_texpaint_settings(layout, context, brush, *, compact=False):
|
|
"""Draw Tool Settings header for Vertex Paint and 2D and 3D Texture Paint modes."""
|
|
capabilities = brush.image_paint_capabilities
|
|
|
|
if capabilities.has_color:
|
|
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="")
|
|
row.separator()
|
|
layout.prop(brush, "blend", text="" if compact else "Blend")
|
|
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"size",
|
|
pressure_name="use_pressure_size",
|
|
unified_name="use_unified_size",
|
|
slider=True,
|
|
text="Radius",
|
|
header=True,
|
|
)
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"strength",
|
|
pressure_name="use_pressure_strength",
|
|
unified_name="use_unified_strength",
|
|
header=True,
|
|
)
|
|
|
|
|
|
def brush_basic__draw_color_selector(context, layout, brush, gp_settings, props):
|
|
tool_settings = context.scene.tool_settings
|
|
settings = tool_settings.gpencil_paint
|
|
ma = gp_settings.material
|
|
|
|
row = layout.row(align=True)
|
|
if not gp_settings.use_material_pin:
|
|
ma = context.object.active_material
|
|
icon_id = 0
|
|
txt_ma = ""
|
|
if ma:
|
|
ma.id_data.preview_ensure()
|
|
if ma.id_data.preview:
|
|
icon_id = ma.id_data.preview.icon_id
|
|
txt_ma = ma.name
|
|
maxw = 25
|
|
if len(txt_ma) > maxw:
|
|
txt_ma = txt_ma[:maxw - 5] + '..' + txt_ma[-3:]
|
|
|
|
sub = row.row(align=True)
|
|
sub.enabled = not gp_settings.use_material_pin
|
|
sub.ui_units_x = 8
|
|
sub.popover(
|
|
panel="TOPBAR_PT_gpencil_materials",
|
|
text=txt_ma,
|
|
icon_value=icon_id,
|
|
)
|
|
|
|
row.prop(gp_settings, "use_material_pin", text="")
|
|
|
|
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')
|
|
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 = 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")
|
|
row.prop(gp_settings, "pin_draw_mode", text="")
|
|
|
|
if props:
|
|
row = layout.row(align=True)
|
|
row.prop(props, "subdivision")
|
|
|
|
|
|
def brush_basic_gpencil_paint_settings(layout, context, brush, *, compact=False):
|
|
tool_settings = context.tool_settings
|
|
settings = tool_settings.gpencil_paint
|
|
gp_settings = brush.gpencil_settings
|
|
tool = context.workspace.tools.from_space_view3d_mode(context.mode, create=False)
|
|
if gp_settings is None:
|
|
return
|
|
|
|
# Brush details
|
|
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 = layout.row(align=True)
|
|
row.prop(gp_settings, "eraser_mode", expand=True)
|
|
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 = layout.row(align=True)
|
|
row.prop(gp_settings, "eraser_strength_factor")
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "eraser_thickness_factor")
|
|
|
|
# FIXME: tools must use their own UI drawing!
|
|
elif brush.gpencil_tool == 'FILL':
|
|
use_property_split_prev = layout.use_property_split
|
|
if compact:
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_direction", text="", expand=True)
|
|
else:
|
|
layout.use_property_split = False
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_direction", expand=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_factor")
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "dilate")
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "size", text="Thickness")
|
|
layout.use_property_split = use_property_split_prev
|
|
|
|
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')
|
|
|
|
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)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "pen_strength", slider=True)
|
|
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)
|
|
|
|
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':
|
|
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",
|
|
}:
|
|
settings = context.tool_settings.gpencil_sculpt
|
|
if compact:
|
|
row = layout.row(align=True)
|
|
row.prop(settings, "use_thickness_curve", text="", icon='SPHERECURVE')
|
|
sub = row.row(align=True)
|
|
sub.active = settings.use_thickness_curve
|
|
sub.popover(
|
|
panel="TOPBAR_PT_gpencil_primitive",
|
|
text="Thickness Profile",
|
|
)
|
|
else:
|
|
row = layout.row(align=True)
|
|
row.prop(settings, "use_thickness_curve", text="Use Thickness Profile")
|
|
sub = row.row(align=True)
|
|
if settings.use_thickness_curve:
|
|
# Curve
|
|
layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
|
|
|
|
|
|
def brush_basic_grease_pencil_paint_settings(layout, context, brush, *, compact=False):
|
|
gp_settings = brush.gpencil_settings
|
|
tool = context.workspace.tools.from_space_view3d_mode(context.mode, create=False)
|
|
if gp_settings is None:
|
|
return
|
|
|
|
grease_pencil_tool = brush.gpencil_tool
|
|
|
|
if grease_pencil_tool in {'DRAW', 'ERASE', 'TINT'} or tool.idname in {
|
|
"builtin.arc",
|
|
"builtin.curve",
|
|
"builtin.line",
|
|
"builtin.box",
|
|
"builtin.circle",
|
|
"builtin.polyline",
|
|
}:
|
|
size = "size"
|
|
if brush.use_locked_size == 'SCENE' and (grease_pencil_tool == 'DRAW' or tool.idname in {
|
|
"builtin.arc",
|
|
"builtin.curve",
|
|
"builtin.line",
|
|
"builtin.box",
|
|
"builtin.circle",
|
|
"builtin.polyline",
|
|
}):
|
|
size = "unprojected_radius"
|
|
row = layout.row(align=True)
|
|
row.prop(brush, size, slider=True, text="Radius")
|
|
row.prop(brush, "use_pressure_size", text="")
|
|
|
|
if brush.use_pressure_size and not compact:
|
|
col = layout.column()
|
|
col.template_curve_mapping(gp_settings, "curve_sensitivity", brush=True, use_negative_slope=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "strength", slider=True, text="Strength")
|
|
row.prop(brush, "use_pressure_strength", text="")
|
|
|
|
if brush.use_pressure_strength and not compact:
|
|
col = layout.column()
|
|
col.template_curve_mapping(gp_settings, "curve_strength", brush=True, use_negative_slope=True)
|
|
|
|
# Brush details
|
|
if tool.idname in {
|
|
"builtin.arc",
|
|
"builtin.curve",
|
|
"builtin.line",
|
|
"builtin.box",
|
|
"builtin.circle",
|
|
"builtin.polyline",
|
|
}:
|
|
row = layout.row(align=True)
|
|
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")
|
|
|
|
settings = context.tool_settings.gpencil_sculpt
|
|
if compact:
|
|
row = layout.row(align=True)
|
|
row.prop(settings, "use_thickness_curve", text="", icon='SPHERECURVE')
|
|
sub = row.row(align=True)
|
|
sub.active = settings.use_thickness_curve
|
|
sub.popover(
|
|
panel="TOPBAR_PT_gpencil_primitive",
|
|
text="Thickness Profile",
|
|
)
|
|
else:
|
|
row = layout.row(align=True)
|
|
row.prop(settings, "use_thickness_curve", text="Use Thickness Profile")
|
|
sub = row.row(align=True)
|
|
if settings.use_thickness_curve:
|
|
# Pressure curve.
|
|
layout.template_curve_mapping(settings, "thickness_primitive_curve", brush=True)
|
|
elif grease_pencil_tool == 'DRAW':
|
|
row = layout.row(align=True)
|
|
if compact:
|
|
row.prop(gp_settings, "caps_type", text="", expand=True)
|
|
else:
|
|
row.prop(gp_settings, "caps_type", text="Caps Type")
|
|
elif brush.gpencil_tool == 'FILL':
|
|
use_property_split_prev = layout.use_property_split
|
|
if compact:
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_direction", text="", expand=True)
|
|
else:
|
|
layout.use_property_split = False
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_direction", expand=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "fill_factor")
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "dilate")
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "size", text="Thickness")
|
|
layout.use_property_split = use_property_split_prev
|
|
elif grease_pencil_tool == 'ERASE':
|
|
layout.prop(gp_settings, "eraser_mode", expand=True)
|
|
if gp_settings.eraser_mode in {'HARD', 'SOFT'}:
|
|
layout.prop(gp_settings, "use_keep_caps_eraser")
|
|
layout.prop(gp_settings, "use_active_layer_only")
|
|
elif grease_pencil_tool == 'TINT':
|
|
layout.prop(gp_settings, "vertex_mode", text="Mode")
|
|
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
|
layout.prop(gp_settings, "use_active_layer_only")
|
|
|
|
|
|
def brush_basic_gpencil_sculpt_settings(layout, _context, brush, *, compact=False):
|
|
if brush is None:
|
|
return
|
|
gp_settings = brush.gpencil_settings
|
|
if gp_settings is None:
|
|
return
|
|
tool = brush.gpencil_sculpt_tool
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "size", slider=True)
|
|
sub = row.row(align=True)
|
|
sub.enabled = tool not in {'GRAB', 'CLONE'}
|
|
sub.prop(gp_settings, "use_pressure", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "strength", slider=True)
|
|
row.prop(brush, "use_pressure_strength", text="")
|
|
|
|
if compact:
|
|
if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
|
|
row.separator()
|
|
row.prop(brush, "direction", expand=True, text="")
|
|
else:
|
|
use_property_split_prev = layout.use_property_split
|
|
layout.use_property_split = False
|
|
if tool in {'THICKNESS', 'STRENGTH', 'PINCH', 'TWIST'}:
|
|
layout.row().prop(brush, "direction", expand=True)
|
|
layout.use_property_split = use_property_split_prev
|
|
|
|
|
|
def brush_basic_gpencil_weight_settings(layout, _context, brush, *, compact=False):
|
|
layout.prop(brush, "size", slider=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "strength", slider=True)
|
|
row.prop(brush, "use_pressure_strength", text="")
|
|
|
|
if brush.gpencil_weight_tool in {'WEIGHT'}:
|
|
layout.prop(brush, "weight", slider=True)
|
|
|
|
layout.prop(brush, "direction", expand=True, text="" if compact else "Direction")
|
|
|
|
|
|
def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=False):
|
|
gp_settings = brush.gpencil_settings
|
|
|
|
# Brush details
|
|
row = layout.row(align=True)
|
|
row.prop(brush, "size", text="Radius")
|
|
row.prop(gp_settings, "use_pressure", text="", icon='STYLUS_PRESSURE')
|
|
|
|
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')
|
|
|
|
if brush.gpencil_vertex_tool in {'DRAW', 'REPLACE'}:
|
|
row = layout.row(align=True)
|
|
row.prop(gp_settings, "vertex_mode", text="Mode")
|
|
|
|
|
|
def brush_basic_grease_pencil_weight_settings(layout, context, brush, *, compact=False):
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"size",
|
|
pressure_name="use_pressure_size",
|
|
unified_name="use_unified_size",
|
|
text="Radius",
|
|
slider=True,
|
|
header=compact,
|
|
)
|
|
|
|
capabilities = brush.sculpt_capabilities
|
|
pressure_name = "use_pressure_strength" if capabilities.has_strength_pressure else None
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"strength",
|
|
pressure_name=pressure_name,
|
|
unified_name="use_unified_strength",
|
|
text="Strength",
|
|
header=compact,
|
|
)
|
|
|
|
if brush.gpencil_weight_tool in {'WEIGHT'}:
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"weight",
|
|
unified_name="use_unified_weight",
|
|
text="Weight",
|
|
slider=True,
|
|
header=compact,
|
|
)
|
|
layout.prop(brush, "direction", expand=True, text="" if compact else "Direction")
|
|
|
|
|
|
def brush_basic_grease_pencil_vertex_settings(layout, context, brush, *, compact=False):
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"size",
|
|
pressure_name="use_pressure_size",
|
|
unified_name="use_unified_size",
|
|
text="Radius",
|
|
slider=True,
|
|
header=compact,
|
|
)
|
|
|
|
if brush.gpencil_vertex_tool in {'DRAW', 'BLUR', 'SMEAR'}:
|
|
UnifiedPaintPanel.prop_unified(
|
|
layout,
|
|
context,
|
|
brush,
|
|
"strength",
|
|
pressure_name="use_pressure_strength",
|
|
unified_name="use_unified_strength",
|
|
text="Strength",
|
|
header=compact,
|
|
)
|
|
|
|
gp_settings = brush.gpencil_settings
|
|
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,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|