1 # SPDX-License-Identifier: GPL-2.0-or-later
4 from bpy
.types
import (
8 from bpy
.props
import BoolProperty
9 from . import utils_core
11 from bl_ui
.properties_paint_common
import UnifiedPaintPanel
13 class BrushOptionsMenu(Menu
):
14 bl_label
= "Brush Options"
15 bl_idname
= "VIEW3D_MT_sv3_brush_options"
18 def poll(self
, context
):
19 return utils_core
.get_mode() in (
20 'SCULPT', 'VERTEX_PAINT',
21 'WEIGHT_PAINT', 'TEXTURE_PAINT',
25 def draw(self
, context
):
26 mode
= utils_core
.get_mode()
29 # add generic menu items
30 layout
.operator_context
= 'INVOKE_REGION_WIN'
31 layout
.operator("wm.search_menu", text
="Search", icon
='VIEWZOOM')
32 layout
.operator("wm.toolbar", text
="Tools", icon
='TOOL_SETTINGS')
33 layout
.menu("SCREEN_MT_user_menu", text
="Quick Favorites", icon
='HEART')
34 layout
.operator_menu_enum("object.mode_set", "mode",
35 text
="Interactive Mode", icon
='VIEW3D')
39 # add mode specific menu items
41 self
.sculpt(mode
, layout
, context
)
43 elif mode
in ('VERTEX_PAINT', 'WEIGHT_PAINT'):
44 self
.vw_paint(mode
, layout
, context
)
46 elif mode
== 'TEXTURE_PAINT':
47 self
.texpaint(mode
, layout
, context
)
50 self
.particle(layout
, context
)
52 def sculpt(self
, mode
, layout
, context
):
53 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
54 icons
= brushes
.get_brush_icon(mode
, has_brush
.sculpt_tool
) if \
55 has_brush
else "BRUSH_DATA"
57 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
60 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
63 # if the active brush is unlinked these menus don't do anything
64 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
65 layout
.row().menu(BrushAutosmoothMenu
.bl_idname
)
66 layout
.row().menu(BrushModeMenu
.bl_idname
)
67 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
68 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
69 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
71 layout
.row().menu("VIEW3D_MT_sv3_dyntopo")
72 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
74 def vw_paint(self
, mode
, layout
, context
):
75 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
76 icons
= brushes
.get_brush_icon(mode
, has_brush
.vertex_tool
) if \
77 has_brush
else "BRUSH_DATA"
79 if mode
== 'VERTEX_PAINT':
80 layout
.row().operator(ColorPickerPopup
.bl_idname
, icon
="COLOR")
81 layout
.row().separator()
83 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
86 if mode
== 'VERTEX_PAINT':
87 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
90 # if the active brush is unlinked these menus don't do anything
91 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
92 layout
.row().menu(BrushModeMenu
.bl_idname
)
93 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
94 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
95 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
97 if mode
== 'WEIGHT_PAINT':
98 layout
.row().menu(BrushWeightMenu
.bl_idname
)
99 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
102 # if the active brush is unlinked these menus don't do anything
103 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
104 layout
.row().menu(BrushModeMenu
.bl_idname
)
105 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
106 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
108 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
110 def texpaint(self
, mode
, layout
, context
):
111 toolsettings
= context
.tool_settings
.image_paint
113 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
114 icons
= brushes
.get_brush_icon(mode
, has_brush
.image_tool
) if \
115 has_brush
else "BRUSH_DATA"
117 if context
.image_paint_object
and not toolsettings
.detect_data():
118 if toolsettings
.missing_uvs
:
119 layout
.row().label(text
="Missing UVs", icon
='ERROR')
120 layout
.row().operator("paint.add_simple_uvs")
124 elif toolsettings
.missing_materials
or toolsettings
.missing_texture
:
125 layout
.row().label(text
="Missing Data", icon
='ERROR')
126 layout
.row().operator_menu_enum("paint.add_texture_paint_slot", \
129 text
="Add Texture Paint Slot")
133 elif toolsettings
.missing_stencil
:
134 layout
.row().label(text
="Missing Data", icon
='ERROR')
135 layout
.row().label(text
="See Mask Properties", icon
='FORWARD')
136 layout
.row().separator()
137 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
143 layout
.row().label(text
="Missing Data", icon
="INFO")
146 if has_brush
and has_brush
.image_tool
in {'DRAW', 'FILL'} and \
147 has_brush
.blend
not in {'ERASE_ALPHA', 'ADD_ALPHA'}:
148 layout
.row().operator(ColorPickerPopup
.bl_idname
, icon
="COLOR")
149 layout
.row().separator()
151 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
155 # if the active brush is unlinked these menus don't do anything
156 if has_brush
and has_brush
.image_tool
in {'MASK'}:
157 layout
.row().menu(BrushWeightMenu
.bl_idname
, text
="Mask Value")
159 if has_brush
and has_brush
.image_tool
not in {'FILL'}:
160 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
162 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
164 if has_brush
and has_brush
.image_tool
in {'DRAW'}:
165 layout
.row().menu(BrushModeMenu
.bl_idname
)
167 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
168 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
169 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
171 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
173 def particle(self
, layout
, context
):
174 particle_edit
= context
.tool_settings
.particle_edit
176 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
178 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
180 if particle_edit
.tool
!= 'ADD':
181 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
183 layout
.row().menu(ParticleCountMenu
.bl_idname
)
184 layout
.row().separator()
185 layout
.row().prop(particle_edit
, "use_default_interpolate", toggle
=True)
187 layout
.row().prop(particle_edit
.brush
, "steps", slider
=True)
188 layout
.row().prop(particle_edit
, "default_key_count", slider
=True)
190 if particle_edit
.tool
== 'LENGTH':
191 layout
.row().separator()
192 layout
.row().menu(ParticleLengthMenu
.bl_idname
)
194 if particle_edit
.tool
== 'PUFF':
195 layout
.row().separator()
196 layout
.row().menu(ParticlePuffMenu
.bl_idname
)
197 layout
.row().prop(particle_edit
.brush
, "use_puff_volume", toggle
=True)
199 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
202 class BrushRadiusMenu(Menu
):
204 bl_idname
= "VIEW3D_MT_sv3_brush_radius_menu"
205 bl_description
= "Change the size of the brushes"
208 if utils_core
.get_mode() == 'PARTICLE_EDIT':
209 settings
= (("100", 100),
216 datapath
= "tool_settings.particle_edit.brush.size"
217 proppath
= bpy
.context
.tool_settings
.particle_edit
.brush
220 settings
= (("200", 200),
227 datapath
= "tool_settings.unified_paint_settings.size"
228 proppath
= bpy
.context
.tool_settings
.unified_paint_settings
230 return settings
, datapath
, proppath
232 def draw(self
, context
):
233 settings
, datapath
, proppath
= self
.init()
237 layout
.row().prop(proppath
, "size", slider
=True)
238 layout
.row().separator()
240 # add the rest of the menu items
241 for i
in range(len(settings
)):
243 layout
.row(), settings
[i
][0], settings
[i
][1],
244 datapath
, icon
='RADIOBUT_OFF', disable
=True,
245 disable_icon
='RADIOBUT_ON'
249 class BrushStrengthMenu(Menu
):
250 bl_label
= "Strength"
251 bl_idname
= "VIEW3D_MT_sv3_brush_strength_menu"
254 mode
= utils_core
.get_mode()
255 settings
= (("1.0", 1.0),
262 proppath
= utils_core
.get_brush_link(bpy
.context
, types
="brush")
265 datapath
= "tool_settings.sculpt.brush.strength"
267 elif mode
== 'VERTEX_PAINT':
268 datapath
= "tool_settings.vertex_paint.brush.strength"
270 elif mode
== 'WEIGHT_PAINT':
271 datapath
= "tool_settings.weight_paint.brush.strength"
273 elif mode
== 'TEXTURE_PAINT':
274 datapath
= "tool_settings.image_paint.brush.strength"
277 datapath
= "tool_settings.particle_edit.brush.strength"
278 proppath
= bpy
.context
.tool_settings
.particle_edit
.brush
280 return settings
, datapath
, proppath
282 def draw(self
, context
):
283 settings
, datapath
, proppath
= self
.init()
288 layout
.row().prop(proppath
, "strength", slider
=True)
289 layout
.row().separator()
291 # add the rest of the menu items
292 for i
in range(len(settings
)):
294 layout
.row(), settings
[i
][0], settings
[i
][1],
295 datapath
, icon
='RADIOBUT_OFF', disable
=True,
296 disable_icon
='RADIOBUT_ON'
299 layout
.row().label(text
="No brushes available", icon
="INFO")
302 class BrushModeMenu(Menu
):
303 bl_label
= "Brush Mode"
304 bl_idname
= "VIEW3D_MT_sv3_brush_mode_menu"
307 mode
= utils_core
.get_mode()
308 has_brush
= utils_core
.get_brush_link(bpy
.context
, types
="brush")
311 enum
= has_brush
.bl_rna
.properties
['sculpt_plane'].enum_items
if \
313 path
= "tool_settings.sculpt.brush.sculpt_plane"
315 elif mode
== 'VERTEX_PAINT':
316 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
318 path
= "tool_settings.vertex_paint.brush.blend"
320 elif mode
== 'WEIGHT_PAINT':
321 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
323 path
= "tool_settings.weight_paint.brush.blend"
325 elif mode
== 'TEXTURE_PAINT':
326 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
328 path
= "tool_settings.image_paint.brush.blend"
336 def draw(self
, context
):
337 enum
, path
= self
.init()
339 colum_n
= utils_core
.addon_settings()
341 layout
.row().label(text
="Brush Mode")
342 layout
.row().separator()
345 if utils_core
.get_mode() != 'SCULPT':
346 column_flow
= layout
.column_flow(columns
=colum_n
)
348 # add all the brush modes to the menu
351 column_flow
.row(), brush
.name
,
352 brush
.identifier
, path
, icon
='RADIOBUT_OFF',
353 disable
=True, disable_icon
='RADIOBUT_ON'
356 # add all the brush modes to the menu
359 layout
.row(), brush
.name
,
360 brush
.identifier
, path
, icon
='RADIOBUT_OFF',
361 disable
=True, disable_icon
='RADIOBUT_ON'
364 layout
.row().label(text
="No brushes available", icon
="INFO")
367 class BrushAutosmoothMenu(Menu
):
368 bl_label
= "Autosmooth"
369 bl_idname
= "VIEW3D_MT_sv3_brush_autosmooth_menu"
372 settings
= (("1.0", 1.0),
381 def draw(self
, context
):
382 settings
= self
.init()
384 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
388 layout
.row().prop(has_brush
, "auto_smooth_factor", slider
=True)
389 layout
.row().separator()
391 # add the rest of the menu items
392 for i
in range(len(settings
)):
394 layout
.row(), settings
[i
][0], settings
[i
][1],
395 "tool_settings.sculpt.brush.auto_smooth_factor",
396 icon
='RADIOBUT_OFF', disable
=True,
397 disable_icon
='RADIOBUT_ON'
400 layout
.row().label(text
="No Smooth options available", icon
="INFO")
403 class BrushWeightMenu(Menu
):
405 bl_idname
= "VIEW3D_MT_sv3_brush_weight_menu"
408 settings
= (("1.0", 1.0),
415 if utils_core
.get_mode() == 'WEIGHT_PAINT':
416 brush
= bpy
.context
.tool_settings
.unified_paint_settings
417 brushstr
= "tool_settings.unified_paint_settings.weight"
421 brush
= bpy
.context
.tool_settings
.image_paint
.brush
422 brushstr
= "tool_settings.image_paint.brush.weight"
425 return settings
, brush
, brushstr
, name
427 def draw(self
, context
):
428 settings
, brush
, brushstr
, name
= self
.init()
433 layout
.row().prop(brush
, "weight", text
=name
, slider
=True)
434 layout
.row().separator()
436 # add the rest of the menu items
437 for i
in range(len(settings
)):
439 layout
.row(), settings
[i
][0], settings
[i
][1],
441 icon
='RADIOBUT_OFF', disable
=True,
442 disable_icon
='RADIOBUT_ON'
445 layout
.row().label(text
="No brush available", icon
="INFO")
448 class ParticleCountMenu(Menu
):
450 bl_idname
= "VIEW3D_MT_sv3_particle_count_menu"
453 settings
= (("50", 50),
462 def draw(self
, context
):
463 settings
= self
.init()
467 layout
.row().prop(context
.tool_settings
.particle_edit
.brush
,
468 "count", slider
=True)
469 layout
.row().separator()
471 # add the rest of the menu items
472 for i
in range(len(settings
)):
474 layout
.row(), settings
[i
][0], settings
[i
][1],
475 "tool_settings.particle_edit.brush.count",
476 icon
='RADIOBUT_OFF', disable
=True,
477 disable_icon
='RADIOBUT_ON'
481 class ParticleLengthMenu(Menu
):
482 bl_label
= "Length Mode"
483 bl_idname
= "VIEW3D_MT_sv3_particle_length_menu"
485 def draw(self
, context
):
487 path
= "tool_settings.particle_edit.brush.length_mode"
490 for item
in context
.tool_settings
.particle_edit
.brush
. \
491 bl_rna
.properties
['length_mode'].enum_items
:
493 layout
.row(), item
.name
, item
.identifier
, path
,
496 disable_icon
='RADIOBUT_ON'
500 class ParticlePuffMenu(Menu
):
501 bl_label
= "Puff Mode"
502 bl_idname
= "VIEW3D_MT_sv3_particle_puff_menu"
504 def draw(self
, context
):
506 path
= "tool_settings.particle_edit.brush.puff_mode"
509 for item
in context
.tool_settings
.particle_edit
.brush
. \
510 bl_rna
.properties
['puff_mode'].enum_items
:
512 layout
.row(), item
.name
, item
.identifier
, path
,
515 disable_icon
='RADIOBUT_ON'
519 class FlipColorsAll(Operator
):
520 """Switch between Foreground and Background colors"""
521 bl_label
= "Flip Colors"
522 bl_idname
= "view3d.sv3_flip_colors_all"
523 bl_description
= "Switch between Foreground and Background colors"
525 is_tex
: BoolProperty(
530 def execute(self
, context
):
532 if self
.is_tex
is False:
533 color
= context
.tool_settings
.vertex_paint
.brush
.color
534 secondary_color
= context
.tool_settings
.vertex_paint
.brush
.secondary_color
536 orig_prim
= color
.hsv
537 orig_sec
= secondary_color
.hsv
540 secondary_color
.hsv
= orig_prim
542 color
= context
.tool_settings
.image_paint
.brush
.color
543 secondary_color
= context
.tool_settings
.image_paint
.brush
.secondary_color
545 orig_prim
= color
.hsv
546 orig_sec
= secondary_color
.hsv
549 secondary_color
.hsv
= orig_prim
553 except Exception as e
:
554 utils_core
.error_handlers(self
, "view3d.sv3_flip_colors_all", e
,
555 "Flip Colors could not be completed")
560 class ColorPickerPopup(Operator
):
561 """Open Color Picker"""
563 bl_idname
= "view3d.sv3_color_picker_popup"
564 bl_description
= "Open Color Picker"
565 bl_options
= {'REGISTER'}
568 def poll(self
, context
):
569 return utils_core
.get_mode() in (
574 def check(self
, context
):
578 if utils_core
.get_mode() == 'TEXTURE_PAINT':
579 settings
= bpy
.context
.tool_settings
.image_paint
580 brush
= getattr(settings
, "brush", None)
582 settings
= bpy
.context
.tool_settings
.vertex_paint
583 brush
= settings
.brush
584 brush
= getattr(settings
, "brush", None)
586 return settings
, brush
588 def draw(self
, context
):
590 settings
, brush
= self
.init()
594 layout
.row().template_color_picker(brush
, "color", value_slider
=True)
595 prim_sec_row
= layout
.row(align
=True)
596 prim_sec_row
.prop(brush
, "color", text
="")
597 prim_sec_row
.prop(brush
, "secondary_color", text
="")
599 if utils_core
.get_mode() == 'VERTEX_PAINT':
600 prim_sec_row
.operator(
601 FlipColorsAll
.bl_idname
,
602 icon
='FILE_REFRESH', text
=""
605 prim_sec_row
.operator(
606 FlipColorsAll
.bl_idname
,
607 icon
='FILE_REFRESH', text
=""
611 layout
.column().template_palette(settings
, "palette", color
=True)
613 layout
.row().template_ID(settings
, "palette", new
="palette.new")
615 layout
.row().label(text
="No brushes currently available", icon
="INFO")
619 def execute(self
, context
):
620 return context
.window_manager
.invoke_popup(self
, width
=180)
639 bpy
.utils
.register_class(cls
)
643 bpy
.utils
.unregister_class(cls
)