1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 from bpy
.types
import (
10 from bpy
.props
import BoolProperty
11 from . import utils_core
13 from bl_ui
.properties_paint_common
import UnifiedPaintPanel
15 class BrushOptionsMenu(Menu
):
16 bl_label
= "Brush Options"
17 bl_idname
= "VIEW3D_MT_sv3_brush_options"
20 def poll(self
, context
):
21 return utils_core
.get_mode() in (
22 'SCULPT', 'VERTEX_PAINT',
23 'WEIGHT_PAINT', 'TEXTURE_PAINT',
27 def draw(self
, context
):
28 mode
= utils_core
.get_mode()
31 # add generic menu items
32 layout
.operator_context
= 'INVOKE_REGION_WIN'
33 layout
.operator("wm.search_menu", text
="Search", icon
='VIEWZOOM')
34 layout
.operator("wm.toolbar", text
="Tools", icon
='TOOL_SETTINGS')
35 layout
.menu("SCREEN_MT_user_menu", text
="Quick Favorites", icon
='HEART')
36 layout
.operator_menu_enum("object.mode_set", "mode",
37 text
="Interactive Mode", icon
='VIEW3D')
41 # add mode specific menu items
43 self
.sculpt(mode
, layout
, context
)
45 elif mode
in ('VERTEX_PAINT', 'WEIGHT_PAINT'):
46 self
.vw_paint(mode
, layout
, context
)
48 elif mode
== 'TEXTURE_PAINT':
49 self
.texpaint(mode
, layout
, context
)
52 self
.particle(layout
, context
)
54 def sculpt(self
, mode
, layout
, context
):
55 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
56 icons
= brushes
.get_brush_icon(mode
, has_brush
.sculpt_tool
) if \
57 has_brush
else "BRUSH_DATA"
59 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
62 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
65 # if the active brush is unlinked these menus don't do anything
66 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
67 layout
.row().menu(BrushAutosmoothMenu
.bl_idname
)
68 layout
.row().menu(BrushModeMenu
.bl_idname
)
69 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
70 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
71 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
73 layout
.row().menu("VIEW3D_MT_sv3_dyntopo")
74 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
76 def vw_paint(self
, mode
, layout
, context
):
77 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
78 icons
= brushes
.get_brush_icon(mode
, has_brush
.vertex_tool
) if \
79 has_brush
else "BRUSH_DATA"
81 if mode
== 'VERTEX_PAINT':
82 layout
.row().operator(ColorPickerPopup
.bl_idname
, icon
="COLOR")
83 layout
.row().separator()
85 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
88 if mode
== 'VERTEX_PAINT':
89 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
92 # if the active brush is unlinked these menus don't do anything
93 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
94 layout
.row().menu(BrushModeMenu
.bl_idname
)
95 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
96 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
97 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
99 if mode
== 'WEIGHT_PAINT':
100 layout
.row().menu(BrushWeightMenu
.bl_idname
)
101 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
104 # if the active brush is unlinked these menus don't do anything
105 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
106 layout
.row().menu(BrushModeMenu
.bl_idname
)
107 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
108 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
110 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
112 def texpaint(self
, mode
, layout
, context
):
113 toolsettings
= context
.tool_settings
.image_paint
115 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
116 icons
= brushes
.get_brush_icon(mode
, has_brush
.image_tool
) if \
117 has_brush
else "BRUSH_DATA"
119 if context
.image_paint_object
and not toolsettings
.detect_data():
120 if toolsettings
.missing_uvs
:
121 layout
.row().label(text
="Missing UVs", icon
='ERROR')
122 layout
.row().operator("paint.add_simple_uvs")
126 elif toolsettings
.missing_materials
or toolsettings
.missing_texture
:
127 layout
.row().label(text
="Missing Data", icon
='ERROR')
128 layout
.row().operator_menu_enum("paint.add_texture_paint_slot", \
131 text
="Add Texture Paint Slot")
135 elif toolsettings
.missing_stencil
:
136 layout
.row().label(text
="Missing Data", icon
='ERROR')
137 layout
.row().label(text
="See Mask Properties", icon
='FORWARD')
138 layout
.row().separator()
139 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
145 layout
.row().label(text
="Missing Data", icon
="INFO")
148 if has_brush
and has_brush
.image_tool
in {'DRAW', 'FILL'} and \
149 has_brush
.blend
not in {'ERASE_ALPHA', 'ADD_ALPHA'}:
150 layout
.row().operator(ColorPickerPopup
.bl_idname
, icon
="COLOR")
151 layout
.row().separator()
153 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
157 # if the active brush is unlinked these menus don't do anything
158 if has_brush
and has_brush
.image_tool
in {'MASK'}:
159 layout
.row().menu(BrushWeightMenu
.bl_idname
, text
="Mask Value")
161 if has_brush
and has_brush
.image_tool
not in {'FILL'}:
162 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
164 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
166 if has_brush
and has_brush
.image_tool
in {'DRAW'}:
167 layout
.row().menu(BrushModeMenu
.bl_idname
)
169 layout
.row().menu("VIEW3D_MT_sv3_texture_menu")
170 layout
.row().menu("VIEW3D_MT_sv3_stroke_options")
171 layout
.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
173 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
175 def particle(self
, layout
, context
):
176 particle_edit
= context
.tool_settings
.particle_edit
178 layout
.row().menu("VIEW3D_MT_sv3_brushes_menu",
180 layout
.row().menu(BrushRadiusMenu
.bl_idname
)
182 if particle_edit
.tool
!= 'ADD':
183 layout
.row().menu(BrushStrengthMenu
.bl_idname
)
185 layout
.row().menu(ParticleCountMenu
.bl_idname
)
186 layout
.row().separator()
187 layout
.row().prop(particle_edit
, "use_default_interpolate", toggle
=True)
189 layout
.row().prop(particle_edit
.brush
, "steps", slider
=True)
190 layout
.row().prop(particle_edit
, "default_key_count", slider
=True)
192 if particle_edit
.tool
== 'LENGTH':
193 layout
.row().separator()
194 layout
.row().menu(ParticleLengthMenu
.bl_idname
)
196 if particle_edit
.tool
== 'PUFF':
197 layout
.row().separator()
198 layout
.row().menu(ParticlePuffMenu
.bl_idname
)
199 layout
.row().prop(particle_edit
.brush
, "use_puff_volume", toggle
=True)
201 layout
.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
204 class BrushRadiusMenu(Menu
):
206 bl_idname
= "VIEW3D_MT_sv3_brush_radius_menu"
207 bl_description
= "Change the size of the brushes"
210 if utils_core
.get_mode() == 'PARTICLE_EDIT':
211 settings
= (("100", 100),
218 datapath
= "tool_settings.particle_edit.brush.size"
219 proppath
= bpy
.context
.tool_settings
.particle_edit
.brush
222 settings
= (("200", 200),
229 datapath
= "tool_settings.unified_paint_settings.size"
230 proppath
= bpy
.context
.tool_settings
.unified_paint_settings
232 return settings
, datapath
, proppath
234 def draw(self
, context
):
235 settings
, datapath
, proppath
= self
.init()
239 layout
.row().prop(proppath
, "size", slider
=True)
240 layout
.row().separator()
242 # add the rest of the menu items
243 for i
in range(len(settings
)):
245 layout
.row(), settings
[i
][0], settings
[i
][1],
246 datapath
, icon
='RADIOBUT_OFF', disable
=True,
247 disable_icon
='RADIOBUT_ON'
251 class BrushStrengthMenu(Menu
):
252 bl_label
= "Strength"
253 bl_idname
= "VIEW3D_MT_sv3_brush_strength_menu"
256 mode
= utils_core
.get_mode()
257 settings
= (("1.0", 1.0),
264 proppath
= utils_core
.get_brush_link(bpy
.context
, types
="brush")
267 datapath
= "tool_settings.sculpt.brush.strength"
269 elif mode
== 'VERTEX_PAINT':
270 datapath
= "tool_settings.vertex_paint.brush.strength"
272 elif mode
== 'WEIGHT_PAINT':
273 datapath
= "tool_settings.weight_paint.brush.strength"
275 elif mode
== 'TEXTURE_PAINT':
276 datapath
= "tool_settings.image_paint.brush.strength"
279 datapath
= "tool_settings.particle_edit.brush.strength"
280 proppath
= bpy
.context
.tool_settings
.particle_edit
.brush
282 return settings
, datapath
, proppath
284 def draw(self
, context
):
285 settings
, datapath
, proppath
= self
.init()
290 layout
.row().prop(proppath
, "strength", slider
=True)
291 layout
.row().separator()
293 # add the rest of the menu items
294 for i
in range(len(settings
)):
296 layout
.row(), settings
[i
][0], settings
[i
][1],
297 datapath
, icon
='RADIOBUT_OFF', disable
=True,
298 disable_icon
='RADIOBUT_ON'
301 layout
.row().label(text
="No brushes available", icon
="INFO")
304 class BrushModeMenu(Menu
):
305 bl_label
= "Brush Mode"
306 bl_idname
= "VIEW3D_MT_sv3_brush_mode_menu"
309 mode
= utils_core
.get_mode()
310 has_brush
= utils_core
.get_brush_link(bpy
.context
, types
="brush")
313 enum
= has_brush
.bl_rna
.properties
['sculpt_plane'].enum_items
if \
315 path
= "tool_settings.sculpt.brush.sculpt_plane"
317 elif mode
== 'VERTEX_PAINT':
318 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
320 path
= "tool_settings.vertex_paint.brush.blend"
322 elif mode
== 'WEIGHT_PAINT':
323 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
325 path
= "tool_settings.weight_paint.brush.blend"
327 elif mode
== 'TEXTURE_PAINT':
328 enum
= has_brush
.bl_rna
.properties
['blend'].enum_items
if \
330 path
= "tool_settings.image_paint.brush.blend"
338 def draw(self
, context
):
339 enum
, path
= self
.init()
341 colum_n
= utils_core
.addon_settings()
343 layout
.row().label(text
="Brush Mode")
344 layout
.row().separator()
347 if utils_core
.get_mode() != 'SCULPT':
348 column_flow
= layout
.column_flow(columns
=colum_n
)
350 # add all the brush modes to the menu
353 column_flow
.row(), brush
.name
,
354 brush
.identifier
, path
, icon
='RADIOBUT_OFF',
355 disable
=True, disable_icon
='RADIOBUT_ON'
358 # add all the brush modes to the menu
361 layout
.row(), brush
.name
,
362 brush
.identifier
, path
, icon
='RADIOBUT_OFF',
363 disable
=True, disable_icon
='RADIOBUT_ON'
366 layout
.row().label(text
="No brushes available", icon
="INFO")
369 class BrushAutosmoothMenu(Menu
):
370 bl_label
= "Autosmooth"
371 bl_idname
= "VIEW3D_MT_sv3_brush_autosmooth_menu"
374 settings
= (("1.0", 1.0),
383 def draw(self
, context
):
384 settings
= self
.init()
386 has_brush
= utils_core
.get_brush_link(context
, types
="brush")
390 layout
.row().prop(has_brush
, "auto_smooth_factor", slider
=True)
391 layout
.row().separator()
393 # add the rest of the menu items
394 for i
in range(len(settings
)):
396 layout
.row(), settings
[i
][0], settings
[i
][1],
397 "tool_settings.sculpt.brush.auto_smooth_factor",
398 icon
='RADIOBUT_OFF', disable
=True,
399 disable_icon
='RADIOBUT_ON'
402 layout
.row().label(text
="No Smooth options available", icon
="INFO")
405 class BrushWeightMenu(Menu
):
407 bl_idname
= "VIEW3D_MT_sv3_brush_weight_menu"
410 settings
= (("1.0", 1.0),
417 if utils_core
.get_mode() == 'WEIGHT_PAINT':
418 brush
= bpy
.context
.tool_settings
.unified_paint_settings
419 brushstr
= "tool_settings.unified_paint_settings.weight"
423 brush
= bpy
.context
.tool_settings
.image_paint
.brush
424 brushstr
= "tool_settings.image_paint.brush.weight"
427 return settings
, brush
, brushstr
, name
429 def draw(self
, context
):
430 settings
, brush
, brushstr
, name
= self
.init()
435 layout
.row().prop(brush
, "weight", text
=name
, slider
=True)
436 layout
.row().separator()
438 # add the rest of the menu items
439 for i
in range(len(settings
)):
441 layout
.row(), settings
[i
][0], settings
[i
][1],
443 icon
='RADIOBUT_OFF', disable
=True,
444 disable_icon
='RADIOBUT_ON'
447 layout
.row().label(text
="No brush available", icon
="INFO")
450 class ParticleCountMenu(Menu
):
452 bl_idname
= "VIEW3D_MT_sv3_particle_count_menu"
455 settings
= (("50", 50),
464 def draw(self
, context
):
465 settings
= self
.init()
469 layout
.row().prop(context
.tool_settings
.particle_edit
.brush
,
470 "count", slider
=True)
471 layout
.row().separator()
473 # add the rest of the menu items
474 for i
in range(len(settings
)):
476 layout
.row(), settings
[i
][0], settings
[i
][1],
477 "tool_settings.particle_edit.brush.count",
478 icon
='RADIOBUT_OFF', disable
=True,
479 disable_icon
='RADIOBUT_ON'
483 class ParticleLengthMenu(Menu
):
484 bl_label
= "Length Mode"
485 bl_idname
= "VIEW3D_MT_sv3_particle_length_menu"
487 def draw(self
, context
):
489 path
= "tool_settings.particle_edit.brush.length_mode"
492 for item
in context
.tool_settings
.particle_edit
.brush
. \
493 bl_rna
.properties
['length_mode'].enum_items
:
495 layout
.row(), item
.name
, item
.identifier
, path
,
498 disable_icon
='RADIOBUT_ON'
502 class ParticlePuffMenu(Menu
):
503 bl_label
= "Puff Mode"
504 bl_idname
= "VIEW3D_MT_sv3_particle_puff_menu"
506 def draw(self
, context
):
508 path
= "tool_settings.particle_edit.brush.puff_mode"
511 for item
in context
.tool_settings
.particle_edit
.brush
. \
512 bl_rna
.properties
['puff_mode'].enum_items
:
514 layout
.row(), item
.name
, item
.identifier
, path
,
517 disable_icon
='RADIOBUT_ON'
521 class FlipColorsAll(Operator
):
522 """Switch between Foreground and Background colors"""
523 bl_label
= "Flip Colors"
524 bl_idname
= "view3d.sv3_flip_colors_all"
525 bl_description
= "Switch between Foreground and Background colors"
527 is_tex
: BoolProperty(
532 def execute(self
, context
):
534 if self
.is_tex
is False:
535 color
= context
.tool_settings
.vertex_paint
.brush
.color
536 secondary_color
= context
.tool_settings
.vertex_paint
.brush
.secondary_color
538 orig_prim
= color
.hsv
539 orig_sec
= secondary_color
.hsv
542 secondary_color
.hsv
= orig_prim
544 color
= context
.tool_settings
.image_paint
.brush
.color
545 secondary_color
= context
.tool_settings
.image_paint
.brush
.secondary_color
547 orig_prim
= color
.hsv
548 orig_sec
= secondary_color
.hsv
551 secondary_color
.hsv
= orig_prim
555 except Exception as e
:
556 utils_core
.error_handlers(self
, "view3d.sv3_flip_colors_all", e
,
557 "Flip Colors could not be completed")
562 class ColorPickerPopup(Operator
):
563 """Open Color Picker"""
565 bl_idname
= "view3d.sv3_color_picker_popup"
566 bl_description
= "Open Color Picker"
567 bl_options
= {'REGISTER'}
570 def poll(self
, context
):
571 return utils_core
.get_mode() in (
576 def check(self
, context
):
580 if utils_core
.get_mode() == 'TEXTURE_PAINT':
581 settings
= bpy
.context
.tool_settings
.image_paint
582 brush
= getattr(settings
, "brush", None)
584 settings
= bpy
.context
.tool_settings
.vertex_paint
585 brush
= settings
.brush
586 brush
= getattr(settings
, "brush", None)
588 return settings
, brush
590 def draw(self
, context
):
592 settings
, brush
= self
.init()
596 layout
.row().template_color_picker(brush
, "color", value_slider
=True)
597 prim_sec_row
= layout
.row(align
=True)
598 prim_sec_row
.prop(brush
, "color", text
="")
599 prim_sec_row
.prop(brush
, "secondary_color", text
="")
601 if utils_core
.get_mode() == 'VERTEX_PAINT':
602 prim_sec_row
.operator(
603 FlipColorsAll
.bl_idname
,
604 icon
='FILE_REFRESH', text
=""
607 prim_sec_row
.operator(
608 FlipColorsAll
.bl_idname
,
609 icon
='FILE_REFRESH', text
=""
613 layout
.column().template_palette(settings
, "palette", color
=True)
615 layout
.row().template_ID(settings
, "palette", new
="palette.new")
617 layout
.row().label(text
="No brushes currently available", icon
="INFO")
621 def execute(self
, context
):
622 return context
.window_manager
.invoke_popup(self
, width
=180)
641 bpy
.utils
.register_class(cls
)
645 bpy
.utils
.unregister_class(cls
)