This commit adds a float selection to curve control points or curves, a sculpt tool to paint the selection, and uses the selection influence in the existing sculpt brushes. The selection is the inverse of the "mask" from mesh sculpt mode currently. That change is described in more detail here: T97903 Since some sculpt tools are really "per curve" tools, they use the average point selection of all of their points. The delete brush considers a curve selected if any of its points have a non-zero selection. There is a new option to choose the selection domain, which affects how painting the selection works. You can also turn the selection off by clicking on the active domain. Sculpt brushes can be faster when the selection is small, because finding selected curves or points is generally faster than the existing brush intersection and distance checks. The main limitation currently is that we can't see the selection in the viewport by default. For now, to see the selection one has to add a simple material to the curves object as shown in the differential revision. And one has to switch to Material Preview in the 3d view. Differential Revision: https://developer.blender.org/D14934
1390 lines
50 KiB
Python
1390 lines
50 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# <pep8 compliant>
|
|
from bpy.types import Menu
|
|
|
|
|
|
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.has_datablock:
|
|
# tool.has_datablock is always true for tools that 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':
|
|
if space_data.show_uvedit:
|
|
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:
|
|
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
|
|
elif mode == 'UV_SCULPT':
|
|
return tool_settings.uv_sculpt
|
|
# 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 == 'SCULPT_CURVES':
|
|
return tool_settings.curves_sculpt
|
|
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 = "Brushes"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
settings = self.paint_settings(context)
|
|
brush = settings.brush
|
|
|
|
row = layout.row()
|
|
large_preview = True
|
|
if large_preview:
|
|
row.column().template_ID_preview(settings, "brush", new="brush.add", rows=3, cols=8, hide_buttons=False)
|
|
else:
|
|
row.column().template_ID(settings, "brush", new="brush.add")
|
|
col = row.column()
|
|
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
|
|
|
|
if brush is not None:
|
|
col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="")
|
|
|
|
if brush.use_custom_icon:
|
|
layout.prop(brush, "icon_filepath", 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()
|
|
col.prop(settings, "input_samples")
|
|
|
|
|
|
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.prop(brush, "use_smooth_stroke", text="")
|
|
|
|
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.prop(settings, "show_brush", text="")
|
|
row.label(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")
|
|
|
|
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")
|
|
layout.prop(brush.curves_sculpt_settings, "curve_length")
|
|
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 = layout.column()
|
|
col.active = not brush.curves_sculpt_settings.interpolate_length
|
|
col.prop(brush.curves_sculpt_settings, "curve_length")
|
|
|
|
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':
|
|
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. """
|
|
|
|
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
|
|
|
|
# UV Sculpt #
|
|
if mode == 'UV_SCULPT':
|
|
size = True
|
|
strength = True
|
|
|
|
# Sculpt Curves #
|
|
if mode == 'SCULPT_CURVES':
|
|
size = True
|
|
strength = True
|
|
direction = brush.curves_sculpt_tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
|
|
|
|
### 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':
|
|
capabilities = brush.sculpt_capabilities
|
|
use_accumulate = capabilities.has_accumulate
|
|
use_frontface = True
|
|
|
|
col = layout.column(heading="Auto-Masking", align=True)
|
|
|
|
# topology automasking
|
|
col.prop(brush, "use_automasking_topology", text="Topology")
|
|
|
|
# face masks automasking
|
|
col.prop(brush, "use_automasking_face_sets", text="Face Sets")
|
|
|
|
# boundary edges/face sets automasking
|
|
col.prop(brush, "use_automasking_boundary_edges", text="Mesh Boundary")
|
|
col.prop(brush, "use_automasking_boundary_face_sets", text="Face Sets Boundary")
|
|
col.prop(brush, "automasking_boundary_edges_propagation_steps")
|
|
|
|
layout.separator()
|
|
|
|
# sculpt plane settings
|
|
if capabilities.has_sculpt_plane:
|
|
layout.prop(brush, "sculpt_plane")
|
|
col = layout.column(heading="Use Original", align=True)
|
|
col.prop(brush, "use_original_normal", text="Normal")
|
|
col.prop(brush, "use_original_plane", text="Plane")
|
|
layout.separator()
|
|
|
|
# 3D and 2D Texture Paint.
|
|
elif mode in {'PAINT_TEXTURE', 'PAINT_2D'}:
|
|
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, "use_alpha")
|
|
if brush.vertex_tool != 'SMEAR':
|
|
use_accumulate = True
|
|
use_frontface = True
|
|
|
|
# Weight Paint
|
|
elif mode == 'PAINT_WEIGHT':
|
|
if brush.weight_tool != 'SMEAR':
|
|
use_accumulate = True
|
|
use_frontface = True
|
|
|
|
# Draw shared settings.
|
|
if use_accumulate:
|
|
layout.prop(brush, "use_accumulate")
|
|
|
|
if use_frontface:
|
|
layout.prop(brush, "use_frontface", text="Front Faces Only")
|
|
|
|
|
|
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")
|
|
|
|
|
|
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.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':
|
|
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")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(settings, "show_brush", text="Display Cursor")
|
|
|
|
# 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_gpencil_sculpt_settings(layout, _context, brush, *, compact=False):
|
|
gp_settings = brush.gpencil_settings
|
|
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(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'}:
|
|
layout.row().prop(gp_settings, "direction", expand=True)
|
|
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 = layout.row(align=True)
|
|
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
|
|
|
|
|
|
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="")
|
|
|
|
layout.prop(brush, "weight", slider=True)
|
|
|
|
|
|
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")
|
|
|
|
|
|
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)
|