Cleanup: surround multiply by parenthesis, remove unused import
[blender-addons.git] / object_skinify.py
blob8a2aaceb10d21bad6d49b6cdb338d2a211448bba
1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Skinify Rig",
7 "author": "Albert Makac (karab44)",
8 "version": (0, 11, 2),
9 "blender": (2, 80, 0),
10 "location": "Pose Mode > Sidebar > Create Tab",
11 "description": "Creates a mesh object from selected bones",
12 "warning": "Work in progress",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/skinify.html",
14 "category": "Object",
17 import bpy
18 from bpy.props import (
19 FloatProperty,
20 IntProperty,
21 BoolProperty,
22 PointerProperty,
24 from bpy.types import (
25 Operator,
26 Panel,
27 PropertyGroup,
29 from mathutils import (
30 Vector,
31 Euler,
33 from bpy.app.handlers import persistent
34 from enum import Enum
36 # can the armature data properties group_prop and row be fetched directly from the rigify script?
37 horse_data = \
38 (1, 5), (2, 4), (3, 0), (4, 3), (5, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), \
39 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), \
40 (13, 6), (1, 4), (14, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
42 shark_data = \
43 (1, 5), (2, 4), (1, 0), (3, 3), (4, 4), (5, 6), (6, 5), (7, 4), (6, 5), (7, 4), \
44 (8, 3), (9, 4), (1, 0), (1, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), \
45 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
47 bird_data = \
48 (1, 6), (2, 4), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (6, 5), (8, 0), (7, 4), (6, 5), \
49 (8, 0), (7, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
50 (13, 6), (14, 4), (1, 0), (8, 6), (1, 0), (1, 0), (1, 0), (14, 1),
52 cat_data = \
53 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
54 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 3), (14, 4), \
55 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (16, 1),
57 biped_data = \
58 (1, 0), (1, 0), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), (7, 2), \
59 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
60 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
62 human_data = \
63 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
64 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
65 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
67 wolf_data = \
68 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
69 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), (1, 0), \
70 (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
72 quadruped_data = \
73 (1, 0), (2, 0), (2, 0), (3, 3), (4, 4), (5, 0), (6, 0), (7, 2), (8, 5), (9, 4), \
74 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), \
75 (1, 0), (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
77 human_legacy_data = \
78 (1, None), (1, None), (2, None), (1, None), (3, None), (3, None), (4, None), (5, None), \
79 (6, None), (4, None), (5, None), (6, None), (7, None), (8, None), (9, None), (7, None), \
80 (8, None), (9, None), (1, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
81 (1, None), (1, None), (1, None), (1, None),
83 pitchipoy_data = \
84 (1, None), (2, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), \
85 (8, None), (9, None), (7, None), (8, None), (9, None), (10, None), (11, None), (12, None), \
86 (10, None), (11, None), (12, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
87 (1, None), (1, None), (1, None), (1, None),
89 rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \
90 wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
93 class Rig_type(Enum):
94 HORSE = 0
95 SHARK = 1
96 BIRD = 2
97 CAT = 3
98 BIPED = 4
99 HUMAN = 5
100 WOLF = 6
101 QUAD = 7
102 LEGACY = 8
103 PITCHIPOY = 9
104 OTHER = 10
107 rig_type = Rig_type.OTHER
110 class Idx_Store(object):
111 def __init__(self, rig_type):
112 self.rig_type = rig_type
113 self.hand_r_merge = []
114 self.hand_l_merge = []
115 self.hands_pretty = []
116 self.root = []
118 if not self.rig_type == Rig_type.LEGACY and \
119 not self.rig_type == Rig_type.HUMAN and \
120 not self.rig_type == Rig_type.PITCHIPOY:
121 return
123 if self.rig_type == Rig_type.LEGACY:
124 self.hand_l_merge = [7, 12, 16, 21, 26, 27]
125 self.hand_r_merge = [30, 31, 36, 40, 45, 50]
126 self.hands_pretty = [6, 29]
127 self.root = [59]
129 if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
130 self.hand_l_merge = [9, 10, 15, 19, 24, 29]
131 self.hand_r_merge = [32, 33, 37, 42, 47, 52]
132 self.hands_pretty = [8, 31]
133 self.root = [56]
135 def get_all_idx(self):
136 return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root
138 def get_hand_l_merge_idx(self):
139 return self.hand_l_merge
141 def get_hand_r_merge_idx(self):
142 return self.hand_r_merge
144 def get_hands_pretty_idx(self):
145 return self.hands_pretty
147 def get_root_idx(self):
148 return self.root
151 # initialize properties
152 def init_props():
153 # additional check - this should be a rare case if the handler
154 # wasn't removed for some reason and the add-on is not toggled on/off
155 if hasattr(bpy.types.Scene, "skinify"):
156 scn = bpy.context.scene.skinify
158 scn.connect_mesh = False
159 scn.connect_parents = False
160 scn.generate_all = False
161 scn.thickness = 0.8
162 scn.finger_thickness = 0.25
163 scn.apply_mod = True
164 scn.parent_armature = True
165 scn.sub_level = 1
168 # selects vertices
169 def select_vertices(mesh_obj, idx):
170 bpy.context.view_layer.objects.active = mesh_obj
171 mode = mesh_obj.mode
172 bpy.ops.object.mode_set(mode='EDIT')
173 bpy.ops.mesh.select_all(action='DESELECT')
174 bpy.ops.object.mode_set(mode='OBJECT')
176 for i in idx:
177 mesh_obj.data.vertices[i].select = True
179 selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
181 bpy.ops.object.mode_set(mode=mode)
182 return selectedVerts
185 def identify_rig():
186 if 'rigify_layers' not in bpy.context.object.data:
187 return Rig_type.OTHER # non recognized
189 LEGACY_LAYERS_SIZE = 28
190 layers = bpy.context.object.data['rigify_layers']
192 for type, rig in enumerate(rigify_data):
193 index = 0
195 for props in layers:
196 if len(layers) == LEGACY_LAYERS_SIZE and 'group_prop' not in props:
198 if props['row'] != rig[index][0] or rig[index][1] is not None:
199 break
201 elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
202 break
204 # SUCCESS if reach the end
205 if index == len(layers) - 1:
206 return Rig_type(type)
208 index = index + 1
210 return Rig_type.OTHER
213 def prepare_ignore_list(rig_type, bones):
214 # detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization
215 common_ignore_list = ['eye', 'heel', 'breast', 'root']
217 # edit these lists to suits your taste
219 horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.']
221 shark_ignore_list = ['jaw']
223 bird_ignore_list = [
224 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
225 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
227 cat_ignore_list = [
228 'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
229 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
231 biped_ignore_list = ['pelvis']
233 human_ignore_list = [
234 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
235 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
237 wolf_ignore_list = [
238 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
239 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
241 quad_ignore_list = [
242 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
243 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
245 rigify_legacy_ignore_list = []
247 pitchipoy_ignore_list = [
248 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
249 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
252 other_ignore_list = []
254 ignore_list = common_ignore_list
256 if rig_type == Rig_type.HORSE:
257 ignore_list = ignore_list + horse_ignore_list
258 print("RIDER OF THE APOCALYPSE")
259 elif rig_type == Rig_type.SHARK:
260 ignore_list = ignore_list + shark_ignore_list
261 print("DEADLY JAWS")
262 elif rig_type == Rig_type.BIRD:
263 ignore_list = ignore_list + bird_ignore_list
264 print("WINGS OF LIBERTY")
265 elif rig_type == Rig_type.CAT:
266 ignore_list = ignore_list + cat_ignore_list
267 print("MEOW")
268 elif rig_type == Rig_type.BIPED:
269 ignore_list = ignore_list + biped_ignore_list
270 print("HUMANOID")
271 elif rig_type == Rig_type.HUMAN:
272 ignore_list = ignore_list + human_ignore_list
273 print("JUST A HUMAN AFTER ALL")
274 elif rig_type == Rig_type.WOLF:
275 ignore_list = ignore_list + wolf_ignore_list
276 print("WHITE FANG")
277 elif rig_type == Rig_type.QUAD:
278 ignore_list = ignore_list + quad_ignore_list
279 print("MYSTERIOUS CREATURE")
280 elif rig_type == Rig_type.LEGACY:
281 ignore_list = ignore_list + rigify_legacy_ignore_list
282 print("LEGACY RIGIFY")
283 elif rig_type == Rig_type.PITCHIPOY:
284 ignore_list = ignore_list + pitchipoy_ignore_list
285 print("PITCHIPOY")
286 elif rig_type == Rig_type.OTHER:
287 ignore_list = ignore_list + other_ignore_list
288 print("rig non recognized...")
290 return ignore_list
293 # generates edges from vertices used by skin modifier
294 def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False,
295 head_ornaments=False, generate_all=False):
297 This function adds vertices for all heads and tails
299 # scene preferences
301 alternate_scale_list = []
303 me = mesh
304 verts = []
305 edges = []
306 idx = 0
307 alternate_scale_idx_list = list()
309 rig_type = identify_rig()
310 ignore_list = prepare_ignore_list(rig_type, bones)
312 # edge generator loop
313 for b in bones:
314 # look for rig's hands and their childs
315 if 'hand' in b.name.lower():
316 # prepare the list
317 for c in b.children_recursive:
318 alternate_scale_list.append(c.name)
320 found = False
322 for i in ignore_list:
323 if i in b.name.lower():
324 found = True
325 break
327 if found and generate_all is False:
328 continue
330 # fix for drawing rootbone and relationship lines
331 if 'root' in b.name.lower() and generate_all is False:
332 continue
334 # ignore any head ornaments
335 if head_ornaments is False:
336 if b.parent is not None:
338 if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN:
339 continue
341 if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN:
342 continue
344 if connect_parents:
345 if b.parent is not None and b.parent.bone.select is True and b.bone.use_connect is False:
346 if 'root' in b.parent.name.lower() and generate_all is False:
347 continue
348 # ignore shoulder
349 if 'shoulder' in b.name.lower() and connect_mesh is True:
350 continue
351 # connect the upper arm directly with chest omitting shoulders
352 if 'shoulder' in b.parent.name.lower() and connect_mesh is True:
353 vert1 = b.head
354 vert2 = b.parent.parent.tail
356 else:
357 vert1 = b.head
358 vert2 = b.parent.tail
360 verts.append(vert1)
361 verts.append(vert2)
362 edges.append([idx, idx + 1])
364 # also make list of edges made of gaps between the bones
365 for a in alternate_scale_list:
366 if b.name == a:
367 alternate_scale_idx_list.append(idx)
368 alternate_scale_idx_list.append(idx + 1)
370 idx = idx + 2
371 # for bvh free floating hips and hips correction for rigify and pitchipoy
372 if ((generate_all is False and 'hip' in b.name.lower()) or
373 (generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or
374 (b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and
375 rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))):
376 continue
378 vert1 = b.head
379 vert2 = b.tail
380 verts.append(vert1)
381 verts.append(vert2)
383 edges.append([idx, idx + 1])
385 for a in alternate_scale_list:
386 if b.name == a:
387 alternate_scale_idx_list.append(idx)
388 alternate_scale_idx_list.append(idx + 1)
390 idx = idx + 2
392 # Create mesh from given verts, faces
393 me.from_pydata(verts, edges, [])
394 # Update mesh with new data
395 me.update()
397 # set object scale exact as armature's scale
398 shape_object.scale = scale
400 return alternate_scale_idx_list, rig_type
403 def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1,
404 connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True,
405 alternate_scale_idx_list=[], rig_type=0, bones=[]):
407 This function adds modifiers for generated edges
409 total_bones_num = bpy.context.selected_pose_bones_from_active_object
410 selected_bones_num = len(bones)
412 bpy.ops.object.mode_set(mode='EDIT')
413 bpy.ops.mesh.select_all(action='DESELECT')
415 # add skin modifier
416 skin_modifier = shape_object.modifiers.new("Skin", 'SKIN')
417 bpy.ops.mesh.select_all(action='SELECT')
419 # calculate optimal thickness for defaults
420 bpy.ops.object.skin_root_mark()
421 bpy.ops.transform.skin_resize(
422 value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
423 constraint_axis=(False, False, False),
424 orient_type='GLOBAL',
425 mirror=False,
426 use_proportional_edit=False,
428 skin_modifier.use_smooth_shade = True
429 skin_modifier.use_x_symmetry = True
431 # select finger vertices and calculate optimal thickness for fingers to fix proportions
432 if len(alternate_scale_idx_list) > 0:
433 select_vertices(shape_object, alternate_scale_idx_list)
435 bpy.ops.object.skin_loose_mark_clear(action='MARK')
436 # by default set fingers thickness to 25 percent of body thickness
437 bpy.ops.transform.skin_resize(
438 value=(finger_thickness, finger_thickness, finger_thickness),
439 constraint_axis=(False, False, False), orient_type='GLOBAL',
440 mirror=False,
441 use_proportional_edit=False,
443 # make loose hands only for better topology
445 # bpy.ops.mesh.select_all(action='DESELECT')
447 if connect_mesh:
448 bpy.ops.object.mode_set(mode='EDIT')
449 bpy.ops.mesh.select_all(action='DESELECT')
450 bpy.ops.mesh.select_all(action='SELECT')
451 bpy.ops.mesh.remove_doubles()
453 idx_store = Idx_Store(rig_type)
455 # fix rigify and pitchipoy hands topology
456 if connect_mesh and connect_parents and generate_all is False and \
457 (rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \
458 selected_bones_num == total_bones_num:
459 # thickness will set palm vertex for both hands look pretty
460 corrective_thickness = 2.5
461 # left hand verts
462 merge_idx = idx_store.get_hand_l_merge_idx()
464 select_vertices(shape_object, merge_idx)
465 bpy.ops.mesh.merge(type='CENTER')
466 bpy.ops.transform.skin_resize(
467 value=(corrective_thickness, corrective_thickness, corrective_thickness),
468 constraint_axis=(False, False, False), orient_type='GLOBAL',
469 mirror=False,
470 use_proportional_edit=False,
472 bpy.ops.mesh.select_all(action='DESELECT')
474 # right hand verts
475 merge_idx = idx_store.get_hand_r_merge_idx()
477 select_vertices(shape_object, merge_idx)
478 bpy.ops.mesh.merge(type='CENTER')
479 bpy.ops.transform.skin_resize(
480 value=(corrective_thickness, corrective_thickness, corrective_thickness),
481 constraint_axis=(False, False, False), orient_type='GLOBAL',
482 mirror=False,
483 use_proportional_edit=False,
486 # making hands even more pretty
487 bpy.ops.mesh.select_all(action='DESELECT')
488 hands_idx = idx_store.get_hands_pretty_idx()
490 select_vertices(shape_object, hands_idx)
491 # change the thickness to make hands look less blocky and more sexy
492 corrective_thickness = 0.7
493 bpy.ops.transform.skin_resize(
494 value=(corrective_thickness, corrective_thickness, corrective_thickness),
495 constraint_axis=(False, False, False), orient_type='GLOBAL',
496 mirror=False,
497 use_proportional_edit=False,
499 bpy.ops.mesh.select_all(action='DESELECT')
501 # todo optionally take root from rig's hip tail or head depending on scenario
503 root_idx = idx_store.get_root_idx()
505 if selected_bones_num == total_bones_num:
506 root_idx = [0]
508 if len(root_idx) > 0:
509 select_vertices(shape_object, root_idx)
510 bpy.ops.object.skin_root_mark()
511 # skin in edit mode
512 # add Subsurf modifier
513 subsurf_modifier = shape_object.modifiers.new("Subsurf", 'SUBSURF')
514 subsurf_modifier.levels = sub_level
515 subsurf_modifier.render_levels = sub_level
517 bpy.ops.object.mode_set(mode='OBJECT')
518 bpy.context.view_layer.update()
520 # object mode apply all modifiers
521 if apply_mod:
522 bpy.ops.object.modifier_apply(modifier=skin_modifier.name)
523 bpy.ops.object.modifier_apply(modifier=subsurf_modifier.name)
525 return {'FINISHED'}
528 def main(context):
530 This script will create a custom shape
533 # ### Check if selection is OK ###
534 if len(context.selected_pose_bones) == 0 or \
535 len(context.selected_objects) == 0 or \
536 context.selected_objects[0].type != 'ARMATURE':
537 return {'CANCELLED'}, "No bone selected or the Armature is hidden"
539 scn = bpy.context.scene
540 sknfy = scn.skinify
542 # initialize the mesh object
543 mesh_name = context.selected_objects[0].name + "_mesh"
544 obj_name = context.selected_objects[0].name + "_object"
545 armature_object = context.object
547 origin = context.object.location
548 bone_selection = context.selected_pose_bones
549 oldLocation = None
550 oldRotation = None
551 oldScale = None
552 armature_object = context.view_layer.objects.active
553 armature_object.select_set(True)
555 old_pose_pos = armature_object.data.pose_position
556 bpy.ops.object.mode_set(mode='OBJECT')
557 oldLocation = Vector(armature_object.location)
558 oldRotation = Euler(armature_object.rotation_euler)
559 oldScale = Vector(armature_object.scale)
561 bpy.ops.object.rotation_clear(clear_delta=False)
562 bpy.ops.object.location_clear(clear_delta=False)
563 bpy.ops.object.scale_clear(clear_delta=False)
564 if sknfy.apply_mod and sknfy.parent_armature:
565 armature_object.data.pose_position = 'REST'
567 scale = bpy.context.object.scale
568 size = bpy.context.object.dimensions[2]
570 bpy.ops.object.mode_set(mode='OBJECT')
571 bpy.ops.object.select_all(action='DESELECT')
573 bpy.ops.object.add(type='MESH', enter_editmode=False, location=origin)
575 # get the mesh object
576 ob = context.view_layer.objects.active
577 ob.name = obj_name
578 me = ob.data
579 me.name = mesh_name
581 # this way we fit mesh and bvh with armature modifier correctly
583 alternate_scale_idx_list, rig_type = generate_edges(
584 me, ob, bone_selection, scale, sknfy.connect_mesh,
585 sknfy.connect_parents, sknfy.head_ornaments,
586 sknfy.generate_all
589 generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level,
590 sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
591 sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection)
593 # parent mesh with armature only if modifiers are applied
594 if sknfy.apply_mod and sknfy.parent_armature:
595 bpy.ops.object.mode_set(mode='OBJECT')
596 bpy.ops.object.select_all(action='DESELECT')
597 ob.select_set(True)
598 armature_object.select_set(True)
599 bpy.context.view_layer.objects.active = armature_object
601 bpy.ops.object.parent_set(type='ARMATURE_AUTO')
602 armature_object.data.pose_position = old_pose_pos
603 armature_object.select_set(False)
604 else:
605 bpy.ops.object.mode_set(mode='OBJECT')
606 ob.location = oldLocation
607 ob.rotation_euler = oldRotation
608 ob.scale = oldScale
609 ob.select_set(False)
610 armature_object.select_set(True)
611 context.view_layer.objects.active = armature_object
613 armature_object.location = oldLocation
614 armature_object.rotation_euler = oldRotation
615 armature_object.scale = oldScale
616 bpy.ops.object.mode_set(mode='POSE')
618 return {'FINISHED'}, me
621 class BONE_OT_custom_shape(Operator):
622 bl_idname = "object.skinify_rig"
623 bl_label = "Skinify Rig"
624 bl_description = "Creates a mesh object at the selected bones positions"
625 bl_options = {'UNDO', 'INTERNAL'}
627 @classmethod
628 def poll(cls, context):
629 return context.active_object is not None
631 def execute(self, context):
632 Mesh = main(context)
633 if Mesh[0] == {'CANCELLED'}:
634 self.report({'WARNING'}, Mesh[1])
635 return {'CANCELLED'}
636 else:
637 self.report({'INFO'}, Mesh[1].name + " has been created")
639 return {'FINISHED'}
642 class BONE_PT_custom_shape(Panel):
643 bl_space_type = "VIEW_3D"
644 bl_region_type = "UI"
645 bl_category = "Create"
646 # bl_context = "bone"
647 bl_label = "Skinify Rig"
648 bl_options = {'DEFAULT_CLOSED'}
650 @classmethod
651 def poll(cls, context):
652 ob = context.object
653 return ob and ob.mode == 'POSE' #and context.bone
655 def draw(self, context):
656 layout = self.layout
657 scn = context.scene.skinify
659 row = layout.row()
660 row.operator("object.skinify_rig", text="Add Shape", icon='BONE_DATA')
662 split = layout.split(factor=0.3)
663 split.label(text="Thickness:")
664 split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
665 split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')
667 split = layout.split(factor=0.3)
668 split.label(text="Mesh Density:")
669 split.prop(scn, "sub_level", icon='MESH_ICOSPHERE')
671 row = layout.row()
672 row.prop(scn, "connect_mesh", icon='EDITMODE_HLT')
673 row.prop(scn, "connect_parents", icon='CONSTRAINT_BONE')
674 row = layout.row()
675 row.prop(scn, "head_ornaments", icon='GROUP_BONE')
676 row.prop(scn, "generate_all", icon='GROUP_BONE')
677 row = layout.row()
678 row.prop(scn, "apply_mod", icon='FILE_TICK')
679 if scn.apply_mod:
680 row = layout.row()
681 row.prop(scn, "parent_armature", icon='POSE_HLT')
684 # define the scene properties in a group - call them with context.scene.skinify
685 class Skinify_Properties(PropertyGroup):
686 sub_level: IntProperty(
687 name="Sub level",
688 min=0, max=4,
689 default=1,
690 description="Mesh density"
692 thickness: FloatProperty(
693 name="Thickness",
694 min=0.01,
695 default=0.8,
696 description="Adjust shape thickness"
698 finger_thickness: FloatProperty(
699 name="Finger Thickness",
700 min=0.01, max=1.0,
701 default=0.25,
702 description="Adjust finger thickness relative to body"
704 connect_mesh: BoolProperty(
705 name="Solid Shape",
706 default=False,
707 description="Makes solid shape from bone chains"
709 connect_parents: BoolProperty(
710 name="Fill Gaps",
711 default=False,
712 description="Fills the gaps between parented bones"
714 generate_all: BoolProperty(
715 name="All Shapes",
716 default=False,
717 description="Generates shapes from all bones"
719 head_ornaments: BoolProperty(
720 name="Head Ornaments",
721 default=False,
722 description="Includes head ornaments"
724 apply_mod: BoolProperty(
725 name="Apply Modifiers",
726 default=True,
727 description="Applies Modifiers to mesh"
729 parent_armature: BoolProperty(
730 name="Parent Armature",
731 default=True,
732 description="Applies mesh to Armature"
736 # startup defaults
738 @persistent
739 def startup_init(dummy):
740 init_props()
743 def register():
744 bpy.utils.register_class(BONE_OT_custom_shape)
745 bpy.utils.register_class(BONE_PT_custom_shape)
746 bpy.utils.register_class(Skinify_Properties)
748 bpy.types.Scene.skinify = PointerProperty(
749 type=Skinify_Properties
751 # startup defaults
752 bpy.app.handlers.load_post.append(startup_init)
755 def unregister():
756 bpy.utils.unregister_class(BONE_OT_custom_shape)
757 bpy.utils.unregister_class(BONE_PT_custom_shape)
758 bpy.utils.unregister_class(Skinify_Properties)
760 # cleanup the handler
761 bpy.app.handlers.load_post.remove(startup_init)
763 del bpy.types.Scene.skinify
766 if __name__ == "__main__":
767 register()