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