1# ##### BEGIN GPL LICENSE BLOCK ##### 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software Foundation, 15# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16# 17# ##### END GPL LICENSE BLOCK ##### 18 19# <pep8 compliant> 20import bpy 21from bpy.types import Menu, Panel, UIList 22from rna_prop_ui import PropertyPanel 23 24 25class MESH_MT_vertex_group_context_menu(Menu): 26 bl_label = "Vertex Group Specials" 27 28 def draw(self, _context): 29 layout = self.layout 30 31 layout.operator( 32 "object.vertex_group_sort", 33 icon='SORTALPHA', 34 text="Sort by Name", 35 ).sort_type = 'NAME' 36 layout.operator( 37 "object.vertex_group_sort", 38 icon='BONE_DATA', 39 text="Sort by Bone Hierarchy", 40 ).sort_type = 'BONE_HIERARCHY' 41 layout.separator() 42 layout.operator("object.vertex_group_copy", icon='DUPLICATE') 43 layout.operator("object.vertex_group_copy_to_linked") 44 layout.operator("object.vertex_group_copy_to_selected") 45 layout.separator() 46 layout.operator("object.vertex_group_mirror", icon='ARROW_LEFTRIGHT').use_topology = False 47 layout.operator("object.vertex_group_mirror", text="Mirror Vertex Group (Topology)").use_topology = True 48 layout.separator() 49 layout.operator( 50 "object.vertex_group_remove_from", 51 icon='X', 52 text="Remove from All Groups", 53 ).use_all_groups = True 54 layout.operator("object.vertex_group_remove_from", text="Clear Active Group").use_all_verts = True 55 layout.operator("object.vertex_group_remove", text="Delete All Unlocked Groups").all_unlocked = True 56 layout.operator("object.vertex_group_remove", text="Delete All Groups").all = True 57 layout.separator() 58 props = layout.operator("object.vertex_group_lock", icon='LOCKED', text="Lock All") 59 props.action, props.mask = 'LOCK', 'ALL' 60 props = layout.operator("object.vertex_group_lock", icon='UNLOCKED', text="UnLock All") 61 props.action, props.mask = 'UNLOCK', 'ALL' 62 props = layout.operator("object.vertex_group_lock", text="Lock Invert All") 63 props.action, props.mask = 'INVERT', 'ALL' 64 65 66class MESH_MT_shape_key_context_menu(Menu): 67 bl_label = "Shape Key Specials" 68 69 def draw(self, _context): 70 layout = self.layout 71 72 layout.operator("object.shape_key_add", icon='ADD', text="New Shape from Mix").from_mix = True 73 layout.separator() 74 layout.operator("object.shape_key_mirror", icon='ARROW_LEFTRIGHT').use_topology = False 75 layout.operator("object.shape_key_mirror", text="Mirror Shape Key (Topology)").use_topology = True 76 layout.separator() 77 layout.operator("object.join_shapes") 78 layout.operator("object.shape_key_transfer") 79 layout.separator() 80 layout.operator("object.shape_key_remove", icon='X', text="Delete All Shape Keys").all = True 81 layout.separator() 82 layout.operator("object.shape_key_move", icon='TRIA_UP_BAR', text="Move to Top").type = 'TOP' 83 layout.operator("object.shape_key_move", icon='TRIA_DOWN_BAR', text="Move to Bottom").type = 'BOTTOM' 84 85 86class MESH_UL_vgroups(UIList): 87 def draw_item(self, _context, layout, _data, item, icon, _active_data_, _active_propname, _index): 88 # assert(isinstance(item, bpy.types.VertexGroup)) 89 vgroup = item 90 if self.layout_type in {'DEFAULT', 'COMPACT'}: 91 layout.prop(vgroup, "name", text="", emboss=False, icon_value=icon) 92 icon = 'LOCKED' if vgroup.lock_weight else 'UNLOCKED' 93 layout.prop(vgroup, "lock_weight", text="", icon=icon, emboss=False) 94 elif self.layout_type == 'GRID': 95 layout.alignment = 'CENTER' 96 layout.label(text="", icon_value=icon) 97 98 99class MESH_UL_fmaps(UIList): 100 def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): 101 # assert(isinstance(item, bpy.types.FaceMap)) 102 fmap = item 103 if self.layout_type in {'DEFAULT', 'COMPACT'}: 104 layout.prop(fmap, "name", text="", emboss=False, icon='FACE_MAPS') 105 elif self.layout_type == 'GRID': 106 layout.alignment = 'CENTER' 107 layout.label(text="", icon_value=icon) 108 109 110class MESH_UL_shape_keys(UIList): 111 def draw_item(self, _context, layout, _data, item, icon, active_data, _active_propname, index): 112 # assert(isinstance(item, bpy.types.ShapeKey)) 113 obj = active_data 114 # key = data 115 key_block = item 116 if self.layout_type in {'DEFAULT', 'COMPACT'}: 117 split = layout.split(factor=0.66, align=False) 118 split.prop(key_block, "name", text="", emboss=False, icon_value=icon) 119 row = split.row(align=True) 120 if key_block.mute or (obj.mode == 'EDIT' and not (obj.use_shape_key_edit_mode and obj.type == 'MESH')): 121 row.active = False 122 if not item.id_data.use_relative: 123 row.prop(key_block, "frame", text="", emboss=False) 124 elif index > 0: 125 row.prop(key_block, "value", text="", emboss=False) 126 else: 127 row.label(text="") 128 row.prop(key_block, "mute", text="", emboss=False) 129 elif self.layout_type == 'GRID': 130 layout.alignment = 'CENTER' 131 layout.label(text="", icon_value=icon) 132 133 134class MESH_UL_uvmaps(UIList): 135 def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): 136 # assert(isinstance(item, (bpy.types.MeshTexturePolyLayer, bpy.types.MeshLoopColorLayer))) 137 if self.layout_type in {'DEFAULT', 'COMPACT'}: 138 layout.prop(item, "name", text="", emboss=False, icon='GROUP_UVS') 139 icon = 'RESTRICT_RENDER_OFF' if item.active_render else 'RESTRICT_RENDER_ON' 140 layout.prop(item, "active_render", text="", icon=icon, emboss=False) 141 elif self.layout_type == 'GRID': 142 layout.alignment = 'CENTER' 143 layout.label(text="", icon_value=icon) 144 145 146class MESH_UL_vcols(UIList): 147 def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): 148 # assert(isinstance(item, (bpy.types.MeshTexturePolyLayer, bpy.types.MeshLoopColorLayer))) 149 if self.layout_type in {'DEFAULT', 'COMPACT'}: 150 layout.prop(item, "name", text="", emboss=False, icon='GROUP_VCOL') 151 icon = 'RESTRICT_RENDER_OFF' if item.active_render else 'RESTRICT_RENDER_ON' 152 layout.prop(item, "active_render", text="", icon=icon, emboss=False) 153 elif self.layout_type == 'GRID': 154 layout.alignment = 'CENTER' 155 layout.label(text="", icon_value=icon) 156 157 158class MeshButtonsPanel: 159 bl_space_type = 'PROPERTIES' 160 bl_region_type = 'WINDOW' 161 bl_context = "data" 162 163 @classmethod 164 def poll(cls, context): 165 engine = context.engine 166 return context.mesh and (engine in cls.COMPAT_ENGINES) 167 168 169class DATA_PT_context_mesh(MeshButtonsPanel, Panel): 170 bl_label = "" 171 bl_options = {'HIDE_HEADER'} 172 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 173 174 def draw(self, context): 175 layout = self.layout 176 177 ob = context.object 178 mesh = context.mesh 179 space = context.space_data 180 181 if ob: 182 layout.template_ID(ob, "data") 183 elif mesh: 184 layout.template_ID(space, "pin_id") 185 186 187class DATA_PT_normals(MeshButtonsPanel, Panel): 188 bl_label = "Normals" 189 bl_options = {'DEFAULT_CLOSED'} 190 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 191 192 def draw(self, context): 193 layout = self.layout 194 layout.use_property_split = True 195 196 mesh = context.mesh 197 198 col = layout.column(align=False, heading="Auto Smooth") 199 col.use_property_decorate = False 200 row = col.row(align=True) 201 sub = row.row(align=True) 202 sub.prop(mesh, "use_auto_smooth", text="") 203 sub = sub.row(align=True) 204 sub.active = mesh.use_auto_smooth and not mesh.has_custom_normals 205 sub.prop(mesh, "auto_smooth_angle", text="") 206 row.prop_decorator(mesh, "auto_smooth_angle") 207 208 209class DATA_PT_texture_space(MeshButtonsPanel, Panel): 210 bl_label = "Texture Space" 211 bl_options = {'DEFAULT_CLOSED'} 212 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 213 214 def draw(self, context): 215 layout = self.layout 216 layout.use_property_split = True 217 218 mesh = context.mesh 219 220 layout.prop(mesh, "texture_mesh") 221 222 layout.separator() 223 224 layout.prop(mesh, "use_auto_texspace") 225 226 layout.prop(mesh, "texspace_location", text="Location") 227 layout.prop(mesh, "texspace_size", text="Size") 228 229 230class DATA_PT_vertex_groups(MeshButtonsPanel, Panel): 231 bl_label = "Vertex Groups" 232 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 233 234 @classmethod 235 def poll(cls, context): 236 engine = context.engine 237 obj = context.object 238 return (obj and obj.type in {'MESH', 'LATTICE'} and (engine in cls.COMPAT_ENGINES)) 239 240 def draw(self, context): 241 layout = self.layout 242 243 ob = context.object 244 group = ob.vertex_groups.active 245 246 rows = 3 247 if group: 248 rows = 5 249 250 row = layout.row() 251 row.template_list("MESH_UL_vgroups", "", ob, "vertex_groups", ob.vertex_groups, "active_index", rows=rows) 252 253 col = row.column(align=True) 254 255 col.operator("object.vertex_group_add", icon='ADD', text="") 256 props = col.operator("object.vertex_group_remove", icon='REMOVE', text="") 257 props.all_unlocked = props.all = False 258 259 col.separator() 260 261 col.menu("MESH_MT_vertex_group_context_menu", icon='DOWNARROW_HLT', text="") 262 263 if group: 264 col.separator() 265 col.operator("object.vertex_group_move", icon='TRIA_UP', text="").direction = 'UP' 266 col.operator("object.vertex_group_move", icon='TRIA_DOWN', text="").direction = 'DOWN' 267 268 if ( 269 ob.vertex_groups and 270 (ob.mode == 'EDIT' or 271 (ob.mode == 'WEIGHT_PAINT' and ob.type == 'MESH' and ob.data.use_paint_mask_vertex)) 272 ): 273 row = layout.row() 274 275 sub = row.row(align=True) 276 sub.operator("object.vertex_group_assign", text="Assign") 277 sub.operator("object.vertex_group_remove_from", text="Remove") 278 279 sub = row.row(align=True) 280 sub.operator("object.vertex_group_select", text="Select") 281 sub.operator("object.vertex_group_deselect", text="Deselect") 282 283 layout.prop(context.tool_settings, "vertex_group_weight", text="Weight") 284 285 286class DATA_PT_face_maps(MeshButtonsPanel, Panel): 287 bl_label = "Face Maps" 288 bl_options = {'DEFAULT_CLOSED'} 289 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 290 291 @classmethod 292 def poll(cls, context): 293 obj = context.object 294 return (obj and obj.type == 'MESH') 295 296 def draw(self, context): 297 layout = self.layout 298 299 ob = context.object 300 facemap = ob.face_maps.active 301 302 rows = 2 303 if facemap: 304 rows = 4 305 306 row = layout.row() 307 row.template_list("MESH_UL_fmaps", "", ob, "face_maps", ob.face_maps, "active_index", rows=rows) 308 309 col = row.column(align=True) 310 col.operator("object.face_map_add", icon='ADD', text="") 311 col.operator("object.face_map_remove", icon='REMOVE', text="") 312 313 if facemap: 314 col.separator() 315 col.operator("object.face_map_move", icon='TRIA_UP', text="").direction = 'UP' 316 col.operator("object.face_map_move", icon='TRIA_DOWN', text="").direction = 'DOWN' 317 318 if ob.face_maps and (ob.mode == 'EDIT' and ob.type == 'MESH'): 319 row = layout.row() 320 321 sub = row.row(align=True) 322 sub.operator("object.face_map_assign", text="Assign") 323 sub.operator("object.face_map_remove_from", text="Remove") 324 325 sub = row.row(align=True) 326 sub.operator("object.face_map_select", text="Select") 327 sub.operator("object.face_map_deselect", text="Deselect") 328 329 330class DATA_PT_shape_keys(MeshButtonsPanel, Panel): 331 bl_label = "Shape Keys" 332 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 333 334 @classmethod 335 def poll(cls, context): 336 engine = context.engine 337 obj = context.object 338 return (obj and obj.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'} and (engine in cls.COMPAT_ENGINES)) 339 340 def draw(self, context): 341 layout = self.layout 342 343 ob = context.object 344 key = ob.data.shape_keys 345 kb = ob.active_shape_key 346 347 enable_edit = ob.mode != 'EDIT' 348 enable_edit_value = False 349 enable_pin = False 350 351 if enable_edit or (ob.use_shape_key_edit_mode and ob.type == 'MESH'): 352 enable_pin = True 353 if ob.show_only_shape_key is False: 354 enable_edit_value = True 355 356 row = layout.row() 357 358 rows = 3 359 if kb: 360 rows = 5 361 362 row.template_list("MESH_UL_shape_keys", "", key, "key_blocks", ob, "active_shape_key_index", rows=rows) 363 364 col = row.column(align=True) 365 366 col.operator("object.shape_key_add", icon='ADD', text="").from_mix = False 367 col.operator("object.shape_key_remove", icon='REMOVE', text="").all = False 368 369 col.separator() 370 371 col.menu("MESH_MT_shape_key_context_menu", icon='DOWNARROW_HLT', text="") 372 373 if kb: 374 col.separator() 375 376 sub = col.column(align=True) 377 sub.operator("object.shape_key_move", icon='TRIA_UP', text="").type = 'UP' 378 sub.operator("object.shape_key_move", icon='TRIA_DOWN', text="").type = 'DOWN' 379 380 split = layout.split(factor=0.4) 381 row = split.row() 382 row.enabled = enable_edit 383 row.prop(key, "use_relative") 384 385 row = split.row() 386 row.alignment = 'RIGHT' 387 388 sub = row.row(align=True) 389 sub.label() # XXX, for alignment only 390 subsub = sub.row(align=True) 391 subsub.active = enable_pin 392 subsub.prop(ob, "show_only_shape_key", text="") 393 sub.prop(ob, "use_shape_key_edit_mode", text="") 394 395 sub = row.row() 396 if key.use_relative: 397 sub.operator("object.shape_key_clear", icon='X', text="") 398 else: 399 sub.operator("object.shape_key_retime", icon='RECOVER_LAST', text="") 400 401 layout.use_property_split = True 402 if key.use_relative: 403 if ob.active_shape_key_index != 0: 404 row = layout.row() 405 row.active = enable_edit_value 406 row.prop(kb, "value") 407 408 col = layout.column() 409 sub.active = enable_edit_value 410 sub = col.column(align=True) 411 sub.prop(kb, "slider_min", text="Range Min") 412 sub.prop(kb, "slider_max", text="Max") 413 414 col.prop_search(kb, "vertex_group", ob, "vertex_groups", text="Vertex Group") 415 col.prop_search(kb, "relative_key", key, "key_blocks", text="Relative To") 416 417 else: 418 layout.prop(kb, "interpolation") 419 row = layout.column() 420 row.active = enable_edit_value 421 row.prop(key, "eval_time") 422 423 424class DATA_PT_uv_texture(MeshButtonsPanel, Panel): 425 bl_label = "UV Maps" 426 bl_options = {'DEFAULT_CLOSED'} 427 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 428 429 def draw(self, context): 430 layout = self.layout 431 432 me = context.mesh 433 434 row = layout.row() 435 col = row.column() 436 437 col.template_list("MESH_UL_uvmaps", "uvmaps", me, "uv_layers", me.uv_layers, "active_index", rows=2) 438 439 col = row.column(align=True) 440 col.operator("mesh.uv_texture_add", icon='ADD', text="") 441 col.operator("mesh.uv_texture_remove", icon='REMOVE', text="") 442 443 444class DATA_PT_vertex_colors(MeshButtonsPanel, Panel): 445 bl_label = "Vertex Colors" 446 bl_options = {'DEFAULT_CLOSED'} 447 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 448 449 def draw(self, context): 450 layout = self.layout 451 452 me = context.mesh 453 454 row = layout.row() 455 col = row.column() 456 457 col.template_list("MESH_UL_vcols", "vcols", me, "vertex_colors", me.vertex_colors, "active_index", rows=2) 458 459 col = row.column(align=True) 460 col.operator("mesh.vertex_color_add", icon='ADD', text="") 461 col.operator("mesh.vertex_color_remove", icon='REMOVE', text="") 462 463 464class DATA_PT_sculpt_vertex_colors(MeshButtonsPanel, Panel): 465 bl_label = "Sculpt Vertex Colors" 466 bl_options = {'DEFAULT_CLOSED'} 467 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 468 469 @classmethod 470 def poll(cls, context): 471 return super().poll(context) and context.preferences.experimental.use_sculpt_vertex_colors 472 473 def draw(self, context): 474 layout = self.layout 475 476 me = context.mesh 477 478 row = layout.row() 479 col = row.column() 480 481 col.template_list( 482 "MESH_UL_vcols", 483 "svcols", 484 me, 485 "sculpt_vertex_colors", 486 me.sculpt_vertex_colors, 487 "active_index", 488 rows=2, 489 ) 490 491 col = row.column(align=True) 492 col.operator("mesh.sculpt_vertex_color_add", icon='ADD', text="") 493 col.operator("mesh.sculpt_vertex_color_remove", icon='REMOVE', text="") 494 495 row = layout.row() 496 col = row.column() 497 col.operator("sculpt.vertex_to_loop_colors", text="Store Sculpt Vertex Color") 498 col.operator("sculpt.loop_to_vertex_colors", text="Load Sculpt Vertex Color") 499 500 501class DATA_PT_remesh(MeshButtonsPanel, Panel): 502 bl_label = "Remesh" 503 bl_options = {'DEFAULT_CLOSED'} 504 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 505 506 def draw(self, context): 507 layout = self.layout 508 layout.use_property_split = True 509 layout.use_property_decorate = False 510 row = layout.row() 511 512 mesh = context.mesh 513 row.prop(mesh, "remesh_mode", text="Mode", expand=True) 514 col = layout.column() 515 if mesh.remesh_mode == 'VOXEL': 516 col.prop(mesh, "remesh_voxel_size") 517 col.prop(mesh, "remesh_voxel_adaptivity") 518 col.prop(mesh, "use_remesh_fix_poles") 519 col.prop(mesh, "use_remesh_smooth_normals") 520 521 col = layout.column(heading="Preserve") 522 col.prop(mesh, "use_remesh_preserve_volume", text="Volume") 523 col.prop(mesh, "use_remesh_preserve_paint_mask", text="Paint Mask") 524 col.prop(mesh, "use_remesh_preserve_sculpt_face_sets", text="Face Sets") 525 if context.preferences.experimental.use_sculpt_vertex_colors: 526 col.prop(mesh, "use_remesh_preserve_vertex_colors", text="Vertex Colors") 527 528 col.operator("object.voxel_remesh", text="Voxel Remesh") 529 else: 530 col.operator("object.quadriflow_remesh", text="QuadriFlow Remesh") 531 532 533class DATA_PT_customdata(MeshButtonsPanel, Panel): 534 bl_label = "Geometry Data" 535 bl_options = {'DEFAULT_CLOSED'} 536 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 537 538 def draw(self, context): 539 layout = self.layout 540 layout.use_property_split = True 541 layout.use_property_decorate = False 542 543 obj = context.object 544 me = context.mesh 545 col = layout.column() 546 547 col.operator("mesh.customdata_mask_clear", icon='X') 548 col.operator("mesh.customdata_skin_clear", icon='X') 549 550 if me.has_custom_normals: 551 col.operator("mesh.customdata_custom_splitnormals_clear", icon='X') 552 else: 553 col.operator("mesh.customdata_custom_splitnormals_add", icon='ADD') 554 555 col = layout.column(heading="Store") 556 557 col.enabled = obj is not None and obj.mode != 'EDIT' 558 col.prop(me, "use_customdata_vertex_bevel", text="Vertex Bevel Weight") 559 col.prop(me, "use_customdata_edge_bevel", text="Edge Bevel Weight") 560 col.prop(me, "use_customdata_edge_crease", text="Edge Crease") 561 562 563class DATA_PT_custom_props_mesh(MeshButtonsPanel, PropertyPanel, Panel): 564 COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'} 565 _context_path = "object.data" 566 _property_type = bpy.types.Mesh 567 568 569classes = ( 570 MESH_MT_vertex_group_context_menu, 571 MESH_MT_shape_key_context_menu, 572 MESH_UL_vgroups, 573 MESH_UL_fmaps, 574 MESH_UL_shape_keys, 575 MESH_UL_uvmaps, 576 MESH_UL_vcols, 577 DATA_PT_context_mesh, 578 DATA_PT_vertex_groups, 579 DATA_PT_shape_keys, 580 DATA_PT_uv_texture, 581 DATA_PT_vertex_colors, 582 DATA_PT_sculpt_vertex_colors, 583 DATA_PT_face_maps, 584 DATA_PT_normals, 585 DATA_PT_texture_space, 586 DATA_PT_remesh, 587 DATA_PT_customdata, 588 DATA_PT_custom_props_mesh, 589) 590 591if __name__ == "__main__": # only for live edit. 592 from bpy.utils import register_class 593 for cls in classes: 594 register_class(cls) 595