1 # SPDX-FileCopyrightText: 2017-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 "author": "Albert Makac (karab44)",
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",
18 from bpy
.props
import (
24 from bpy
.types
import (
29 from mathutils
import (
33 from bpy
.app
.handlers
import persistent
36 # can the armature data properties group_prop and row be fetched directly from the rigify script?
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),
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),
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),
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),
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),
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),
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),
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),
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),
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
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
= []
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
:
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]
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]
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
):
151 # initialize properties
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
162 scn
.finger_thickness
= 0.25
164 scn
.parent_armature
= True
169 def select_vertices(mesh_obj
, idx
):
170 bpy
.context
.view_layer
.objects
.active
= mesh_obj
172 bpy
.ops
.object.mode_set(mode
='EDIT')
173 bpy
.ops
.mesh
.select_all(action
='DESELECT')
174 bpy
.ops
.object.mode_set(mode
='OBJECT')
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
)
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
):
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:
201 elif (props
['row'] != rig
[index
][0]) or (props
['group_prop'] != rig
[index
][1]):
204 # SUCCESS if reach the end
205 if index
== len(layers
) - 1:
206 return Rig_type(type)
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']
224 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
225 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
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'
238 'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
239 'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
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
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
268 elif rig_type
== Rig_type
.BIPED
:
269 ignore_list
= ignore_list
+ biped_ignore_list
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
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
286 elif rig_type
== Rig_type
.OTHER
:
287 ignore_list
= ignore_list
+ other_ignore_list
288 print("rig non recognized...")
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
301 alternate_scale_list
= []
307 alternate_scale_idx_list
= list()
309 rig_type
= identify_rig()
310 ignore_list
= prepare_ignore_list(rig_type
, bones
)
312 # edge generator loop
314 # look for rig's hands and their childs
315 if 'hand' in b
.name
.lower():
317 for c
in b
.children_recursive
:
318 alternate_scale_list
.append(c
.name
)
322 for i
in ignore_list
:
323 if i
in b
.name
.lower():
327 if found
and generate_all
is False:
330 # fix for drawing rootbone and relationship lines
331 if 'root' in b
.name
.lower() and generate_all
is False:
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
:
341 if 'face' in b
.parent
.name
.lower() and rig_type
== Rig_type
.HUMAN
:
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:
349 if 'shoulder' in b
.name
.lower() and connect_mesh
is True:
351 # connect the upper arm directly with chest omitting shoulders
352 if 'shoulder' in b
.parent
.name
.lower() and connect_mesh
is True:
354 vert2
= b
.parent
.parent
.tail
358 vert2
= b
.parent
.tail
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
:
367 alternate_scale_idx_list
.append(idx
)
368 alternate_scale_idx_list
.append(idx
+ 1)
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
))):
383 edges
.append([idx
, idx
+ 1])
385 for a
in alternate_scale_list
:
387 alternate_scale_idx_list
.append(idx
)
388 alternate_scale_idx_list
.append(idx
+ 1)
392 # Create mesh from given verts, faces
393 me
.from_pydata(verts
, edges
, [])
394 # Update mesh with new data
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')
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',
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',
441 use_proportional_edit
=False,
443 # make loose hands only for better topology
445 # bpy.ops.mesh.select_all(action='DESELECT')
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
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',
470 use_proportional_edit
=False,
472 bpy
.ops
.mesh
.select_all(action
='DESELECT')
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',
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',
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
:
508 if len(root_idx
) > 0:
509 select_vertices(shape_object
, root_idx
)
510 bpy
.ops
.object.skin_root_mark()
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
522 bpy
.ops
.object.modifier_apply(modifier
=skin_modifier
.name
)
523 bpy
.ops
.object.modifier_apply(modifier
=subsurf_modifier
.name
)
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
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
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
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
,
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')
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)
605 bpy
.ops
.object.mode_set(mode
='OBJECT')
606 ob
.location
= oldLocation
607 ob
.rotation_euler
= oldRotation
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'}
628 def poll(cls
, context
):
629 return context
.active_object
is not None
631 def execute(self
, context
):
633 if Mesh
[0] == {'CANCELLED'}:
634 self
.report({'WARNING'}, Mesh
[1])
637 self
.report({'INFO'}, Mesh
[1].name
+ " has been created")
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'}
651 def poll(cls
, context
):
653 return ob
and ob
.mode
== 'POSE' #and context.bone
655 def draw(self
, context
):
657 scn
= context
.scene
.skinify
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')
672 row
.prop(scn
, "connect_mesh", icon
='EDITMODE_HLT')
673 row
.prop(scn
, "connect_parents", icon
='CONSTRAINT_BONE')
675 row
.prop(scn
, "head_ornaments", icon
='GROUP_BONE')
676 row
.prop(scn
, "generate_all", icon
='GROUP_BONE')
678 row
.prop(scn
, "apply_mod", icon
='FILE_TICK')
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(
690 description
="Mesh density"
692 thickness
: FloatProperty(
696 description
="Adjust shape thickness"
698 finger_thickness
: FloatProperty(
699 name
="Finger Thickness",
702 description
="Adjust finger thickness relative to body"
704 connect_mesh
: BoolProperty(
707 description
="Makes solid shape from bone chains"
709 connect_parents
: BoolProperty(
712 description
="Fills the gaps between parented bones"
714 generate_all
: BoolProperty(
717 description
="Generates shapes from all bones"
719 head_ornaments
: BoolProperty(
720 name
="Head Ornaments",
722 description
="Includes head ornaments"
724 apply_mod
: BoolProperty(
725 name
="Apply Modifiers",
727 description
="Applies Modifiers to mesh"
729 parent_armature
: BoolProperty(
730 name
="Parent Armature",
732 description
="Applies mesh to Armature"
739 def startup_init(dummy
):
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
752 bpy
.app
.handlers
.load_post
.append(startup_init
)
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__":