Merge branch 'blender-v3.3-release'
[blender-addons.git] / object_skinify.py
blobe8f57b415bc70c0a88e5ccb56d4245598f6c24e3
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Skinify Rig",
5 "author": "Albert Makac (karab44)",
6 "version": (0, 11, 0),
7 "blender": (2, 80, 0),
8 "location": "Pose Mode > Sidebar > Create Tab",
9 "description": "Creates a mesh object from selected bones",
10 "warning": "Work in progress",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/skinify.html",
12 "category": "Object",
15 import bpy
16 from bpy.props import (
17 FloatProperty,
18 IntProperty,
19 BoolProperty,
20 PointerProperty,
22 from bpy.types import (
23 Operator,
24 Panel,
25 PropertyGroup,
27 from mathutils import (
28 Vector,
29 Euler,
31 from bpy.app.handlers import persistent
32 from enum import Enum
34 # can the armature data properties group_prop and row be fetched directly from the rigify script?
35 horse_data = \
36 (1, 5), (2, 4), (3, 0), (4, 3), (5, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), \
37 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), \
38 (13, 6), (1, 4), (14, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
40 shark_data = \
41 (1, 5), (2, 4), (1, 0), (3, 3), (4, 4), (5, 6), (6, 5), (7, 4), (6, 5), (7, 4), \
42 (8, 3), (9, 4), (1, 0), (1, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), \
43 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
45 bird_data = \
46 (1, 6), (2, 4), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (6, 5), (8, 0), (7, 4), (6, 5), \
47 (8, 0), (7, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
48 (13, 6), (14, 4), (1, 0), (8, 6), (1, 0), (1, 0), (1, 0), (14, 1),
50 cat_data = \
51 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
52 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 3), (14, 4), \
53 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (16, 1),
55 biped_data = \
56 (1, 0), (1, 0), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), (7, 2), \
57 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
58 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
60 human_data = \
61 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
62 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
63 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
65 wolf_data = \
66 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
67 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), (1, 0), \
68 (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
70 quadruped_data = \
71 (1, 0), (2, 0), (2, 0), (3, 3), (4, 4), (5, 0), (6, 0), (7, 2), (8, 5), (9, 4), \
72 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), \
73 (1, 0), (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
75 human_legacy_data = \
76 (1, None), (1, None), (2, None), (1, None), (3, None), (3, None), (4, None), (5, None), \
77 (6, None), (4, None), (5, None), (6, None), (7, None), (8, None), (9, None), (7, None), \
78 (8, None), (9, None), (1, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
79 (1, None), (1, None), (1, None), (1, None),
81 pitchipoy_data = \
82 (1, None), (2, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), \
83 (8, None), (9, None), (7, None), (8, None), (9, None), (10, None), (11, None), (12, None), \
84 (10, None), (11, None), (12, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
85 (1, None), (1, None), (1, None), (1, None),
87 rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \
88 wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
91 class Rig_type(Enum):
92 HORSE = 0
93 SHARK = 1
94 BIRD = 2
95 CAT = 3
96 BIPED = 4
97 HUMAN = 5
98 WOLF = 6
99 QUAD = 7
100 LEGACY = 8
101 PITCHIPOY = 9
102 OTHER = 10
105 rig_type = Rig_type.OTHER
108 class Idx_Store(object):
109 def __init__(self, rig_type):
110 self.rig_type = rig_type
111 self.hand_r_merge = []
112 self.hand_l_merge = []
113 self.hands_pretty = []
114 self.root = []
116 if not self.rig_type == Rig_type.LEGACY and \
117 not self.rig_type == Rig_type.HUMAN and \
118 not self.rig_type == Rig_type.PITCHIPOY:
119 return
121 if self.rig_type == Rig_type.LEGACY:
122 self.hand_l_merge = [7, 12, 16, 21, 26, 27]
123 self.hand_r_merge = [30, 31, 36, 40, 45, 50]
124 self.hands_pretty = [6, 29]
125 self.root = [59]
127 if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
128 self.hand_l_merge = [9, 10, 15, 19, 24, 29]
129 self.hand_r_merge = [32, 33, 37, 42, 47, 52]
130 self.hands_pretty = [8, 31]
131 self.root = [56]
133 def get_all_idx(self):
134 return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root
136 def get_hand_l_merge_idx(self):
137 return self.hand_l_merge
139 def get_hand_r_merge_idx(self):
140 return self.hand_r_merge
142 def get_hands_pretty_idx(self):
143 return self.hands_pretty
145 def get_root_idx(self):
146 return self.root
149 # initialize properties
150 def init_props():
151 # additional check - this should be a rare case if the handler
152 # wasn't removed for some reason and the add-on is not toggled on/off
153 if hasattr(bpy.types.Scene, "skinify"):
154 scn = bpy.context.scene.skinify
156 scn.connect_mesh = False
157 scn.connect_parents = False
158 scn.generate_all = False
159 scn.thickness = 0.8
160 scn.finger_thickness = 0.25
161 scn.apply_mod = True
162 scn.parent_armature = True
163 scn.sub_level = 1
166 # selects vertices
167 def select_vertices(mesh_obj, idx):
168 bpy.context.view_layer.objects.active = mesh_obj
169 mode = mesh_obj.mode
170 bpy.ops.object.mode_set(mode='EDIT')
171 bpy.ops.mesh.select_all(action='DESELECT')
172 bpy.ops.object.mode_set(mode='OBJECT')
174 for i in idx:
175 mesh_obj.data.vertices[i].select = True
177 selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
179 bpy.ops.object.mode_set(mode=mode)
180 return selectedVerts
183 def identify_rig():
184 if 'rigify_layers' not in bpy.context.object.data:
185 return Rig_type.OTHER # non recognized
187 LEGACY_LAYERS_SIZE = 28
188 layers = bpy.context.object.data['rigify_layers']
190 for type, rig in enumerate(rigify_data):
191 index = 0
193 for props in layers:
194 if len(layers) == LEGACY_LAYERS_SIZE and 'group_prop' not in props:
196 if props['row'] != rig[index][0] or rig[index][1] is not None:
197 break
199 elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
200 break
202 # SUCCESS if reach the end
203 if index == len(layers) - 1:
204 return Rig_type(type)
206 index = index + 1
208 return Rig_type.OTHER
211 def prepare_ignore_list(rig_type, bones):
212 # detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization
213 common_ignore_list = ['eye', 'heel', 'breast', 'root']
215 # edit these lists to suits your taste
217 horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.']
219 shark_ignore_list = ['jaw']
221 bird_ignore_list = [
222 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
223 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
225 cat_ignore_list = [
226 'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
227 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
229 biped_ignore_list = ['pelvis']
231 human_ignore_list = [
232 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
233 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
235 wolf_ignore_list = [
236 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
237 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
239 quad_ignore_list = [
240 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
241 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
243 rigify_legacy_ignore_list = []
245 pitchipoy_ignore_list = [
246 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
247 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
250 other_ignore_list = []
252 ignore_list = common_ignore_list
254 if rig_type == Rig_type.HORSE:
255 ignore_list = ignore_list + horse_ignore_list
256 print("RIDER OF THE APOCALYPSE")
257 elif rig_type == Rig_type.SHARK:
258 ignore_list = ignore_list + shark_ignore_list
259 print("DEADLY JAWS")
260 elif rig_type == Rig_type.BIRD:
261 ignore_list = ignore_list + bird_ignore_list
262 print("WINGS OF LIBERTY")
263 elif rig_type == Rig_type.CAT:
264 ignore_list = ignore_list + cat_ignore_list
265 print("MEOW")
266 elif rig_type == Rig_type.BIPED:
267 ignore_list = ignore_list + biped_ignore_list
268 print("HUMANOID")
269 elif rig_type == Rig_type.HUMAN:
270 ignore_list = ignore_list + human_ignore_list
271 print("JUST A HUMAN AFTER ALL")
272 elif rig_type == Rig_type.WOLF:
273 ignore_list = ignore_list + wolf_ignore_list
274 print("WHITE FANG")
275 elif rig_type == Rig_type.QUAD:
276 ignore_list = ignore_list + quad_ignore_list
277 print("MYSTERIOUS CREATURE")
278 elif rig_type == Rig_type.LEGACY:
279 ignore_list = ignore_list + rigify_legacy_ignore_list
280 print("LEGACY RIGIFY")
281 elif rig_type == Rig_type.PITCHIPOY:
282 ignore_list = ignore_list + pitchipoy_ignore_list
283 print("PITCHIPOY")
284 elif rig_type == Rig_type.OTHER:
285 ignore_list = ignore_list + other_ignore_list
286 print("rig non recognized...")
288 return ignore_list
291 # generates edges from vertices used by skin modifier
292 def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False,
293 head_ornaments=False, generate_all=False):
295 This function adds vertices for all heads and tails
297 # scene preferences
299 alternate_scale_list = []
301 me = mesh
302 verts = []
303 edges = []
304 idx = 0
305 alternate_scale_idx_list = list()
307 rig_type = identify_rig()
308 ignore_list = prepare_ignore_list(rig_type, bones)
310 # edge generator loop
311 for b in bones:
312 # look for rig's hands and their childs
313 if 'hand' in b.name.lower():
314 # prepare the list
315 for c in b.children_recursive:
316 alternate_scale_list.append(c.name)
318 found = False
320 for i in ignore_list:
321 if i in b.name.lower():
322 found = True
323 break
325 if found and generate_all is False:
326 continue
328 # fix for drawing rootbone and relationship lines
329 if 'root' in b.name.lower() and generate_all is False:
330 continue
332 # ignore any head ornaments
333 if head_ornaments is False:
334 if b.parent is not None:
336 if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN:
337 continue
339 if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN:
340 continue
342 if connect_parents:
343 if b.parent is not None and b.parent.bone.select is True and b.bone.use_connect is False:
344 if 'root' in b.parent.name.lower() and generate_all is False:
345 continue
346 # ignore shoulder
347 if 'shoulder' in b.name.lower() and connect_mesh is True:
348 continue
349 # connect the upper arm directly with chest omitting shoulders
350 if 'shoulder' in b.parent.name.lower() and connect_mesh is True:
351 vert1 = b.head
352 vert2 = b.parent.parent.tail
354 else:
355 vert1 = b.head
356 vert2 = b.parent.tail
358 verts.append(vert1)
359 verts.append(vert2)
360 edges.append([idx, idx + 1])
362 # also make list of edges made of gaps between the bones
363 for a in alternate_scale_list:
364 if b.name == a:
365 alternate_scale_idx_list.append(idx)
366 alternate_scale_idx_list.append(idx + 1)
368 idx = idx + 2
369 # for bvh free floating hips and hips correction for rigify and pitchipoy
370 if ((generate_all is False and 'hip' in b.name.lower()) or
371 (generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or
372 (b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and
373 rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))):
374 continue
376 vert1 = b.head
377 vert2 = b.tail
378 verts.append(vert1)
379 verts.append(vert2)
381 edges.append([idx, idx + 1])
383 for a in alternate_scale_list:
384 if b.name == a:
385 alternate_scale_idx_list.append(idx)
386 alternate_scale_idx_list.append(idx + 1)
388 idx = idx + 2
390 # Create mesh from given verts, faces
391 me.from_pydata(verts, edges, [])
392 # Update mesh with new data
393 me.update()
395 # set object scale exact as armature's scale
396 shape_object.scale = scale
398 return alternate_scale_idx_list, rig_type
401 def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1,
402 connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True,
403 alternate_scale_idx_list=[], rig_type=0, bones=[]):
405 This function adds modifiers for generated edges
407 total_bones_num = bpy.context.selected_pose_bones_from_active_object
408 selected_bones_num = len(bones)
410 bpy.ops.object.mode_set(mode='EDIT')
411 bpy.ops.mesh.select_all(action='DESELECT')
413 # add skin modifier
414 shape_object.modifiers.new("Skin", 'SKIN')
415 bpy.ops.mesh.select_all(action='SELECT')
417 override = bpy.context.copy()
418 for area in bpy.context.screen.areas:
419 if area.type == 'VIEW_3D':
420 for region in area.regions:
421 if region.type == 'WINDOW':
422 override['area'] = area
423 override['region'] = region
424 override['edit_object'] = bpy.context.edit_object
425 override['scene'] = bpy.context.scene
426 override['active_object'] = shape_object
427 override['object'] = shape_object
428 override['modifier'] = bpy.context.object.modifiers
429 break
431 # calculate optimal thickness for defaults
432 bpy.ops.object.skin_root_mark(override)
433 bpy.ops.transform.skin_resize(
434 override,
435 value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
436 constraint_axis=(False, False, False),
437 orient_type='GLOBAL',
438 mirror=False,
439 use_proportional_edit=False,
441 shape_object.modifiers["Skin"].use_smooth_shade = True
442 shape_object.modifiers["Skin"].use_x_symmetry = True
444 # select finger vertices and calculate optimal thickness for fingers to fix proportions
445 if len(alternate_scale_idx_list) > 0:
446 select_vertices(shape_object, alternate_scale_idx_list)
448 bpy.ops.object.skin_loose_mark_clear(override, action='MARK')
449 # by default set fingers thickness to 25 percent of body thickness
450 bpy.ops.transform.skin_resize(
451 override,
452 value=(finger_thickness, finger_thickness, finger_thickness),
453 constraint_axis=(False, False, False), orient_type='GLOBAL',
454 mirror=False,
455 use_proportional_edit=False,
457 # make loose hands only for better topology
459 # bpy.ops.mesh.select_all(action='DESELECT')
461 if connect_mesh:
462 bpy.ops.object.mode_set(mode='EDIT')
463 bpy.ops.mesh.select_all(action='DESELECT')
464 bpy.ops.mesh.select_all(action='SELECT')
465 bpy.ops.mesh.remove_doubles()
467 idx_store = Idx_Store(rig_type)
469 # fix rigify and pitchipoy hands topology
470 if connect_mesh and connect_parents and generate_all is False and \
471 (rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \
472 selected_bones_num == total_bones_num:
473 # thickness will set palm vertex for both hands look pretty
474 corrective_thickness = 2.5
475 # left hand verts
476 merge_idx = idx_store.get_hand_l_merge_idx()
478 select_vertices(shape_object, merge_idx)
479 bpy.ops.mesh.merge(type='CENTER')
480 bpy.ops.transform.skin_resize(
481 override,
482 value=(corrective_thickness, corrective_thickness, corrective_thickness),
483 constraint_axis=(False, False, False), orient_type='GLOBAL',
484 mirror=False,
485 use_proportional_edit=False,
487 bpy.ops.mesh.select_all(action='DESELECT')
489 # right hand verts
490 merge_idx = idx_store.get_hand_r_merge_idx()
492 select_vertices(shape_object, merge_idx)
493 bpy.ops.mesh.merge(type='CENTER')
494 bpy.ops.transform.skin_resize(
495 override,
496 value=(corrective_thickness, corrective_thickness, corrective_thickness),
497 constraint_axis=(False, False, False), orient_type='GLOBAL',
498 mirror=False,
499 use_proportional_edit=False,
502 # making hands even more pretty
503 bpy.ops.mesh.select_all(action='DESELECT')
504 hands_idx = idx_store.get_hands_pretty_idx()
506 select_vertices(shape_object, hands_idx)
507 # change the thickness to make hands look less blocky and more sexy
508 corrective_thickness = 0.7
509 bpy.ops.transform.skin_resize(
510 override,
511 value=(corrective_thickness, corrective_thickness, corrective_thickness),
512 constraint_axis=(False, False, False), orient_type='GLOBAL',
513 mirror=False,
514 use_proportional_edit=False,
516 bpy.ops.mesh.select_all(action='DESELECT')
518 # todo optionally take root from rig's hip tail or head depending on scenario
520 root_idx = idx_store.get_root_idx()
522 if selected_bones_num == total_bones_num:
523 root_idx = [0]
525 if len(root_idx) > 0:
526 select_vertices(shape_object, root_idx)
527 bpy.ops.object.skin_root_mark(override)
528 # skin in edit mode
529 # add Subsurf modifier
530 shape_object.modifiers.new("Subsurf", 'SUBSURF')
531 shape_object.modifiers["Subsurf"].levels = sub_level
532 shape_object.modifiers["Subsurf"].render_levels = sub_level
534 bpy.ops.object.mode_set(mode='OBJECT')
536 # object mode apply all modifiers
537 if apply_mod:
538 bpy.ops.object.modifier_apply(override, modifier="Skin")
539 bpy.ops.object.modifier_apply(override, modifier="Subsurf")
541 return {'FINISHED'}
544 def main(context):
546 This script will create a custom shape
549 # ### Check if selection is OK ###
550 if len(context.selected_pose_bones) == 0 or \
551 len(context.selected_objects) == 0 or \
552 context.selected_objects[0].type != 'ARMATURE':
553 return {'CANCELLED'}, "No bone selected or the Armature is hidden"
555 scn = bpy.context.scene
556 sknfy = scn.skinify
558 # initialize the mesh object
559 mesh_name = context.selected_objects[0].name + "_mesh"
560 obj_name = context.selected_objects[0].name + "_object"
561 armature_object = context.object
563 origin = context.object.location
564 bone_selection = context.selected_pose_bones
565 oldLocation = None
566 oldRotation = None
567 oldScale = None
568 armature_object = context.view_layer.objects.active
569 armature_object.select_set(True)
571 old_pose_pos = armature_object.data.pose_position
572 bpy.ops.object.mode_set(mode='OBJECT')
573 oldLocation = Vector(armature_object.location)
574 oldRotation = Euler(armature_object.rotation_euler)
575 oldScale = Vector(armature_object.scale)
577 bpy.ops.object.rotation_clear(clear_delta=False)
578 bpy.ops.object.location_clear(clear_delta=False)
579 bpy.ops.object.scale_clear(clear_delta=False)
580 if sknfy.apply_mod and sknfy.parent_armature:
581 armature_object.data.pose_position = 'REST'
583 scale = bpy.context.object.scale
584 size = bpy.context.object.dimensions[2]
586 bpy.ops.object.mode_set(mode='OBJECT')
587 bpy.ops.object.select_all(action='DESELECT')
589 bpy.ops.object.add(type='MESH', enter_editmode=False, location=origin)
591 # get the mesh object
592 ob = context.view_layer.objects.active
593 ob.name = obj_name
594 me = ob.data
595 me.name = mesh_name
597 # this way we fit mesh and bvh with armature modifier correctly
599 alternate_scale_idx_list, rig_type = generate_edges(
600 me, ob, bone_selection, scale, sknfy.connect_mesh,
601 sknfy.connect_parents, sknfy.head_ornaments,
602 sknfy.generate_all
605 generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level,
606 sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
607 sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection)
609 # parent mesh with armature only if modifiers are applied
610 if sknfy.apply_mod and sknfy.parent_armature:
611 bpy.ops.object.mode_set(mode='OBJECT')
612 bpy.ops.object.select_all(action='DESELECT')
613 ob.select_set(True)
614 armature_object.select_set(True)
615 bpy.context.view_layer.objects.active = armature_object
617 bpy.ops.object.parent_set(type='ARMATURE_AUTO')
618 armature_object.data.pose_position = old_pose_pos
619 armature_object.select_set(False)
620 else:
621 bpy.ops.object.mode_set(mode='OBJECT')
622 ob.location = oldLocation
623 ob.rotation_euler = oldRotation
624 ob.scale = oldScale
625 ob.select_set(False)
626 armature_object.select_set(True)
627 scn.objects.active = armature_object
629 armature_object.location = oldLocation
630 armature_object.rotation_euler = oldRotation
631 armature_object.scale = oldScale
632 bpy.ops.object.mode_set(mode='POSE')
634 return {'FINISHED'}, me
637 class BONE_OT_custom_shape(Operator):
638 bl_idname = "object.skinify_rig"
639 bl_label = "Skinify Rig"
640 bl_description = "Creates a mesh object at the selected bones positions"
641 bl_options = {'UNDO', 'INTERNAL'}
643 @classmethod
644 def poll(cls, context):
645 return context.active_object is not None
647 def execute(self, context):
648 Mesh = main(context)
649 if Mesh[0] == {'CANCELLED'}:
650 self.report({'WARNING'}, Mesh[1])
651 return {'CANCELLED'}
652 else:
653 self.report({'INFO'}, Mesh[1].name + " has been created")
655 return {'FINISHED'}
658 class BONE_PT_custom_shape(Panel):
659 bl_space_type = "VIEW_3D"
660 bl_region_type = "UI"
661 bl_category = "Create"
662 # bl_context = "bone"
663 bl_label = "Skinify Rig"
664 bl_options = {'DEFAULT_CLOSED'}
666 @classmethod
667 def poll(cls, context):
668 ob = context.object
669 return ob and ob.mode == 'POSE' #and context.bone
671 def draw(self, context):
672 layout = self.layout
673 scn = context.scene.skinify
675 row = layout.row()
676 row.operator("object.skinify_rig", text="Add Shape", icon='BONE_DATA')
678 split = layout.split(factor=0.3)
679 split.label(text="Thickness:")
680 split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
681 split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')
683 split = layout.split(factor=0.3)
684 split.label(text="Mesh Density:")
685 split.prop(scn, "sub_level", icon='MESH_ICOSPHERE')
687 row = layout.row()
688 row.prop(scn, "connect_mesh", icon='EDITMODE_HLT')
689 row.prop(scn, "connect_parents", icon='CONSTRAINT_BONE')
690 row = layout.row()
691 row.prop(scn, "head_ornaments", icon='GROUP_BONE')
692 row.prop(scn, "generate_all", icon='GROUP_BONE')
693 row = layout.row()
694 row.prop(scn, "apply_mod", icon='FILE_TICK')
695 if scn.apply_mod:
696 row = layout.row()
697 row.prop(scn, "parent_armature", icon='POSE_HLT')
700 # define the scene properties in a group - call them with context.scene.skinify
701 class Skinify_Properties(PropertyGroup):
702 sub_level: IntProperty(
703 name="Sub level",
704 min=0, max=4,
705 default=1,
706 description="Mesh density"
708 thickness: FloatProperty(
709 name="Thickness",
710 min=0.01,
711 default=0.8,
712 description="Adjust shape thickness"
714 finger_thickness: FloatProperty(
715 name="Finger Thickness",
716 min=0.01, max=1.0,
717 default=0.25,
718 description="Adjust finger thickness relative to body"
720 connect_mesh: BoolProperty(
721 name="Solid Shape",
722 default=False,
723 description="Makes solid shape from bone chains"
725 connect_parents: BoolProperty(
726 name="Fill Gaps",
727 default=False,
728 description="Fills the gaps between parented bones"
730 generate_all: BoolProperty(
731 name="All Shapes",
732 default=False,
733 description="Generates shapes from all bones"
735 head_ornaments: BoolProperty(
736 name="Head Ornaments",
737 default=False,
738 description="Includes head ornaments"
740 apply_mod: BoolProperty(
741 name="Apply Modifiers",
742 default=True,
743 description="Applies Modifiers to mesh"
745 parent_armature: BoolProperty(
746 name="Parent Armature",
747 default=True,
748 description="Applies mesh to Armature"
752 # startup defaults
754 @persistent
755 def startup_init(dummy):
756 init_props()
759 def register():
760 bpy.utils.register_class(BONE_OT_custom_shape)
761 bpy.utils.register_class(BONE_PT_custom_shape)
762 bpy.utils.register_class(Skinify_Properties)
764 bpy.types.Scene.skinify = PointerProperty(
765 type=Skinify_Properties
767 # startup defaults
768 bpy.app.handlers.load_post.append(startup_init)
771 def unregister():
772 bpy.utils.unregister_class(BONE_OT_custom_shape)
773 bpy.utils.unregister_class(BONE_PT_custom_shape)
774 bpy.utils.unregister_class(Skinify_Properties)
776 # cleanup the handler
777 bpy.app.handlers.load_post.remove(startup_init)
779 del bpy.types.Scene.skinify
782 if __name__ == "__main__":
783 register()