Update 'bl_info' use 'doc_url' instead of 'wiki_url'
[blender-addons.git] / object_skinify.py
blob58263c9f4da76a8205728904d0b6fe9032576e19
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_info = {
20 "name": "Skinify Rig",
21 "author": "Albert Makac (karab44)",
22 "version": (0, 11, 0),
23 "blender": (2, 80, 0),
24 "location": "Pose Mode > Sidebar > Create Tab",
25 "description": "Creates a mesh object from selected bones",
26 "warning": "Work in progress",
27 "doc_url": "https://docs.blender.org/manual/en/dev/addons/"
28 "object/skinify.html",
29 "category": "Object",
32 import bpy
33 from bpy.props import (
34 FloatProperty,
35 IntProperty,
36 BoolProperty,
37 PointerProperty,
39 from bpy.types import (
40 Operator,
41 Panel,
42 PropertyGroup,
44 from mathutils import (
45 Vector,
46 Euler,
48 from bpy.app.handlers import persistent
49 from enum import Enum
51 # can the armature data properties group_prop and row be fetched directly from the rigify script?
52 horse_data = \
53 (1, 5), (2, 4), (3, 0), (4, 3), (5, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), \
54 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), \
55 (13, 6), (1, 4), (14, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
57 shark_data = \
58 (1, 5), (2, 4), (1, 0), (3, 3), (4, 4), (5, 6), (6, 5), (7, 4), (6, 5), (7, 4), \
59 (8, 3), (9, 4), (1, 0), (1, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), \
60 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
62 bird_data = \
63 (1, 6), (2, 4), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (6, 5), (8, 0), (7, 4), (6, 5), \
64 (8, 0), (7, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
65 (13, 6), (14, 4), (1, 0), (8, 6), (1, 0), (1, 0), (1, 0), (14, 1),
67 cat_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, 3), (14, 4), \
70 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (16, 1),
72 biped_data = \
73 (1, 0), (1, 0), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), (7, 2), \
74 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
75 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
77 human_data = \
78 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
79 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
80 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
82 wolf_data = \
83 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
84 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), (1, 0), \
85 (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
87 quadruped_data = \
88 (1, 0), (2, 0), (2, 0), (3, 3), (4, 4), (5, 0), (6, 0), (7, 2), (8, 5), (9, 4), \
89 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), \
90 (1, 0), (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
92 human_legacy_data = \
93 (1, None), (1, None), (2, None), (1, None), (3, None), (3, None), (4, None), (5, None), \
94 (6, None), (4, None), (5, None), (6, None), (7, None), (8, None), (9, None), (7, None), \
95 (8, None), (9, None), (1, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
96 (1, None), (1, None), (1, None), (1, None),
98 pitchipoy_data = \
99 (1, None), (2, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), \
100 (8, None), (9, None), (7, None), (8, None), (9, None), (10, None), (11, None), (12, None), \
101 (10, None), (11, None), (12, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
102 (1, None), (1, None), (1, None), (1, None),
104 rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \
105 wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
108 class Rig_type(Enum):
109 HORSE = 0
110 SHARK = 1
111 BIRD = 2
112 CAT = 3
113 BIPED = 4
114 HUMAN = 5
115 WOLF = 6
116 QUAD = 7
117 LEGACY = 8
118 PITCHIPOY = 9
119 OTHER = 10
122 rig_type = Rig_type.OTHER
125 class Idx_Store(object):
126 def __init__(self, rig_type):
127 self.rig_type = rig_type
128 self.hand_r_merge = []
129 self.hand_l_merge = []
130 self.hands_pretty = []
131 self.root = []
133 if not self.rig_type == Rig_type.LEGACY and \
134 not self.rig_type == Rig_type.HUMAN and \
135 not self.rig_type == Rig_type.PITCHIPOY:
136 return
138 if self.rig_type == Rig_type.LEGACY:
139 self.hand_l_merge = [7, 12, 16, 21, 26, 27]
140 self.hand_r_merge = [30, 31, 36, 40, 45, 50]
141 self.hands_pretty = [6, 29]
142 self.root = [59]
144 if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
145 self.hand_l_merge = [9, 10, 15, 19, 24, 29]
146 self.hand_r_merge = [32, 33, 37, 42, 47, 52]
147 self.hands_pretty = [8, 31]
148 self.root = [56]
150 def get_all_idx(self):
151 return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root
153 def get_hand_l_merge_idx(self):
154 return self.hand_l_merge
156 def get_hand_r_merge_idx(self):
157 return self.hand_r_merge
159 def get_hands_pretty_idx(self):
160 return self.hands_pretty
162 def get_root_idx(self):
163 return self.root
166 # initialize properties
167 def init_props():
168 # additional check - this should be a rare case if the handler
169 # wasn't removed for some reason and the add-on is not toggled on/off
170 if hasattr(bpy.types.Scene, "skinify"):
171 scn = bpy.context.scene.skinify
173 scn.connect_mesh = False
174 scn.connect_parents = False
175 scn.generate_all = False
176 scn.thickness = 0.8
177 scn.finger_thickness = 0.25
178 scn.apply_mod = True
179 scn.parent_armature = True
180 scn.sub_level = 1
183 # selects vertices
184 def select_vertices(mesh_obj, idx):
185 bpy.context.view_layer.objects.active = mesh_obj
186 mode = mesh_obj.mode
187 bpy.ops.object.mode_set(mode='EDIT')
188 bpy.ops.mesh.select_all(action='DESELECT')
189 bpy.ops.object.mode_set(mode='OBJECT')
191 for i in idx:
192 mesh_obj.data.vertices[i].select = True
194 selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
196 bpy.ops.object.mode_set(mode=mode)
197 return selectedVerts
200 def identify_rig():
201 if 'rigify_layers' not in bpy.context.object.data:
202 return Rig_type.OTHER # non recognized
204 LEGACY_LAYERS_SIZE = 28
205 layers = bpy.context.object.data['rigify_layers']
207 for type, rig in enumerate(rigify_data):
208 index = 0
210 for props in layers:
211 if len(layers) == LEGACY_LAYERS_SIZE and 'group_prop' not in props:
213 if props['row'] != rig[index][0] or rig[index][1] is not None:
214 break
216 elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
217 break
219 # SUCCESS if reach the end
220 if index == len(layers) - 1:
221 return Rig_type(type)
223 index = index + 1
225 return Rig_type.OTHER
228 def prepare_ignore_list(rig_type, bones):
229 # detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization
230 common_ignore_list = ['eye', 'heel', 'breast', 'root']
232 # edit these lists to suits your taste
234 horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.']
236 shark_ignore_list = ['jaw']
238 bird_ignore_list = [
239 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
240 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
242 cat_ignore_list = [
243 'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
244 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
246 biped_ignore_list = ['pelvis']
248 human_ignore_list = [
249 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
250 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
252 wolf_ignore_list = [
253 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
254 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
256 quad_ignore_list = [
257 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
258 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
260 rigify_legacy_ignore_list = []
262 pitchipoy_ignore_list = [
263 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
264 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
267 other_ignore_list = []
269 ignore_list = common_ignore_list
271 if rig_type == Rig_type.HORSE:
272 ignore_list = ignore_list + horse_ignore_list
273 print("RIDER OF THE APOCALYPSE")
274 elif rig_type == Rig_type.SHARK:
275 ignore_list = ignore_list + shark_ignore_list
276 print("DEADLY JAWS")
277 elif rig_type == Rig_type.BIRD:
278 ignore_list = ignore_list + bird_ignore_list
279 print("WINGS OF LIBERTY")
280 elif rig_type == Rig_type.CAT:
281 ignore_list = ignore_list + cat_ignore_list
282 print("MEOW")
283 elif rig_type == Rig_type.BIPED:
284 ignore_list = ignore_list + biped_ignore_list
285 print("HUMANOID")
286 elif rig_type == Rig_type.HUMAN:
287 ignore_list = ignore_list + human_ignore_list
288 print("JUST A HUMAN AFTER ALL")
289 elif rig_type == Rig_type.WOLF:
290 ignore_list = ignore_list + wolf_ignore_list
291 print("WHITE FANG")
292 elif rig_type == Rig_type.QUAD:
293 ignore_list = ignore_list + quad_ignore_list
294 print("MYSTERIOUS CREATURE")
295 elif rig_type == Rig_type.LEGACY:
296 ignore_list = ignore_list + rigify_legacy_ignore_list
297 print("LEGACY RIGIFY")
298 elif rig_type == Rig_type.PITCHIPOY:
299 ignore_list = ignore_list + pitchipoy_ignore_list
300 print("PITCHIPOY")
301 elif rig_type == Rig_type.OTHER:
302 ignore_list = ignore_list + other_ignore_list
303 print("rig non recognized...")
305 return ignore_list
308 # generates edges from vertices used by skin modifier
309 def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False,
310 head_ornaments=False, generate_all=False):
312 This function adds vertices for all heads and tails
314 # scene preferences
316 alternate_scale_list = []
318 me = mesh
319 verts = []
320 edges = []
321 idx = 0
322 alternate_scale_idx_list = list()
324 rig_type = identify_rig()
325 ignore_list = prepare_ignore_list(rig_type, bones)
327 # edge generator loop
328 for b in bones:
329 # look for rig's hands and their childs
330 if 'hand' in b.name.lower():
331 # prepare the list
332 for c in b.children_recursive:
333 alternate_scale_list.append(c.name)
335 found = False
337 for i in ignore_list:
338 if i in b.name.lower():
339 found = True
340 break
342 if found and generate_all is False:
343 continue
345 # fix for drawing rootbone and relationship lines
346 if 'root' in b.name.lower() and generate_all is False:
347 continue
349 # ignore any head ornaments
350 if head_ornaments is False:
351 if b.parent is not None:
353 if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN:
354 continue
356 if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN:
357 continue
359 if connect_parents:
360 if b.parent is not None and b.parent.bone.select is True and b.bone.use_connect is False:
361 if 'root' in b.parent.name.lower() and generate_all is False:
362 continue
363 # ignore shoulder
364 if 'shoulder' in b.name.lower() and connect_mesh is True:
365 continue
366 # connect the upper arm directly with chest omitting shoulders
367 if 'shoulder' in b.parent.name.lower() and connect_mesh is True:
368 vert1 = b.head
369 vert2 = b.parent.parent.tail
371 else:
372 vert1 = b.head
373 vert2 = b.parent.tail
375 verts.append(vert1)
376 verts.append(vert2)
377 edges.append([idx, idx + 1])
379 # also make list of edges made of gaps between the bones
380 for a in alternate_scale_list:
381 if b.name == a:
382 alternate_scale_idx_list.append(idx)
383 alternate_scale_idx_list.append(idx + 1)
385 idx = idx + 2
386 # for bvh free floating hips and hips correction for rigify and pitchipoy
387 if ((generate_all is False and 'hip' in b.name.lower()) or
388 (generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or
389 (b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and
390 rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))):
391 continue
393 vert1 = b.head
394 vert2 = b.tail
395 verts.append(vert1)
396 verts.append(vert2)
398 edges.append([idx, idx + 1])
400 for a in alternate_scale_list:
401 if b.name == a:
402 alternate_scale_idx_list.append(idx)
403 alternate_scale_idx_list.append(idx + 1)
405 idx = idx + 2
407 # Create mesh from given verts, faces
408 me.from_pydata(verts, edges, [])
409 # Update mesh with new data
410 me.update()
412 # set object scale exact as armature's scale
413 shape_object.scale = scale
415 return alternate_scale_idx_list, rig_type
418 def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1,
419 connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True,
420 alternate_scale_idx_list=[], rig_type=0, bones=[]):
422 This function adds modifiers for generated edges
424 total_bones_num = bpy.context.selected_pose_bones_from_active_object
425 selected_bones_num = len(bones)
427 bpy.ops.object.mode_set(mode='EDIT')
428 bpy.ops.mesh.select_all(action='DESELECT')
430 # add skin modifier
431 shape_object.modifiers.new("Skin", 'SKIN')
432 bpy.ops.mesh.select_all(action='SELECT')
434 override = bpy.context.copy()
435 for area in bpy.context.screen.areas:
436 if area.type == 'VIEW_3D':
437 for region in area.regions:
438 if region.type == 'WINDOW':
439 override['area'] = area
440 override['region'] = region
441 override['edit_object'] = bpy.context.edit_object
442 override['scene'] = bpy.context.scene
443 override['active_object'] = shape_object
444 override['object'] = shape_object
445 override['modifier'] = bpy.context.object.modifiers
446 break
448 # calculate optimal thickness for defaults
449 bpy.ops.object.skin_root_mark(override)
450 bpy.ops.transform.skin_resize(
451 override,
452 value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
453 constraint_axis=(False, False, False),
454 orient_type='GLOBAL',
455 mirror=False,
456 use_proportional_edit=False,
458 shape_object.modifiers["Skin"].use_smooth_shade = True
459 shape_object.modifiers["Skin"].use_x_symmetry = True
461 # select finger vertices and calculate optimal thickness for fingers to fix proportions
462 if len(alternate_scale_idx_list) > 0:
463 select_vertices(shape_object, alternate_scale_idx_list)
465 bpy.ops.object.skin_loose_mark_clear(override, action='MARK')
466 # by default set fingers thickness to 25 percent of body thickness
467 bpy.ops.transform.skin_resize(
468 override,
469 value=(finger_thickness, finger_thickness, finger_thickness),
470 constraint_axis=(False, False, False), orient_type='GLOBAL',
471 mirror=False,
472 use_proportional_edit=False,
474 # make loose hands only for better topology
476 # bpy.ops.mesh.select_all(action='DESELECT')
478 if connect_mesh:
479 bpy.ops.object.mode_set(mode='EDIT')
480 bpy.ops.mesh.select_all(action='DESELECT')
481 bpy.ops.mesh.select_all(action='SELECT')
482 bpy.ops.mesh.remove_doubles()
484 idx_store = Idx_Store(rig_type)
486 # fix rigify and pitchipoy hands topology
487 if connect_mesh and connect_parents and generate_all is False and \
488 (rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \
489 selected_bones_num == total_bones_num:
490 # thickness will set palm vertex for both hands look pretty
491 corrective_thickness = 2.5
492 # left hand verts
493 merge_idx = idx_store.get_hand_l_merge_idx()
495 select_vertices(shape_object, merge_idx)
496 bpy.ops.mesh.merge(type='CENTER')
497 bpy.ops.transform.skin_resize(
498 override,
499 value=(corrective_thickness, corrective_thickness, corrective_thickness),
500 constraint_axis=(False, False, False), orient_type='GLOBAL',
501 mirror=False,
502 use_proportional_edit=False,
504 bpy.ops.mesh.select_all(action='DESELECT')
506 # right hand verts
507 merge_idx = idx_store.get_hand_r_merge_idx()
509 select_vertices(shape_object, merge_idx)
510 bpy.ops.mesh.merge(type='CENTER')
511 bpy.ops.transform.skin_resize(
512 override,
513 value=(corrective_thickness, corrective_thickness, corrective_thickness),
514 constraint_axis=(False, False, False), orient_type='GLOBAL',
515 mirror=False,
516 use_proportional_edit=False,
519 # making hands even more pretty
520 bpy.ops.mesh.select_all(action='DESELECT')
521 hands_idx = idx_store.get_hands_pretty_idx()
523 select_vertices(shape_object, hands_idx)
524 # change the thickness to make hands look less blocky and more sexy
525 corrective_thickness = 0.7
526 bpy.ops.transform.skin_resize(
527 override,
528 value=(corrective_thickness, corrective_thickness, corrective_thickness),
529 constraint_axis=(False, False, False), orient_type='GLOBAL',
530 mirror=False,
531 use_proportional_edit=False,
533 bpy.ops.mesh.select_all(action='DESELECT')
535 # todo optionally take root from rig's hip tail or head depending on scenario
537 root_idx = idx_store.get_root_idx()
539 if selected_bones_num == total_bones_num:
540 root_idx = [0]
542 if len(root_idx) > 0:
543 select_vertices(shape_object, root_idx)
544 bpy.ops.object.skin_root_mark(override)
545 # skin in edit mode
546 # add Subsurf modifier
547 shape_object.modifiers.new("Subsurf", 'SUBSURF')
548 shape_object.modifiers["Subsurf"].levels = sub_level
549 shape_object.modifiers["Subsurf"].render_levels = sub_level
551 bpy.ops.object.mode_set(mode='OBJECT')
553 # object mode apply all modifiers
554 if apply_mod:
555 bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Skin")
556 bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Subsurf")
558 return {'FINISHED'}
561 def main(context):
563 This script will create a custom shape
566 # ### Check if selection is OK ###
567 if len(context.selected_pose_bones) == 0 or \
568 len(context.selected_objects) == 0 or \
569 context.selected_objects[0].type != 'ARMATURE':
570 return {'CANCELLED'}, "No bone selected or the Armature is hidden"
572 scn = bpy.context.scene
573 sknfy = scn.skinify
575 # initialize the mesh object
576 mesh_name = context.selected_objects[0].name + "_mesh"
577 obj_name = context.selected_objects[0].name + "_object"
578 armature_object = context.object
580 origin = context.object.location
581 bone_selection = context.selected_pose_bones
582 oldLocation = None
583 oldRotation = None
584 oldScale = None
585 armature_object = context.view_layer.objects.active
586 armature_object.select_set(True)
588 old_pose_pos = armature_object.data.pose_position
589 bpy.ops.object.mode_set(mode='OBJECT')
590 oldLocation = Vector(armature_object.location)
591 oldRotation = Euler(armature_object.rotation_euler)
592 oldScale = Vector(armature_object.scale)
594 bpy.ops.object.rotation_clear(clear_delta=False)
595 bpy.ops.object.location_clear(clear_delta=False)
596 bpy.ops.object.scale_clear(clear_delta=False)
597 if sknfy.apply_mod and sknfy.parent_armature:
598 armature_object.data.pose_position = 'REST'
600 scale = bpy.context.object.scale
601 size = bpy.context.object.dimensions[2]
603 bpy.ops.object.mode_set(mode='OBJECT')
604 bpy.ops.object.select_all(action='DESELECT')
606 bpy.ops.object.add(type='MESH', enter_editmode=False, location=origin)
608 # get the mesh object
609 ob = context.view_layer.objects.active
610 ob.name = obj_name
611 me = ob.data
612 me.name = mesh_name
614 # this way we fit mesh and bvh with armature modifier correctly
616 alternate_scale_idx_list, rig_type = generate_edges(
617 me, ob, bone_selection, scale, sknfy.connect_mesh,
618 sknfy.connect_parents, sknfy.head_ornaments,
619 sknfy.generate_all
622 generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level,
623 sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
624 sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection)
626 # parent mesh with armature only if modifiers are applied
627 if sknfy.apply_mod and sknfy.parent_armature:
628 bpy.ops.object.mode_set(mode='OBJECT')
629 bpy.ops.object.select_all(action='DESELECT')
630 ob.select_set(True)
631 armature_object.select_set(True)
632 bpy.context.view_layer.objects.active = armature_object
634 bpy.ops.object.parent_set(type='ARMATURE_AUTO')
635 armature_object.data.pose_position = old_pose_pos
636 armature_object.select_set(False)
637 else:
638 bpy.ops.object.mode_set(mode='OBJECT')
639 ob.location = oldLocation
640 ob.rotation_euler = oldRotation
641 ob.scale = oldScale
642 ob.select_set(False)
643 armature_object.select_set(True)
644 scn.objects.active = armature_object
646 armature_object.location = oldLocation
647 armature_object.rotation_euler = oldRotation
648 armature_object.scale = oldScale
649 bpy.ops.object.mode_set(mode='POSE')
651 return {'FINISHED'}, me
654 class BONE_OT_custom_shape(Operator):
655 bl_idname = "object.skinify_rig"
656 bl_label = "Skinify Rig"
657 bl_description = "Creates a mesh object at the selected bones positions"
658 bl_options = {'UNDO', 'INTERNAL'}
660 @classmethod
661 def poll(cls, context):
662 return context.active_object is not None
664 def execute(self, context):
665 Mesh = main(context)
666 if Mesh[0] == {'CANCELLED'}:
667 self.report({'WARNING'}, Mesh[1])
668 return {'CANCELLED'}
669 else:
670 self.report({'INFO'}, Mesh[1].name + " has been created")
672 return {'FINISHED'}
675 class BONE_PT_custom_shape(Panel):
676 bl_space_type = "VIEW_3D"
677 bl_region_type = "UI"
678 bl_category = "Create"
679 # bl_context = "bone"
680 bl_label = "Skinify Rig"
681 bl_options = {'DEFAULT_CLOSED'}
683 @classmethod
684 def poll(cls, context):
685 ob = context.object
686 return ob and ob.mode == 'POSE' #and context.bone
688 def draw(self, context):
689 layout = self.layout
690 scn = context.scene.skinify
692 row = layout.row()
693 row.operator("object.skinify_rig", text="Add Shape", icon='BONE_DATA')
695 split = layout.split(factor=0.3)
696 split.label(text="Thickness:")
697 split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
698 split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')
700 split = layout.split(factor=0.3)
701 split.label(text="Mesh Density:")
702 split.prop(scn, "sub_level", icon='MESH_ICOSPHERE')
704 row = layout.row()
705 row.prop(scn, "connect_mesh", icon='EDITMODE_HLT')
706 row.prop(scn, "connect_parents", icon='CONSTRAINT_BONE')
707 row = layout.row()
708 row.prop(scn, "head_ornaments", icon='GROUP_BONE')
709 row.prop(scn, "generate_all", icon='GROUP_BONE')
710 row = layout.row()
711 row.prop(scn, "apply_mod", icon='FILE_TICK')
712 if scn.apply_mod:
713 row = layout.row()
714 row.prop(scn, "parent_armature", icon='POSE_HLT')
717 # define the scene properties in a group - call them with context.scene.skinify
718 class Skinify_Properties(PropertyGroup):
719 sub_level: IntProperty(
720 name="Sub level",
721 min=0, max=4,
722 default=1,
723 description="Mesh density"
725 thickness: FloatProperty(
726 name="Thickness",
727 min=0.01,
728 default=0.8,
729 description="Adjust shape thickness"
731 finger_thickness: FloatProperty(
732 name="Finger Thickness",
733 min=0.01, max=1.0,
734 default=0.25,
735 description="Adjust finger thickness relative to body"
737 connect_mesh: BoolProperty(
738 name="Solid Shape",
739 default=False,
740 description="Makes solid shape from bone chains"
742 connect_parents: BoolProperty(
743 name="Fill Gaps",
744 default=False,
745 description="Fills the gaps between parented bones"
747 generate_all: BoolProperty(
748 name="All Shapes",
749 default=False,
750 description="Generates shapes from all bones"
752 head_ornaments: BoolProperty(
753 name="Head Ornaments",
754 default=False,
755 description="Includes head ornaments"
757 apply_mod: BoolProperty(
758 name="Apply Modifiers",
759 default=True,
760 description="Applies Modifiers to mesh"
762 parent_armature: BoolProperty(
763 name="Parent Armature",
764 default=True,
765 description="Applies mesh to Armature"
769 # startup defaults
771 @persistent
772 def startup_init(dummy):
773 init_props()
776 def register():
777 bpy.utils.register_class(BONE_OT_custom_shape)
778 bpy.utils.register_class(BONE_PT_custom_shape)
779 bpy.utils.register_class(Skinify_Properties)
781 bpy.types.Scene.skinify = PointerProperty(
782 type=Skinify_Properties
784 # startup defaults
785 bpy.app.handlers.load_post.append(startup_init)
788 def unregister():
789 bpy.utils.unregister_class(BONE_OT_custom_shape)
790 bpy.utils.unregister_class(BONE_PT_custom_shape)
791 bpy.utils.unregister_class(Skinify_Properties)
793 # cleanup the handler
794 bpy.app.handlers.load_post.remove(startup_init)
796 del bpy.types.Scene.skinify
799 if __name__ == "__main__":
800 register()