1# gpl author: Ryan Inch (Imaginer)
2
3import bpy
4from bpy.types import (
5        Operator,
6        Menu,
7        )
8from bpy.props import BoolProperty
9from . import utils_core
10from . import brushes
11from bl_ui.properties_paint_common import UnifiedPaintPanel
12
13class BrushOptionsMenu(Menu):
14    bl_label = "Brush Options"
15    bl_idname = "VIEW3D_MT_sv3_brush_options"
16
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'
23                    )
24
25    def draw(self, context):
26        mode = utils_core.get_mode()
27        layout = self.layout
28
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()
37
38
39        # add mode specific menu items
40        if mode == 'SCULPT':
41            self.sculpt(mode, layout, context)
42
43        elif mode in ('VERTEX_PAINT', 'WEIGHT_PAINT'):
44            self.vw_paint(mode, layout, context)
45
46        elif mode == 'TEXTURE_PAINT':
47            self.texpaint(mode, layout, context)
48
49        else:
50            self.particle(layout, context)
51
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"
56
57        layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
58                            icon=icons)
59
60        layout.row().menu(BrushRadiusMenu.bl_idname)
61
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")
70
71        layout.row().menu("VIEW3D_MT_sv3_dyntopo")
72        layout.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
73
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"
78
79        if mode == 'VERTEX_PAINT':
80            layout.row().operator(ColorPickerPopup.bl_idname, icon="COLOR")
81            layout.row().separator()
82
83        layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
84                            icon=icons)
85
86        if mode == 'VERTEX_PAINT':
87            layout.row().menu(BrushRadiusMenu.bl_idname)
88
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")
96
97        if mode == 'WEIGHT_PAINT':
98            layout.row().menu(BrushWeightMenu.bl_idname)
99            layout.row().menu(BrushRadiusMenu.bl_idname)
100
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")
107
108    def texpaint(self, mode, layout, context):
109        toolsettings = context.tool_settings.image_paint
110
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"
114
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")
119
120                return
121
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")
128
129                return
130
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)
137
138                return
139
140            else:
141                layout.row().label(text="Missing Data", icon="INFO")
142
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()
148
149            layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
150                                icon=icons)
151
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")
156
157                if has_brush and has_brush.image_tool not in {'FILL'}:
158                    layout.row().menu(BrushRadiusMenu.bl_idname)
159
160                layout.row().menu(BrushStrengthMenu.bl_idname)
161
162                if has_brush and has_brush.image_tool in {'DRAW'}:
163                    layout.row().menu(BrushModeMenu.bl_idname)
164
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")
168
169            layout.row().menu("VIEW3D_MT_sv3_master_symmetry_menu")
170
171    def particle(self, layout, context):
172        particle_edit = context.tool_settings.particle_edit
173
174        layout.row().menu("VIEW3D_MT_sv3_brushes_menu",
175                            icon="BRUSH_DATA")
176        layout.row().menu(BrushRadiusMenu.bl_idname)
177
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)
184
185            layout.row().prop(particle_edit.brush, "steps", slider=True)
186            layout.row().prop(particle_edit, "default_key_count", slider=True)
187
188        if particle_edit.tool == 'LENGTH':
189            layout.row().separator()
190            layout.row().menu(ParticleLengthMenu.bl_idname)
191
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)
196
197
198class BrushRadiusMenu(Menu):
199    bl_label = "Radius"
200    bl_idname = "VIEW3D_MT_sv3_brush_radius_menu"
201    bl_description = "Change the size of the brushes"
202
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))
211
212            datapath = "tool_settings.particle_edit.brush.size"
213            proppath = bpy.context.tool_settings.particle_edit.brush
214
215        else:
216            settings = (("200", 200),
217                        ("150", 150),
218                        ("100", 100),
219                        ("50", 50),
220                        ("35", 35),
221                        ("10", 10))
222
223            datapath = "tool_settings.unified_paint_settings.size"
224            proppath = bpy.context.tool_settings.unified_paint_settings
225
226        return settings, datapath, proppath
227
228    def draw(self, context):
229        settings, datapath, proppath = self.init()
230        layout = self.layout
231
232        # add the top slider
233        layout.row().prop(proppath, "size", slider=True)
234        layout.row().separator()
235
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'
242                    )
243
244
245class BrushStrengthMenu(Menu):
246    bl_label = "Strength"
247    bl_idname = "VIEW3D_MT_sv3_brush_strength_menu"
248
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))
257
258        proppath = utils_core.get_brush_link(bpy.context, types="brush")
259
260        if mode == 'SCULPT':
261            datapath = "tool_settings.sculpt.brush.strength"
262
263        elif mode == 'VERTEX_PAINT':
264            datapath = "tool_settings.vertex_paint.brush.strength"
265
266        elif mode == 'WEIGHT_PAINT':
267            datapath = "tool_settings.weight_paint.brush.strength"
268
269        elif mode == 'TEXTURE_PAINT':
270            datapath = "tool_settings.image_paint.brush.strength"
271
272        else:
273            datapath = "tool_settings.particle_edit.brush.strength"
274            proppath = bpy.context.tool_settings.particle_edit.brush
275
276        return settings, datapath, proppath
277
278    def draw(self, context):
279        settings, datapath, proppath = self.init()
280        layout = self.layout
281
282        # add the top slider
283        if proppath:
284            layout.row().prop(proppath, "strength", slider=True)
285            layout.row().separator()
286
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'
293                        )
294        else:
295            layout.row().label(text="No brushes available", icon="INFO")
296
297
298class BrushModeMenu(Menu):
299    bl_label = "Brush Mode"
300    bl_idname = "VIEW3D_MT_sv3_brush_mode_menu"
301
302    def init(self):
303        mode = utils_core.get_mode()
304        has_brush = utils_core.get_brush_link(bpy.context, types="brush")
305
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"
310
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"
315
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"
320
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"
325
326        else:
327            enum = None
328            path = ""
329
330        return enum, path
331
332    def draw(self, context):
333        enum, path = self.init()
334        layout = self.layout
335        colum_n = utils_core.addon_settings()
336
337        layout.row().label(text="Brush Mode")
338        layout.row().separator()
339
340        if enum:
341            if utils_core.get_mode() != 'SCULPT':
342                column_flow = layout.column_flow(columns=colum_n)
343
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'
350                            )
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'
358                            )
359        else:
360            layout.row().label(text="No brushes available", icon="INFO")
361
362
363class BrushAutosmoothMenu(Menu):
364    bl_label = "Autosmooth"
365    bl_idname = "VIEW3D_MT_sv3_brush_autosmooth_menu"
366
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))
374
375        return settings
376
377    def draw(self, context):
378        settings = self.init()
379        layout = self.layout
380        has_brush = utils_core.get_brush_link(context, types="brush")
381
382        if has_brush:
383            # add the top slider
384            layout.row().prop(has_brush, "auto_smooth_factor", slider=True)
385            layout.row().separator()
386
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'
394                        )
395        else:
396            layout.row().label(text="No Smooth options available", icon="INFO")
397
398
399class BrushWeightMenu(Menu):
400    bl_label = "Weight"
401    bl_idname = "VIEW3D_MT_sv3_brush_weight_menu"
402
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))
410
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"
415
416        else:
417            brush = bpy.context.tool_settings.image_paint.brush
418            brushstr = "tool_settings.image_paint.brush.weight"
419            name = "Mask Value"
420
421        return settings, brush, brushstr, name
422
423    def draw(self, context):
424        settings, brush, brushstr, name = self.init()
425        layout = self.layout
426
427        if brush:
428            # add the top slider
429            layout.row().prop(brush, "weight", text=name, slider=True)
430            layout.row().separator()
431
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'
439                        )
440        else:
441            layout.row().label(text="No brush available", icon="INFO")
442
443
444class ParticleCountMenu(Menu):
445    bl_label = "Count"
446    bl_idname = "VIEW3D_MT_sv3_particle_count_menu"
447
448    def init(self):
449        settings = (("50", 50),
450                    ("25", 25),
451                    ("10", 10),
452                    ("5", 5),
453                    ("3", 3),
454                    ("1", 1))
455
456        return settings
457
458    def draw(self, context):
459        settings = self.init()
460        layout = self.layout
461
462        # add the top slider
463        layout.row().prop(context.tool_settings.particle_edit.brush,
464                             "count", slider=True)
465        layout.row().separator()
466
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'
474                    )
475
476
477class ParticleLengthMenu(Menu):
478    bl_label = "Length Mode"
479    bl_idname = "VIEW3D_MT_sv3_particle_length_menu"
480
481    def draw(self, context):
482        layout = self.layout
483        path = "tool_settings.particle_edit.brush.length_mode"
484
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'
493                    )
494
495
496class ParticlePuffMenu(Menu):
497    bl_label = "Puff Mode"
498    bl_idname = "VIEW3D_MT_sv3_particle_puff_menu"
499
500    def draw(self, context):
501        layout = self.layout
502        path = "tool_settings.particle_edit.brush.puff_mode"
503
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'
512                    )
513
514
515class 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"
520
521    is_tex: BoolProperty(
522                default=False,
523                options={'HIDDEN'}
524                )
525
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
531
532                orig_prim = color.hsv
533                orig_sec = secondary_color.hsv
534
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
540
541                orig_prim = color.hsv
542                orig_sec = secondary_color.hsv
543
544                color.hsv = orig_sec
545                secondary_color.hsv = orig_prim
546
547            return {'FINISHED'}
548
549        except Exception as e:
550            utils_core.error_handlers(self, "view3d.sv3_flip_colors_all", e,
551                                     "Flip Colors could not be completed")
552
553            return {'CANCELLED'}
554
555
556class 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'}
562
563    @classmethod
564    def poll(self, context):
565        return utils_core.get_mode() in (
566                        'VERTEX_PAINT',
567                        'TEXTURE_PAINT'
568                        )
569
570    def check(self, context):
571        return True
572
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)
581
582        return settings, brush
583
584    def draw(self, context):
585        layout = self.layout
586        settings, brush = self.init()
587
588
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="")
594
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
605
606            if settings.palette:
607                layout.column().template_palette(settings, "palette", color=True)
608
609            layout.row().template_ID(settings, "palette", new="palette.new")
610        else:
611            layout.row().label(text="No brushes currently available", icon="INFO")
612
613            return
614
615    def execute(self, context):
616        return context.window_manager.invoke_popup(self, width=180)
617
618
619classes = (
620    BrushOptionsMenu,
621    BrushRadiusMenu,
622    BrushStrengthMenu,
623    BrushModeMenu,
624    BrushAutosmoothMenu,
625    BrushWeightMenu,
626    ParticleCountMenu,
627    ParticleLengthMenu,
628    ParticlePuffMenu,
629    FlipColorsAll,
630    ColorPickerPopup
631    )
632
633def register():
634    for cls in classes:
635        bpy.utils.register_class(cls)
636
637def unregister():
638    for cls in classes:
639        bpy.utils.unregister_class(cls)
640