Fix T52833: OBJ triangulate doesn't match viewport
[blender-addons.git] / object_skinify.py
blob079a4202f7b78c3d91708127c2df5613bce60571
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, 7, 9),
24 "location": "Properties > Bone > Skinify Rig (visible on pose mode only)",
25 "description": "Creates a mesh object from selected bones",
26 "warning": "",
27 "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
28 "Py/Scripts/Object/Skinify",
29 "category": "Object"}
31 import bpy
32 from bpy.props import (
33 FloatProperty,
34 IntProperty,
35 BoolProperty,
36 PointerProperty,
38 from bpy.types import (
39 Operator,
40 Panel,
41 PropertyGroup,
43 from mathutils import (
44 Vector,
45 Euler,
47 from bpy.app.handlers import persistent
48 from enum import Enum
50 # can the armature data properties group_prop and row be fetched directly from the rigify script?
51 horse_data = \
52 (1, 5), (2, 4), (3, 0), (4, 3), (5, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), \
53 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), \
54 (13, 6), (1, 4), (14, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
56 shark_data = \
57 (1, 5), (2, 4), (1, 0), (3, 3), (4, 4), (5, 6), (6, 5), (7, 4), (6, 5), (7, 4), \
58 (8, 3), (9, 4), (1, 0), (1, 6), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), \
59 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
61 bird_data = \
62 (1, 6), (2, 4), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (6, 5), (8, 0), (7, 4), (6, 5), \
63 (8, 0), (7, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
64 (13, 6), (14, 4), (1, 0), (8, 6), (1, 0), (1, 0), (1, 0), (14, 1),
66 cat_data = \
67 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
68 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 3), (14, 4), \
69 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (16, 1),
71 biped_data = \
72 (1, 0), (1, 0), (1, 0), (3, 3), (4, 4), (1, 0), (1, 0), (7, 2), (8, 5), (9, 4), (7, 2), \
73 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
74 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
76 human_data = \
77 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
78 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (1, 0), (1, 0), \
79 (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
81 wolf_data = \
82 (1, 5), (2, 2), (2, 3), (3, 3), (4, 4), (5, 6), (6, 4), (7, 2), (8, 5), (9, 4), (7, 2), \
83 (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), (1, 0), \
84 (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
86 quadruped_data = \
87 (1, 0), (2, 0), (2, 0), (3, 3), (4, 4), (5, 0), (6, 0), (7, 2), (8, 5), (9, 4), \
88 (7, 2), (8, 5), (9, 4), (10, 2), (11, 5), (12, 4), (10, 2), (11, 5), (12, 4), (13, 6), \
89 (1, 0), (13, 0), (13, 0), (1, 0), (1, 0), (1, 0), (1, 0), (1, 0), (14, 1),
91 human_legacy_data = \
92 (1, None), (1, None), (2, None), (1, None), (3, None), (3, None), (4, None), (5, None), \
93 (6, None), (4, None), (5, None), (6, None), (7, None), (8, None), (9, None), (7, None), \
94 (8, None), (9, None), (1, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
95 (1, None), (1, None), (1, None), (1, None),
97 pitchipoy_data = \
98 (1, None), (2, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), \
99 (8, None), (9, None), (7, None), (8, None), (9, None), (10, None), (11, None), (12, None), \
100 (10, None), (11, None), (12, None), (1, None), (1, None), (1, None), (1, None), (1, None), \
101 (1, None), (1, None), (1, None), (1, None),
103 rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_data, \
104 wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
107 class Rig_type(Enum):
108 HORSE = 0
109 SHARK = 1
110 BIRD = 2
111 CAT = 3
112 BIPED = 4
113 HUMAN = 5
114 WOLF = 6
115 QUAD = 7
116 LEGACY = 8
117 PITCHIPOY = 9
118 OTHER = 10
121 rig_type = Rig_type.OTHER
124 class Idx_Store(object):
125 def __init__(self, rig_type):
126 self.rig_type = rig_type
127 self.hand_r_merge = []
128 self.hand_l_merge = []
129 self.hands_pretty = []
130 self.root = []
132 if not self.rig_type == Rig_type.LEGACY and \
133 not self.rig_type == Rig_type.HUMAN and \
134 not self.rig_type == Rig_type.PITCHIPOY:
135 return
137 if self.rig_type == Rig_type.LEGACY:
138 self.hand_l_merge = [7, 12, 16, 21, 26, 27]
139 self.hand_r_merge = [30, 31, 36, 40, 45, 50]
140 self.hands_pretty = [6, 29]
141 self.root = [59]
143 if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
144 self.hand_l_merge = [9, 10, 15, 19, 24, 29]
145 self.hand_r_merge = [32, 33, 37, 42, 47, 52]
146 self.hands_pretty = [8, 31]
147 self.root = [56]
149 def get_all_idx(self):
150 return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root
152 def get_hand_l_merge_idx(self):
153 return self.hand_l_merge
155 def get_hand_r_merge_idx(self):
156 return self.hand_r_merge
158 def get_hands_pretty_idx(self):
159 return self.hands_pretty
161 def get_root_idx(self):
162 return self.root
165 # initialize properties
166 def init_props():
167 # additional check - this should be a rare case if the handler
168 # wasn't removed for some reason and the add-on is not toggled on/off
169 if hasattr(bpy.types.Scene, "skinify"):
170 scn = bpy.context.scene.skinify
172 scn.connect_mesh = False
173 scn.connect_parents = False
174 scn.generate_all = False
175 scn.thickness = 0.8
176 scn.finger_thickness = 0.25
177 scn.apply_mod = True
178 scn.parent_armature = True
179 scn.sub_level = 1
182 # selects vertices
183 def select_vertices(mesh_obj, idx):
184 bpy.context.scene.objects.active = mesh_obj
185 mode = mesh_obj.mode
186 bpy.ops.object.mode_set(mode='EDIT')
187 bpy.ops.mesh.select_all(action='DESELECT')
188 bpy.ops.object.mode_set(mode='OBJECT')
190 for i in idx:
191 mesh_obj.data.vertices[i].select = True
193 selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
195 bpy.ops.object.mode_set(mode=mode)
196 return selectedVerts
199 def identify_rig():
200 if 'rigify_layers' not in bpy.context.object.data:
201 return Rig_type.OTHER # non recognized
203 LEGACY_LAYERS_SIZE = 28
204 layers = bpy.context.object.data['rigify_layers']
206 for type, rig in enumerate(rigify_data):
207 index = 0
209 for props in layers:
210 if len(layers) == LEGACY_LAYERS_SIZE and 'group_prop' not in props:
212 if props['row'] != rig[index][0] or rig[index][1] is not None:
213 break
215 elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
216 break
218 # SUCCESS if reach the end
219 if index == len(layers) - 1:
220 return Rig_type(type)
222 index = index + 1
224 return Rig_type.OTHER
227 def prepare_ignore_list(rig_type, bones):
228 # detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization
229 common_ignore_list = ['eye', 'heel', 'breast', 'root']
231 # edit these lists to suits your taste
233 horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.']
235 shark_ignore_list = ['jaw']
237 bird_ignore_list = [
238 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
239 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
241 cat_ignore_list = [
242 'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
243 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
245 biped_ignore_list = ['pelvis']
247 human_ignore_list = [
248 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
249 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
251 wolf_ignore_list = [
252 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
253 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
255 quad_ignore_list = [
256 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
257 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
259 rigify_legacy_ignore_list = []
261 pitchipoy_ignore_list = [
262 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
263 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
266 other_ignore_list = []
268 ignore_list = common_ignore_list
270 if rig_type == Rig_type.HORSE:
271 ignore_list = ignore_list + horse_ignore_list
272 print("RIDER OF THE APOCALYPSE")
273 elif rig_type == Rig_type.SHARK:
274 ignore_list = ignore_list + shark_ignore_list
275 print("DEADLY JAWS")
276 elif rig_type == Rig_type.BIRD:
277 ignore_list = ignore_list + bird_ignore_list
278 print("WINGS OF LIBERTY")
279 elif rig_type == Rig_type.CAT:
280 ignore_list = ignore_list + cat_ignore_list
281 print("MEOW")
282 elif rig_type == Rig_type.BIPED:
283 ignore_list = ignore_list + biped_ignore_list
284 print("HUMANOID")
285 elif rig_type == Rig_type.HUMAN:
286 ignore_list = ignore_list + human_ignore_list
287 print("JUST A HUMAN AFTER ALL")
288 elif rig_type == Rig_type.WOLF:
289 ignore_list = ignore_list + wolf_ignore_list
290 print("WHITE FANG")
291 elif rig_type == Rig_type.QUAD:
292 ignore_list = ignore_list + quad_ignore_list
293 print("MYSTERIOUS CREATURE")
294 elif rig_type == Rig_type.LEGACY:
295 ignore_list = ignore_list + rigify_legacy_ignore_list
296 print("LEGACY RIGIFY")
297 elif rig_type == Rig_type.PITCHIPOY:
298 ignore_list = ignore_list + pitchipoy_ignore_list
299 print("PITCHIPOY")
300 elif rig_type == Rig_type.OTHER:
301 ignore_list = ignore_list + other_ignore_list
302 print("rig non recognized...")
304 return ignore_list
307 # generates edges from vertices used by skin modifier
308 def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False,
309 head_ornaments=False, generate_all=False):
311 This function adds vertices for all heads and tails
313 # scene preferences
315 alternate_scale_list = []
317 me = mesh
318 verts = []
319 edges = []
320 idx = 0
321 alternate_scale_idx_list = list()
323 rig_type = identify_rig()
324 ignore_list = prepare_ignore_list(rig_type, bones)
326 # edge generator loop
327 for b in bones:
328 # look for rig's hands and their childs
329 if 'hand' in b.name.lower():
330 # prepare the list
331 for c in b.children_recursive:
332 alternate_scale_list.append(c.name)
334 found = False
336 for i in ignore_list:
337 if i in b.name.lower():
338 found = True
339 break
341 if found and generate_all is False:
342 continue
344 # fix for drawing rootbone and relationship lines
345 if 'root' in b.name.lower() and generate_all is False:
346 continue
348 # ignore any head ornaments
349 if head_ornaments is False:
350 if b.parent is not None:
352 if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN:
353 continue
355 if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN:
356 continue
358 if connect_parents:
359 if b.parent is not None and b.parent.bone.select is True and b.bone.use_connect is False:
360 if 'root' in b.parent.name.lower() and generate_all is False:
361 continue
362 # ignore shoulder
363 if 'shoulder' in b.name.lower() and connect_mesh is True:
364 continue
365 # connect the upper arm directly with chest ommiting shoulders
366 if 'shoulder' in b.parent.name.lower() and connect_mesh is True:
367 vert1 = b.head
368 vert2 = b.parent.parent.tail
370 else:
371 vert1 = b.head
372 vert2 = b.parent.tail
374 verts.append(vert1)
375 verts.append(vert2)
376 edges.append([idx, idx + 1])
378 # also make list of edges made of gaps between the bones
379 for a in alternate_scale_list:
380 if b.name == a:
381 alternate_scale_idx_list.append(idx)
382 alternate_scale_idx_list.append(idx + 1)
384 idx = idx + 2
385 # for bvh free floating hips and hips correction for rigify and pitchipoy
386 if ((generate_all is False and 'hip' in b.name.lower()) or
387 (generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or
388 (b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and
389 rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))):
390 continue
392 vert1 = b.head
393 vert2 = b.tail
394 verts.append(vert1)
395 verts.append(vert2)
397 edges.append([idx, idx + 1])
399 for a in alternate_scale_list:
400 if b.name == a:
401 alternate_scale_idx_list.append(idx)
402 alternate_scale_idx_list.append(idx + 1)
404 idx = idx + 2
406 # Create mesh from given verts, faces
407 me.from_pydata(verts, edges, [])
408 # Update mesh with new data
409 me.update()
411 # set object scale exact as armature's scale
412 shape_object.scale = scale
414 return alternate_scale_idx_list, rig_type
417 def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1,
418 connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True,
419 alternate_scale_idx_list=[], rig_type=0, bones=[]):
421 This function adds modifiers for generated edges
423 total_bones_num = len(bpy.context.object.pose.bones.keys())
424 selected_bones_num = len(bones)
426 bpy.ops.object.mode_set(mode='EDIT')
427 bpy.ops.mesh.select_all(action='DESELECT')
429 # add skin modifier
430 shape_object.modifiers.new("Skin", 'SKIN')
431 bpy.ops.mesh.select_all(action='SELECT')
433 override = bpy.context.copy()
434 for area in bpy.context.screen.areas:
435 if area.type == 'VIEW_3D':
436 for region in area.regions:
437 if region.type == 'WINDOW':
438 override['area'] = area
439 override['region'] = region
440 override['edit_object'] = bpy.context.edit_object
441 override['scene'] = bpy.context.scene
442 override['active_object'] = shape_object
443 override['object'] = shape_object
444 override['modifier'] = bpy.context.object.modifiers
445 break
447 # calculate optimal thickness for defaults
448 bpy.ops.object.skin_root_mark(override)
449 bpy.ops.transform.skin_resize(override,
450 value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
451 constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
452 mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
453 proportional_size=1
455 shape_object.modifiers["Skin"].use_smooth_shade = True
456 shape_object.modifiers["Skin"].use_x_symmetry = True
458 # select finger vertices and calculate optimal thickness for fingers to fix proportions
459 if len(alternate_scale_idx_list) > 0:
460 select_vertices(shape_object, alternate_scale_idx_list)
462 bpy.ops.object.skin_loose_mark_clear(override, action='MARK')
463 # by default set fingers thickness to 25 percent of body thickness
464 bpy.ops.transform.skin_resize(override,
465 value=(finger_thickness, finger_thickness, finger_thickness),
466 constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
467 mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
468 proportional_size=1
470 # make loose hands only for better topology
472 # bpy.ops.mesh.select_all(action='DESELECT')
474 if connect_mesh:
475 bpy.ops.object.mode_set(mode='EDIT')
476 bpy.ops.mesh.select_all(action='DESELECT')
477 bpy.ops.mesh.select_all(action='SELECT')
478 bpy.ops.mesh.remove_doubles()
480 idx_store = Idx_Store(rig_type)
482 # fix rigify and pitchipoy hands topology
483 if connect_mesh and connect_parents and generate_all is False and \
484 (rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \
485 selected_bones_num == total_bones_num:
486 # thickness will set palm vertex for both hands look pretty
487 corrective_thickness = 2.5
488 # left hand verts
489 merge_idx = idx_store.get_hand_l_merge_idx()
491 select_vertices(shape_object, merge_idx)
492 bpy.ops.mesh.merge(type='CENTER')
493 bpy.ops.transform.skin_resize(override,
494 value=(corrective_thickness, corrective_thickness, corrective_thickness),
495 constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
496 mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
497 proportional_size=1
499 bpy.ops.mesh.select_all(action='DESELECT')
501 # right hand verts
502 merge_idx = idx_store.get_hand_r_merge_idx()
504 select_vertices(shape_object, merge_idx)
505 bpy.ops.mesh.merge(type='CENTER')
506 bpy.ops.transform.skin_resize(override,
507 value=(corrective_thickness, corrective_thickness, corrective_thickness),
508 constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
509 mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
510 proportional_size=1
513 # making hands even more pretty
514 bpy.ops.mesh.select_all(action='DESELECT')
515 hands_idx = idx_store.get_hands_pretty_idx()
517 select_vertices(shape_object, hands_idx)
518 # change the thickness to make hands look less blocky and more sexy
519 corrective_thickness = 0.7
520 bpy.ops.transform.skin_resize(override,
521 value=(corrective_thickness, corrective_thickness, corrective_thickness),
522 constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
523 mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
524 proportional_size=1
526 bpy.ops.mesh.select_all(action='DESELECT')
528 # todo optionally take root from rig's hip tail or head depending on scenario
530 root_idx = idx_store.get_root_idx()
532 if selected_bones_num == total_bones_num:
533 root_idx = [0]
535 if len(root_idx) > 0:
536 select_vertices(shape_object, root_idx)
537 bpy.ops.object.skin_root_mark(override)
538 # skin in edit mode
539 # add Subsurf modifier
540 shape_object.modifiers.new("Subsurf", 'SUBSURF')
541 shape_object.modifiers["Subsurf"].levels = sub_level
542 shape_object.modifiers["Subsurf"].render_levels = sub_level
544 bpy.ops.object.mode_set(mode='OBJECT')
546 # object mode apply all modifiers
547 if apply_mod:
548 bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Skin")
549 bpy.ops.object.modifier_apply(override, apply_as='DATA', modifier="Subsurf")
551 return {'FINISHED'}
554 def main(context):
556 This script will create a custome shape
559 # ### Check if selection is OK ###
560 if len(context.selected_pose_bones) == 0 or \
561 len(context.selected_objects) == 0 or \
562 context.selected_objects[0].type != 'ARMATURE':
563 return {'CANCELLED'}, "No bone selected or the Armature is hidden"
565 scn = bpy.context.scene
566 sknfy = scn.skinify
568 # initialize the mesh object
569 mesh_name = context.selected_objects[0].name + "_mesh"
570 obj_name = context.selected_objects[0].name + "_object"
571 armature_object = context.object
573 origin = context.object.location
574 bone_selection = context.selected_pose_bones
575 oldLocation = None
576 oldRotation = None
577 oldScale = None
578 armature_object = scn.objects.active
579 armature_object.select = True
581 old_pose_pos = armature_object.data.pose_position
582 bpy.ops.object.mode_set(mode='OBJECT')
583 oldLocation = Vector(armature_object.location)
584 oldRotation = Euler(armature_object.rotation_euler)
585 oldScale = Vector(armature_object.scale)
587 bpy.ops.object.rotation_clear(clear_delta=False)
588 bpy.ops.object.location_clear(clear_delta=False)
589 bpy.ops.object.scale_clear(clear_delta=False)
590 if sknfy.apply_mod and sknfy.parent_armature:
591 armature_object.data.pose_position = 'REST'
593 scale = bpy.context.object.scale
594 size = bpy.context.object.dimensions[2]
596 bpy.ops.object.mode_set(mode='OBJECT')
597 bpy.ops.object.select_all(action='DESELECT')
599 bpy.ops.object.add(type='MESH', enter_editmode=False, location=origin)
601 # get the mesh object
602 ob = scn.objects.active
603 ob.name = obj_name
604 me = ob.data
605 me.name = mesh_name
607 # this way we fit mesh and bvh with armature modifier correctly
609 alternate_scale_idx_list, rig_type = generate_edges(
610 me, ob, bone_selection, scale, sknfy.connect_mesh,
611 sknfy.connect_parents, sknfy.head_ornaments,
612 sknfy.generate_all
615 generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level,
616 sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
617 sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection)
619 # parent mesh with armature only if modifiers are applied
620 if sknfy.apply_mod and sknfy.parent_armature:
621 bpy.ops.object.mode_set(mode='OBJECT')
622 bpy.ops.object.select_all(action='DESELECT')
623 ob.select = True
624 armature_object.select = True
625 scn.objects.active = armature_object
627 bpy.ops.object.parent_set(type='ARMATURE_AUTO')
628 armature_object.data.pose_position = old_pose_pos
629 armature_object.select = False
630 else:
631 bpy.ops.object.mode_set(mode='OBJECT')
632 ob.location = oldLocation
633 ob.rotation_euler = oldRotation
634 ob.scale = oldScale
635 ob.select = False
636 armature_object.select = True
637 scn.objects.active = armature_object
639 armature_object.location = oldLocation
640 armature_object.rotation_euler = oldRotation
641 armature_object.scale = oldScale
642 bpy.ops.object.mode_set(mode='POSE')
644 return {'FINISHED'}, me
647 class BONE_OT_custom_shape(Operator):
648 bl_idname = "object.skinify_rig"
649 bl_label = "Skinify Rig"
650 bl_description = "Creates a mesh object at the selected bones positions"
651 bl_options = {'UNDO', 'INTERNAL'}
653 @classmethod
654 def poll(cls, context):
655 return context.active_object is not None
657 def execute(self, context):
658 Mesh = main(context)
659 if Mesh[0] == {'CANCELLED'}:
660 self.report({'WARNING'}, Mesh[1])
661 return {'CANCELLED'}
662 else:
663 self.report({'INFO'}, Mesh[1].name + " has been created")
665 return {'FINISHED'}
668 class BONE_PT_custom_shape(Panel):
669 bl_space_type = 'PROPERTIES'
670 bl_region_type = 'WINDOW'
671 bl_context = "bone"
672 bl_label = "Skinify Rig"
674 @classmethod
675 def poll(cls, context):
676 ob = context.object
677 return ob and ob.mode == 'POSE' and context.bone
679 def draw(self, context):
680 layout = self.layout
681 scn = context.scene.skinify
683 row = layout.row()
684 row.operator("object.skinify_rig", text="Add Shape", icon='BONE_DATA')
686 split = layout.split(percentage=0.3)
687 split.label("Thickness:")
688 split.prop(scn, "thickness", text="Body", icon='MOD_SKIN')
689 split.prop(scn, "finger_thickness", text="Fingers", icon='HAND')
691 split = layout.split(percentage=0.3)
692 split.label("Mesh Density:")
693 split.prop(scn, "sub_level", icon='MESH_ICOSPHERE')
695 row = layout.row()
696 row.prop(scn, "connect_mesh", icon='EDITMODE_HLT')
697 row.prop(scn, "connect_parents", icon='CONSTRAINT_BONE')
698 row = layout.row()
699 row.prop(scn, "head_ornaments", icon='GROUP_BONE')
700 row.prop(scn, "generate_all", icon='GROUP_BONE')
701 row = layout.row()
702 row.prop(scn, "apply_mod", icon='FILE_TICK')
703 if scn.apply_mod:
704 row = layout.row()
705 row.prop(scn, "parent_armature", icon='POSE_HLT')
708 # define the scene properties in a group - call them with context.scene.skinify
709 class Skinify_Properties(PropertyGroup):
710 sub_level = IntProperty(
711 name="Sub level",
712 min=0, max=4,
713 default=1,
714 description="Mesh density"
716 thickness = FloatProperty(
717 name="Thickness",
718 min=0.01,
719 default=0.8,
720 description="Adjust shape thickness"
722 finger_thickness = FloatProperty(
723 name="Finger Thickness",
724 min=0.01, max=1.0,
725 default=0.25,
726 description="Adjust finger thickness relative to body"
728 connect_mesh = BoolProperty(
729 name="Solid Shape",
730 default=False,
731 description="Makes solid shape from bone chains"
733 connect_parents = BoolProperty(
734 name="Fill Gaps",
735 default=False,
736 description="Fills the gaps between parented bones"
738 generate_all = BoolProperty(
739 name="All Shapes",
740 default=False,
741 description="Generates shapes from all bones"
743 head_ornaments = BoolProperty(
744 name="Head Ornaments",
745 default=False,
746 description="Includes head ornaments"
748 apply_mod = BoolProperty(
749 name="Apply Modifiers",
750 default=True,
751 description="Applies Modifiers to mesh"
753 parent_armature = BoolProperty(
754 name="Parent Armature",
755 default=True,
756 description="Applies mesh to Armature"
760 # startup defaults
762 @persistent
763 def startup_init(dummy):
764 init_props()
767 def register():
768 bpy.utils.register_class(BONE_OT_custom_shape)
769 bpy.utils.register_class(BONE_PT_custom_shape)
770 bpy.utils.register_class(Skinify_Properties)
772 bpy.types.Scene.skinify = PointerProperty(
773 type=Skinify_Properties
775 # startup defaults
776 bpy.app.handlers.load_post.append(startup_init)
779 def unregister():
780 bpy.utils.unregister_class(BONE_OT_custom_shape)
781 bpy.utils.unregister_class(BONE_PT_custom_shape)
782 bpy.utils.unregister_class(Skinify_Properties)
784 # cleanup the handler
785 bpy.app.handlers.load_post.remove(startup_init)
787 del bpy.types.Scene.skinify
790 if __name__ == "__main__":
791 register()