Cleanup: trailing space
[blender-addons.git] / space_view3d_brush_menus / brush_menu.py
blobee7eff25f12248e1a47d51c175a3b48b024aeb4e
1 # gpl author: Ryan Inch (Imaginer)
3 import bpy
4 from bpy.types import (
5 Operator,
6 Menu,
8 from bpy.props import BoolProperty
9 from . import utils_core
10 from . import brushes
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"
17 @classmethod
18 def poll(self, context):
19 return utils_core.get_mode() in (
20 'SCULPT', 'VERTEX_PAINT',
21 'WEIGHT_PAINT', 'TEXTURE_PAINT',
22 'PARTICLE_EDIT'
25 def draw(self, context):
26 mode = utils_core.get_mode()
27 layout = self.layout
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')
36 layout.separator()
39 # add mode specific menu items
40 if mode == 'SCULPT':
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)
49 else:
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.brush_icon[mode][has_brush.sculpt_tool] if \
55 has_brush else "BRUSH_DATA"
57 layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
58 icon=icons)
60 layout.row().menu(BrushRadiusMenu.bl_idname)
62 if has_brush:
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.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",
84 icon=icons)
86 if mode == 'VERTEX_PAINT':
87 layout.row().menu(BrushRadiusMenu.bl_idname)
89 if has_brush:
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)
101 if has_brush:
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 def texpaint(self, mode, layout, context):
109 toolsettings = context.tool_settings.image_paint
111 has_brush = utils_core.get_brush_link(context, types="brush")
112 icons = brushes.brush_icon[mode][has_brush.image_tool] if \
113 has_brush else "BRUSH_DATA"
115 if context.image_paint_object and not toolsettings.detect_data():
116 if toolsettings.missing_uvs:
117 layout.row().label(text="Missing UVs", icon='ERROR')
118 layout.row().operator("paint.add_simple_uvs")
120 return
122 elif toolsettings.missing_materials or toolsettings.missing_texture:
123 layout.row().label(text="Missing Data", icon='ERROR')
124 layout.row().operator_menu_enum("paint.add_texture_paint_slot", \
125 "type", \
126 icon='ADD', \
127 text="Add Texture Paint Slot")
129 return
131 elif toolsettings.missing_stencil:
132 layout.row().label(text="Missing Data", icon='ERROR')
133 layout.row().label(text="See Mask Properties", icon='FORWARD')
134 layout.row().separator()
135 layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
136 icon=icons)
138 return
140 else:
141 layout.row().label(text="Missing Data", icon="INFO")
143 else:
144 if has_brush and has_brush.image_tool in {'DRAW', 'FILL'} and \
145 has_brush.blend not in {'ERASE_ALPHA', 'ADD_ALPHA'}:
146 layout.row().operator(ColorPickerPopup.bl_idname, icon="COLOR")
147 layout.row().separator()
149 layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
150 icon=icons)
152 if has_brush:
153 # if the active brush is unlinked these menus don't do anything
154 if has_brush and has_brush.image_tool in {'MASK'}:
155 layout.row().menu(BrushWeightMenu.bl_idname, text="Mask Value")
157 if has_brush and has_brush.image_tool not in {'FILL'}:
158 layout.row().menu(BrushRadiusMenu.bl_idname)
160 layout.row().menu(BrushStrengthMenu.bl_idname)
162 if has_brush and has_brush.image_tool in {'DRAW'}:
163 layout.row().menu(BrushModeMenu.bl_idname)
165 layout.row().menu("VIEW3D_MT_sv3_texture_menu")
166 layout.row().menu("VIEW3D_MT_sv3_stroke_options")
167 layout.row().menu("VIEW3D_MT_sv3_brush_curve_menu")
169 layout.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
171 def particle(self, layout, context):
172 particle_edit = context.tool_settings.particle_edit
174 layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
175 icon="BRUSH_DATA")
176 layout.row().menu(BrushRadiusMenu.bl_idname)
178 if particle_edit.tool != 'ADD':
179 layout.row().menu(BrushStrengthMenu.bl_idname)
180 else:
181 layout.row().menu(ParticleCountMenu.bl_idname)
182 layout.row().separator()
183 layout.row().prop(particle_edit, "use_default_interpolate", toggle=True)
185 layout.row().prop(particle_edit.brush, "steps", slider=True)
186 layout.row().prop(particle_edit, "default_key_count", slider=True)
188 if particle_edit.tool == 'LENGTH':
189 layout.row().separator()
190 layout.row().menu(ParticleLengthMenu.bl_idname)
192 if particle_edit.tool == 'PUFF':
193 layout.row().separator()
194 layout.row().menu(ParticlePuffMenu.bl_idname)
195 layout.row().prop(particle_edit.brush, "use_puff_volume", toggle=True)
198 class BrushRadiusMenu(Menu):
199 bl_label = "Radius"
200 bl_idname = "VIEW3D_MT_sv3_brush_radius_menu"
201 bl_description = "Change the size of the brushes"
203 def init(self):
204 if utils_core.get_mode() == 'PARTICLE_EDIT':
205 settings = (("100", 100),
206 ("70", 70),
207 ("50", 50),
208 ("30", 30),
209 ("20", 20),
210 ("10", 10))
212 datapath = "tool_settings.particle_edit.brush.size"
213 proppath = bpy.context.tool_settings.particle_edit.brush
215 else:
216 settings = (("200", 200),
217 ("150", 150),
218 ("100", 100),
219 ("50", 50),
220 ("35", 35),
221 ("10", 10))
223 datapath = "tool_settings.unified_paint_settings.size"
224 proppath = bpy.context.tool_settings.unified_paint_settings
226 return settings, datapath, proppath
228 def draw(self, context):
229 settings, datapath, proppath = self.init()
230 layout = self.layout
232 # add the top slider
233 layout.row().prop(proppath, "size", slider=True)
234 layout.row().separator()
236 # add the rest of the menu items
237 for i in range(len(settings)):
238 utils_core.menuprop(
239 layout.row(), settings[i][0], settings[i][1],
240 datapath, icon='RADIOBUT_OFF', disable=True,
241 disable_icon='RADIOBUT_ON'
245 class BrushStrengthMenu(Menu):
246 bl_label = "Strength"
247 bl_idname = "VIEW3D_MT_sv3_brush_strength_menu"
249 def init(self):
250 mode = utils_core.get_mode()
251 settings = (("1.0", 1.0),
252 ("0.7", 0.7),
253 ("0.5", 0.5),
254 ("0.3", 0.3),
255 ("0.2", 0.2),
256 ("0.1", 0.1))
258 proppath = utils_core.get_brush_link(bpy.context, types="brush")
260 if mode == 'SCULPT':
261 datapath = "tool_settings.sculpt.brush.strength"
263 elif mode == 'VERTEX_PAINT':
264 datapath = "tool_settings.vertex_paint.brush.strength"
266 elif mode == 'WEIGHT_PAINT':
267 datapath = "tool_settings.weight_paint.brush.strength"
269 elif mode == 'TEXTURE_PAINT':
270 datapath = "tool_settings.image_paint.brush.strength"
272 else:
273 datapath = "tool_settings.particle_edit.brush.strength"
274 proppath = bpy.context.tool_settings.particle_edit.brush
276 return settings, datapath, proppath
278 def draw(self, context):
279 settings, datapath, proppath = self.init()
280 layout = self.layout
282 # add the top slider
283 if proppath:
284 layout.row().prop(proppath, "strength", slider=True)
285 layout.row().separator()
287 # add the rest of the menu items
288 for i in range(len(settings)):
289 utils_core.menuprop(
290 layout.row(), settings[i][0], settings[i][1],
291 datapath, icon='RADIOBUT_OFF', disable=True,
292 disable_icon='RADIOBUT_ON'
294 else:
295 layout.row().label(text="No brushes available", icon="INFO")
298 class BrushModeMenu(Menu):
299 bl_label = "Brush Mode"
300 bl_idname = "VIEW3D_MT_sv3_brush_mode_menu"
302 def init(self):
303 mode = utils_core.get_mode()
304 has_brush = utils_core.get_brush_link(bpy.context, types="brush")
306 if mode == 'SCULPT':
307 enum = has_brush.bl_rna.properties['sculpt_plane'].enum_items if \
308 has_brush else None
309 path = "tool_settings.sculpt.brush.sculpt_plane"
311 elif mode == 'VERTEX_PAINT':
312 enum = has_brush.bl_rna.properties['blend'].enum_items if \
313 has_brush else None
314 path = "tool_settings.vertex_paint.brush.blend"
316 elif mode == 'WEIGHT_PAINT':
317 enum = has_brush.bl_rna.properties['blend'].enum_items if \
318 has_brush else None
319 path = "tool_settings.weight_paint.brush.blend"
321 elif mode == 'TEXTURE_PAINT':
322 enum = has_brush.bl_rna.properties['blend'].enum_items if \
323 has_brush else None
324 path = "tool_settings.image_paint.brush.blend"
326 else:
327 enum = None
328 path = ""
330 return enum, path
332 def draw(self, context):
333 enum, path = self.init()
334 layout = self.layout
335 colum_n = utils_core.addon_settings()
337 layout.row().label(text="Brush Mode")
338 layout.row().separator()
340 if enum:
341 if utils_core.get_mode() != 'SCULPT':
342 column_flow = layout.column_flow(columns=colum_n)
344 # add all the brush modes to the menu
345 for brush in enum:
346 utils_core.menuprop(
347 column_flow.row(), brush.name,
348 brush.identifier, path, icon='RADIOBUT_OFF',
349 disable=True, disable_icon='RADIOBUT_ON'
351 else:
352 # add all the brush modes to the menu
353 for brush in enum:
354 utils_core.menuprop(
355 layout.row(), brush.name,
356 brush.identifier, path, icon='RADIOBUT_OFF',
357 disable=True, disable_icon='RADIOBUT_ON'
359 else:
360 layout.row().label(text="No brushes available", icon="INFO")
363 class BrushAutosmoothMenu(Menu):
364 bl_label = "Autosmooth"
365 bl_idname = "VIEW3D_MT_sv3_brush_autosmooth_menu"
367 def init(self):
368 settings = (("1.0", 1.0),
369 ("0.7", 0.7),
370 ("0.5", 0.5),
371 ("0.3", 0.3),
372 ("0.2", 0.2),
373 ("0.1", 0.1))
375 return settings
377 def draw(self, context):
378 settings = self.init()
379 layout = self.layout
380 has_brush = utils_core.get_brush_link(context, types="brush")
382 if has_brush:
383 # add the top slider
384 layout.row().prop(has_brush, "auto_smooth_factor", slider=True)
385 layout.row().separator()
387 # add the rest of the menu items
388 for i in range(len(settings)):
389 utils_core.menuprop(
390 layout.row(), settings[i][0], settings[i][1],
391 "tool_settings.sculpt.brush.auto_smooth_factor",
392 icon='RADIOBUT_OFF', disable=True,
393 disable_icon='RADIOBUT_ON'
395 else:
396 layout.row().label(text="No Smooth options available", icon="INFO")
399 class BrushWeightMenu(Menu):
400 bl_label = "Weight"
401 bl_idname = "VIEW3D_MT_sv3_brush_weight_menu"
403 def init(self):
404 settings = (("1.0", 1.0),
405 ("0.7", 0.7),
406 ("0.5", 0.5),
407 ("0.3", 0.3),
408 ("0.2", 0.2),
409 ("0.1", 0.1))
411 if utils_core.get_mode() == 'WEIGHT_PAINT':
412 brush = bpy.context.tool_settings.unified_paint_settings
413 brushstr = "tool_settings.unified_paint_settings.weight"
414 name = "Weight"
416 else:
417 brush = bpy.context.tool_settings.image_paint.brush
418 brushstr = "tool_settings.image_paint.brush.weight"
419 name = "Mask Value"
421 return settings, brush, brushstr, name
423 def draw(self, context):
424 settings, brush, brushstr, name = self.init()
425 layout = self.layout
427 if brush:
428 # add the top slider
429 layout.row().prop(brush, "weight", text=name, slider=True)
430 layout.row().separator()
432 # add the rest of the menu items
433 for i in range(len(settings)):
434 utils_core.menuprop(
435 layout.row(), settings[i][0], settings[i][1],
436 brushstr,
437 icon='RADIOBUT_OFF', disable=True,
438 disable_icon='RADIOBUT_ON'
440 else:
441 layout.row().label(text="No brush available", icon="INFO")
444 class ParticleCountMenu(Menu):
445 bl_label = "Count"
446 bl_idname = "VIEW3D_MT_sv3_particle_count_menu"
448 def init(self):
449 settings = (("50", 50),
450 ("25", 25),
451 ("10", 10),
452 ("5", 5),
453 ("3", 3),
454 ("1", 1))
456 return settings
458 def draw(self, context):
459 settings = self.init()
460 layout = self.layout
462 # add the top slider
463 layout.row().prop(context.tool_settings.particle_edit.brush,
464 "count", slider=True)
465 layout.row().separator()
467 # add the rest of the menu items
468 for i in range(len(settings)):
469 utils_core.menuprop(
470 layout.row(), settings[i][0], settings[i][1],
471 "tool_settings.particle_edit.brush.count",
472 icon='RADIOBUT_OFF', disable=True,
473 disable_icon='RADIOBUT_ON'
477 class ParticleLengthMenu(Menu):
478 bl_label = "Length Mode"
479 bl_idname = "VIEW3D_MT_sv3_particle_length_menu"
481 def draw(self, context):
482 layout = self.layout
483 path = "tool_settings.particle_edit.brush.length_mode"
485 # add the menu items
486 for item in context.tool_settings.particle_edit.brush. \
487 bl_rna.properties['length_mode'].enum_items:
488 utils_core.menuprop(
489 layout.row(), item.name, item.identifier, path,
490 icon='RADIOBUT_OFF',
491 disable=True,
492 disable_icon='RADIOBUT_ON'
496 class ParticlePuffMenu(Menu):
497 bl_label = "Puff Mode"
498 bl_idname = "VIEW3D_MT_sv3_particle_puff_menu"
500 def draw(self, context):
501 layout = self.layout
502 path = "tool_settings.particle_edit.brush.puff_mode"
504 # add the menu items
505 for item in context.tool_settings.particle_edit.brush. \
506 bl_rna.properties['puff_mode'].enum_items:
507 utils_core.menuprop(
508 layout.row(), item.name, item.identifier, path,
509 icon='RADIOBUT_OFF',
510 disable=True,
511 disable_icon='RADIOBUT_ON'
515 class FlipColorsAll(Operator):
516 """Switch between Foreground and Background colors"""
517 bl_label = "Flip Colors"
518 bl_idname = "view3d.sv3_flip_colors_all"
519 bl_description = "Switch between Foreground and Background colors"
521 is_tex: BoolProperty(
522 default=False,
523 options={'HIDDEN'}
526 def execute(self, context):
527 try:
528 if self.is_tex is False:
529 color = context.tool_settings.vertex_paint.brush.color
530 secondary_color = context.tool_settings.vertex_paint.brush.secondary_color
532 orig_prim = color.hsv
533 orig_sec = secondary_color.hsv
535 color.hsv = orig_sec
536 secondary_color.hsv = orig_prim
537 else:
538 color = context.tool_settings.image_paint.brush.color
539 secondary_color = context.tool_settings.image_paint.brush.secondary_color
541 orig_prim = color.hsv
542 orig_sec = secondary_color.hsv
544 color.hsv = orig_sec
545 secondary_color.hsv = orig_prim
547 return {'FINISHED'}
549 except Exception as e:
550 utils_core.error_handlers(self, "view3d.sv3_flip_colors_all", e,
551 "Flip Colors could not be completed")
553 return {'CANCELLED'}
556 class ColorPickerPopup(Operator):
557 """Open Color Picker"""
558 bl_label = "Color"
559 bl_idname = "view3d.sv3_color_picker_popup"
560 bl_description = "Open Color Picker"
561 bl_options = {'REGISTER'}
563 @classmethod
564 def poll(self, context):
565 return utils_core.get_mode() in (
566 'VERTEX_PAINT',
567 'TEXTURE_PAINT'
570 def check(self, context):
571 return True
573 def init(self):
574 if utils_core.get_mode() == 'TEXTURE_PAINT':
575 settings = bpy.context.tool_settings.image_paint
576 brush = getattr(settings, "brush", None)
577 else:
578 settings = bpy.context.tool_settings.vertex_paint
579 brush = settings.brush
580 brush = getattr(settings, "brush", None)
582 return settings, brush
584 def draw(self, context):
585 layout = self.layout
586 settings, brush = self.init()
589 if brush:
590 layout.row().template_color_picker(brush, "color", value_slider=True)
591 prim_sec_row = layout.row(align=True)
592 prim_sec_row.prop(brush, "color", text="")
593 prim_sec_row.prop(brush, "secondary_color", text="")
595 if utils_core.get_mode() == 'VERTEX_PAINT':
596 prim_sec_row.operator(
597 FlipColorsAll.bl_idname,
598 icon='FILE_REFRESH', text=""
599 ).is_tex = False
600 else:
601 prim_sec_row.operator(
602 FlipColorsAll.bl_idname,
603 icon='FILE_REFRESH', text=""
604 ).is_tex = True
606 if settings.palette:
607 layout.column().template_palette(settings, "palette", color=True)
609 layout.row().template_ID(settings, "palette", new="palette.new")
610 else:
611 layout.row().label(text="No brushes currently available", icon="INFO")
613 return
615 def execute(self, context):
616 return context.window_manager.invoke_popup(self, width=180)
619 classes = (
620 BrushOptionsMenu,
621 BrushRadiusMenu,
622 BrushStrengthMenu,
623 BrushModeMenu,
624 BrushAutosmoothMenu,
625 BrushWeightMenu,
626 ParticleCountMenu,
627 ParticleLengthMenu,
628 ParticlePuffMenu,
629 FlipColorsAll,
630 ColorPickerPopup
633 def register():
634 for cls in classes:
635 bpy.utils.register_class(cls)
637 def unregister():
638 for cls in classes:
639 bpy.utils.unregister_class(cls)