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