Skip to content
Snippets Groups Projects
Commit 0ffb0c5d authored by meta-androcto's avatar meta-androcto
Browse files

object skinify, platform compatibility, Fix T52536

parent 426a14a6
No related branches found
No related tags found
No related merge requests found
......@@ -19,7 +19,7 @@
bl_info = {
"name": "Skinify Rig",
"author": "Albert Makac (karab44)",
"version": (0, 9, 2),
"version": (0, 9, 9),
"blender": (2, 7, 9),
"location": "Properties > Bone > Skinify Rig (visible on pose mode only)",
"description": "Creates a mesh object from selected bones",
......@@ -46,7 +46,7 @@ from mathutils import (
)
from bpy.app.handlers import persistent
from enum import Enum
import sys
# can the armature data properties group_prop and row be fetched directly from the rigify script?
horse_data = \
......@@ -105,47 +105,91 @@ rigify_data = horse_data, shark_data, bird_data, cat_data, biped_data, human_dat
wolf_data, quadruped_data, human_legacy_data, pitchipoy_data
# Skin.rig_type.ENUM
# Skin.junction_dict['bname'].data[0] idx, data[1] idx + 1, data[2] thickness
# NOTE each fragment contains section information about adequate bone
# junctions idx and idx + 1 and these vertices' ids share common thickness
class Skin(object):
class Rig_type(Enum):
HORSE = 0
SHARK = 1
BIRD = 2
CAT = 3
BIPED = 4
HUMAN = 5
WOLF = 6
QUAD = 7
LEGACY = 8
PITCHIPOY = 9
OTHER = 10
def __init__(self, rig_type):
class Rig_type(Enum):
HORSE = 0
SHARK = 1
BIRD = 2
CAT = 3
BIPED = 4
HUMAN = 5
WOLF = 6
QUAD = 7
LEGACY = 8
PITCHIPOY = 9
OTHER = 10
rig_type = Rig_type.OTHER
#TODO this class contains temporary workaround needed due to https://developer.blender.org/T52536 but in general it's good and elegant solution
# remove sys.platform idx variations after issue is solved
class Idx_Store(object):
def __init__(self, rig_type, os_type):
self.rig_type = rig_type
self.junctions_dict = dict()
def fragment_create(self, bname, idx=None, thickness=0.0):
data = []
data.insert(0, idx)
if idx is not None:
data.insert(1, idx + 1)
else:
data.insert(1, None)
self.junctions_dict[bname] = data
self.junctions_dict[bname].append(thickness)
# for the sake of code clarity
def fragment_update(self, bname, idx=None, thickness=0.0):
self.fragment_create(bname, idx, thickness)
rig_type = Skin.Rig_type.OTHER
self.hand_r_merge = []
self.hand_l_merge = []
self.hands_pretty = []
self.root = []
if not self.rig_type == Rig_type.LEGACY and not self.rig_type == Rig_type.HUMAN and not self.rig_type == Rig_type.PITCHIPOY:
return
if self.rig_type == Rig_type.LEGACY:
if os_type == 'win32':
self.hand_l_merge = [7, 8, 13, 17, 22, 27]
self.hand_r_merge = [30, 35, 39, 44, 45, 50]
self.hands_pretty = [6, 29]
self.root = [59]
print("WIN")
if os_type == 'linux':
self.hand_l_merge = [8, 9, 14, 18, 23, 28]
self.hand_r_merge = [31, 32, 37, 41, 46, 51]
self.hands_pretty = [7, 30]
self.root = [59]
print("LIN")
if os_type == 'darwin':
self.hand_l_merge = [7, 12, 16, 17, 22, 27]
self.hand_r_merge = [30, 35, 39, 40, 45, 50]
self.hands_pretty = [6, 29]
self.root = [59]
print("DARWIN")
if self.rig_type == Rig_type.HUMAN or self.rig_type == Rig_type.PITCHIPOY:
if os_type == 'win32':
self.hand_l_merge = [9, 14, 18, 23, 24, 29]
self.hand_r_merge = [32, 37, 41, 46, 51, 52]
self.hands_pretty = [8, 31]
self.root = [56]
print("WIN")
if os_type == 'linux':
self.hand_l_merge = [10, 11, 16, 20, 25, 30]
self.hand_r_merge = [33, 34, 39, 43, 48, 53]
self.hands_pretty = [9, 32]
self.root = [56]
print("LIN")
if os_type == 'darwin':
self.hand_l_merge = [10, 15, 19, 29, 24, 30]
self.hand_r_merge = [33, 38, 42, 47, 48, 53]
self.hands_pretty = [9, 32]
self.root = [56]
print("DARWIN")
def get_all_idx(self):
return self.hand_l_merge, self.hand_r_merge, self.hands_pretty, self.root
def get_hand_l_merge_idx(self):
return self.hand_l_merge
def get_hand_r_merge_idx(self):
return self.hand_r_merge
def get_hands_pretty_idx(self):
return self.hands_pretty
def get_root_idx(self):
return self.root
# initialize properties
......@@ -165,9 +209,26 @@ def init_props():
scn.sub_level = 1
# selects vertices
def select_vertices(mesh_obj, idx):
bpy.context.scene.objects.active = mesh_obj
mode = mesh_obj.mode
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for i in idx:
mesh_obj.data.vertices[i].select = True
selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
bpy.ops.object.mode_set(mode=mode)
return selectedVerts
def identify_rig():
if 'rigify_layers' not in bpy.context.object.data:
return Skin.Rig_type.OTHER # non recognized
return Rig_type.OTHER # non recognized
LEGACY_LAYERS_SIZE = 28
layers = bpy.context.object.data['rigify_layers']
......@@ -184,33 +245,35 @@ def identify_rig():
elif (props['row'] != rig[index][0]) or (props['group_prop'] != rig[index][1]):
break
# SUCCESS if reaches the end
# SUCCESS if reach the end
if index == len(layers) - 1:
return Skin.Rig_type(type)
return Rig_type(type)
index = index + 1
return Skin.Rig_type.OTHER
return Rig_type.OTHER
# prepares customizable ignore and thickness lists
# edit these lists to suits your taste
def prepare_lists(rig_type, finger_thickness):
# EXAMPLE IGNORE LIST
# detect the head, face, hands, breast, heels or other exceptionary bones for exclusion or customization
def prepare_ignore_list(rig_type, bones):
# detect the head, face, hands, breast, heels or other exceptionary bones to exclusion or customization
common_ignore_list = ['eye', 'heel', 'breast', 'root']
# edit these lists to suits your taste
horse_ignore_list = ['chest', 'belly', 'pelvis', 'jaw', 'nose', 'skull', 'ear.']
shark_ignore_list = ['jaw']
bird_ignore_list = [
'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue', 'beak'
]
cat_ignore_list = [
'face', 'belly', 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
'face', 'belly' 'pelvis.C', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
]
biped_ignore_list = ['pelvis']
human_ignore_list = [
'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
......@@ -224,119 +287,84 @@ def prepare_lists(rig_type, finger_thickness):
'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
]
rigify_legacy_ignore_list = []
pitchipoy_ignore_list = [
'face', 'pelvis', 'nose', 'lip', 'jaw', 'chin', 'ear.', 'brow',
'lid', 'forehead', 'temple', 'cheek', 'teeth', 'tongue'
]
other_ignore_list = []
# EXAMPLE THICKNESS
# feel free to modify and customize the list by adding elements followed by comma
# common_thickness_dict = {"hand": common_finger_thickness, "head": common_head_thickness}
common_finger_thickness = finger_thickness
common_thickness_dict = {"hand": common_finger_thickness}
horse_thickness_dict = {}
shark_thickness_dict = {}
bird_thickness_dict = {}
cat_thickness_dict = {}
face_thickness = 0.20
biped_thickness_dict = {}
human_thickness_dict = {"face": face_thickness}
wolf_thickness_dict = {}
quad_thickness_dict = {}
rigify_legacy_thickness_dict = {}
pitchipoy_thickness_dict = {"face": face_thickness}
other_thickness_dict = {}
# combine lists depending on rig type
ignore_list = common_ignore_list
thickness_dict = common_thickness_dict
if rig_type == Skin.Rig_type.HORSE:
if rig_type == Rig_type.HORSE:
ignore_list = ignore_list + horse_ignore_list
thickness_dict.update(horse_thickness_dict)
print("RIDER OF THE APOCALYPSE")
elif rig_type == Skin.Rig_type.SHARK:
elif rig_type == Rig_type.SHARK:
ignore_list = ignore_list + shark_ignore_list
thickness_dict.update(shark_thickness_dict)
print("DEADLY JAWS")
elif rig_type == Skin.Rig_type.BIRD:
elif rig_type == Rig_type.BIRD:
ignore_list = ignore_list + bird_ignore_list
thickness_dict.update(bird_thickness_dict)
print("WINGS OF LIBERTY")
elif rig_type == Skin.Rig_type.CAT:
elif rig_type == Rig_type.CAT:
ignore_list = ignore_list + cat_ignore_list
thickness_dict.update(cat_thickness_dict)
print("MEOW")
elif rig_type == Skin.Rig_type.BIPED:
elif rig_type == Rig_type.BIPED:
ignore_list = ignore_list + biped_ignore_list
thickness_dict.update(biped_thickness_dict)
print("HUMANOID")
elif rig_type == Skin.Rig_type.HUMAN:
elif rig_type == Rig_type.HUMAN:
ignore_list = ignore_list + human_ignore_list
thickness_dict.update(human_thickness_dict)
print("JUST A HUMAN AFTER ALL")
elif rig_type == Skin.Rig_type.WOLF:
elif rig_type == Rig_type.WOLF:
ignore_list = ignore_list + wolf_ignore_list
thickness_dict.update(wolf_thickness_dict)
print("WHITE FANG")
elif rig_type == Skin.Rig_type.QUAD:
elif rig_type == Rig_type.QUAD:
ignore_list = ignore_list + quad_ignore_list
thickness_dict.update(quad_thickness_dict)
print("MYSTERIOUS CREATURE")
elif rig_type == Skin.Rig_type.LEGACY:
elif rig_type == Rig_type.LEGACY:
ignore_list = ignore_list + rigify_legacy_ignore_list
thickness_dict.update(rigify_legacy_thickness_dict)
print("LEGACY RIGIFY")
elif rig_type == Skin.Rig_type.PITCHIPOY:
elif rig_type == Rig_type.PITCHIPOY:
ignore_list = ignore_list + pitchipoy_ignore_list
thickness_dict.update(pitchipoy_thickness_dict)
print("PITCHIPOY")
elif rig_type == Skin.Rig_type.OTHER:
elif rig_type == Rig_type.OTHER:
ignore_list = ignore_list + other_ignore_list
thickness_dict.update(other_thickness_dict)
print("rig non recognized...")
return ignore_list, thickness_dict
return ignore_list
# generates edges from vertices used by skin modifier
def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect_parents=False,
head_ornaments=False, generate_all=False, thickness=0.0, finger_thickness=0.0):
head_ornaments=False, generate_all=False):
"""
This function adds vertices for all bones' heads and tails
This function adds vertices for all heads and tails
"""
# scene preferences
alternate_scale_list = []
me = mesh
verts = []
edges = []
idx = 0
alternate_scale_idx_list = list()
rig_type = identify_rig()
skin = Skin(rig_type)
# prepare the list
ignore_list, thickness_dict = prepare_lists(skin.rig_type, finger_thickness)
# create default junctions for all bones
for b in bones:
# set default thickness to all new junctions
skin.fragment_create(bname=b.name, idx=None, thickness=thickness)
ignore_list = prepare_ignore_list(rig_type, bones)
# edge generator loop
for b in bones:
# look for rig's specific bones and their childs and set individual thickness
for bname, thick in thickness_dict.items():
if bname.lower() in b.name.lower():
skin.fragment_update(bname=b.name, idx=None, thickness=thick)
for c in b.children_recursive:
# update junctions with specific thickness
skin.fragment_update(bname=c.name, idx=None, thickness=thick)
# look for rig's hands and their childs
if 'hand' in b.name.lower():
# prepare the list
for c in b.children_recursive:
alternate_scale_list.append(c.name)
found = False
for i in ignore_list:
if i.lower() in b.name.lower():
if i in b.name.lower():
found = True
break
......@@ -351,10 +379,10 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect
if head_ornaments is False:
if b.parent is not None:
if 'head' in b.parent.name.lower() and not rig_type == Skin.Rig_type.HUMAN:
if 'head' in b.parent.name.lower() and not rig_type == Rig_type.HUMAN:
continue
if 'face' in b.parent.name.lower() and rig_type == Skin.Rig_type.HUMAN:
if 'face' in b.parent.name.lower() and rig_type == Rig_type.HUMAN:
continue
if connect_parents:
......@@ -378,20 +406,17 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect
edges.append([idx, idx + 1])
# also make list of edges made of gaps between the bones
for bname, data in skin.junctions_dict.items():
if b.name == bname:
skin.fragment_update(b.name, idx, data[2])
break
# create new segment for new connections
skin.fragment_create(b.name + b.parent.name, idx, data[2])
for a in alternate_scale_list:
if b.name == a:
alternate_scale_idx_list.append(idx)
alternate_scale_idx_list.append(idx + 1)
idx = idx + 2
# for bvh free floating hips and hips correction for rigify and pitchipoy
if ((generate_all is False and 'hip' in b.name.lower()) or
(generate_all is False and (b.name == 'hips' and rig_type == Skin.Rig_type.LEGACY) or
(b.name == 'spine' and rig_type == Skin.Rig_type.PITCHIPOY) or (b.name == 'spine' and
rig_type == Skin.Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Skin.Rig_type.BIPED))):
(generate_all is False and (b.name == 'hips' and rig_type == Rig_type.LEGACY) or
(b.name == 'spine' and rig_type == Rig_type.PITCHIPOY) or (b.name == 'spine' and
rig_type == Rig_type.HUMAN) or (b.name == 'spine' and rig_type == Rig_type.BIPED))):
continue
vert1 = b.head
......@@ -401,10 +426,10 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect
edges.append([idx, idx + 1])
# insert idx to junctions and update
for bname, data in skin.junctions_dict.items():
if b.name == bname:
skin.fragment_update(b.name, idx, data[2])
for a in alternate_scale_list:
if b.name == a:
alternate_scale_idx_list.append(idx)
alternate_scale_idx_list.append(idx + 1)
idx = idx + 2
......@@ -412,31 +437,16 @@ def generate_edges(mesh, shape_object, bones, scale, connect_mesh=False, connect
me.from_pydata(verts, edges, [])
# Update mesh with new data
me.update()
# set object scale exact as armature's scale
shape_object.scale = scale
return skin
# selects vertices
def select_vertices(mesh_obj, idx):
bpy.context.scene.objects.active = mesh_obj
mode = mesh_obj.mode
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
for i in idx:
mesh_obj.data.vertices[i].select = True
selectedVerts = [v.index for v in mesh_obj.data.vertices if v.select]
bpy.ops.object.mode_set(mode=mode)
return selectedVerts
return alternate_scale_idx_list, rig_type
def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_parents=False,
generate_all=False, apply_mod=True, skin=None, bones=[]):
def generate_mesh(shape_object, size, thickness=0.8, finger_thickness=0.25, sub_level=1,
connect_mesh=False, connect_parents=False, generate_all=False, apply_mod=True,
alternate_scale_idx_list=[], rig_type=0, bones=[]):
"""
This function adds modifiers for generated edges
"""
......@@ -464,30 +474,31 @@ def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_p
override['modifier'] = bpy.context.object.modifiers
break
# calculate optimal, normalized thickness for each segment
# calculate optimal thickness for defaults
bpy.ops.object.skin_root_mark(override)
# select finger vertices and calculate optimal thickness for fingers to fix proportions
# by default set fingers thickness to 25 percent of body thickness
# make loose hands only for better topology
if len(skin.junctions_dict.keys()) > 0:
for bname, data in skin.junctions_dict.items():
if data[0] is not None:
fragment_idx = list()
fragment_idx.append(data[0])
fragment_idx.append(data[1])
thickness = data[2]
select_vertices(shape_object, fragment_idx)
bpy.ops.transform.skin_resize(override,
value=(1 * thickness * (size / 10), 1 * thickness * (size / 10),
1 * thickness * (size / 10)), constraint_axis=(False, False, False),
constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
proportional_edit_falloff='SMOOTH', proportional_size=1
)
bpy.ops.transform.skin_resize(override,
value=(1 * thickness * (size / 10), 1 * thickness * (size / 10), 1 * thickness * (size / 10)),
constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
proportional_size=1
)
shape_object.modifiers["Skin"].use_smooth_shade = True
shape_object.modifiers["Skin"].use_x_symmetry = True
# select finger vertices and calculate optimal thickness for fingers to fix proportions
if len(alternate_scale_idx_list) > 0:
select_vertices(shape_object, alternate_scale_idx_list)
bpy.ops.object.skin_loose_mark_clear(override, action='MARK')
# by default set fingers thickness to 25 percent of body thickness
bpy.ops.transform.skin_resize(override,
value=(finger_thickness, finger_thickness, finger_thickness),
constraint_axis=(False, False, False), constraint_orientation='GLOBAL',
mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH',
proportional_size=1
)
# make loose hands only for better topology
# bpy.ops.mesh.select_all(action='DESELECT')
if connect_mesh:
......@@ -495,23 +506,18 @@ def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_p
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles()
idx_store = Idx_Store(rig_type, sys.platform)
# fix rigify and pitchipoy hands topology
if connect_mesh and connect_parents and generate_all is False and \
(skin.rig_type == Skin.Rig_type.LEGACY or skin.rig_type == Skin.Rig_type.PITCHIPOY or
skin.rig_type == Skin.Rig_type.HUMAN) and selected_bones_num == total_bones_num:
(rig_type == Rig_type.LEGACY or rig_type == Rig_type.PITCHIPOY or rig_type == Rig_type.HUMAN) and \
selected_bones_num == total_bones_num:
# thickness will set palm vertex for both hands look pretty
corrective_thickness = 2.5
# left hand verts
merge_idx = []
if skin.rig_type == Skin.Rig_type.LEGACY:
merge_idx = [8, 9, 14, 18, 23, 28]
elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN:
merge_idx = [10, 11, 16, 20, 25, 30]
merge_idx = idx_store.get_hand_l_merge_idx()
select_vertices(shape_object, merge_idx)
bpy.ops.mesh.merge(type='CENTER')
bpy.ops.transform.skin_resize(override,
......@@ -523,11 +529,7 @@ def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_p
bpy.ops.mesh.select_all(action='DESELECT')
# right hand verts
if skin.rig_type == Skin.Rig_type.LEGACY:
merge_idx = [31, 32, 37, 41, 46, 51]
elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN:
merge_idx = [33, 34, 39, 43, 48, 53]
merge_idx = idx_store.get_hand_r_merge_idx()
select_vertices(shape_object, merge_idx)
bpy.ops.mesh.merge(type='CENTER')
......@@ -540,15 +542,8 @@ def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_p
# making hands even more pretty
bpy.ops.mesh.select_all(action='DESELECT')
hands_idx = [] # left and right hand vertices
if skin.rig_type == Skin.Rig_type.LEGACY:
# hands_idx = [8, 33] # L and R
hands_idx = [7, 30]
elif skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN:
# hands_idx = [10, 35] # L and R
hands_idx = [9, 32]
hands_idx = idx_store.get_hands_pretty_idx()
select_vertices(shape_object, hands_idx)
# change the thickness to make hands look less blocky and more sexy
corrective_thickness = 0.7
......@@ -562,13 +557,9 @@ def generate_mesh(shape_object, size, sub_level=1, connect_mesh=False, connect_p
# todo optionally take root from rig's hip tail or head depending on scenario
root_idx = []
if skin.rig_type == Skin.Rig_type.LEGACY and selected_bones_num == total_bones_num:
root_idx = [59]
elif (skin.rig_type == Skin.Rig_type.PITCHIPOY or skin.rig_type == Skin.Rig_type.HUMAN) and \
selected_bones_num == total_bones_num:
root_idx = [56]
elif selected_bones_num == total_bones_num:
root_idx = idx_store.get_root_idx()
if selected_bones_num == total_bones_num:
root_idx = [0]
if len(root_idx) > 0:
......@@ -644,15 +635,16 @@ def main(context):
me.name = mesh_name
# this way we fit mesh and bvh with armature modifier correctly
skin = generate_edges(
me, ob, bone_selection, scale, sknfy.connect_mesh,
sknfy.connect_parents, sknfy.head_ornaments,
sknfy.generate_all, sknfy.thickness, sknfy.finger_thickness
)
generate_mesh(ob, size, sknfy.sub_level,
alternate_scale_idx_list, rig_type = generate_edges(
me, ob, bone_selection, scale, sknfy.connect_mesh,
sknfy.connect_parents, sknfy.head_ornaments,
sknfy.generate_all
)
generate_mesh(ob, size, sknfy.thickness, sknfy.finger_thickness, sknfy.sub_level,
sknfy.connect_mesh, sknfy.connect_parents, sknfy.generate_all,
sknfy.apply_mod, skin, bone_selection)
sknfy.apply_mod, alternate_scale_idx_list, rig_type, bone_selection)
# parent mesh with armature only if modifiers are applied
if sknfy.apply_mod and sknfy.parent_armature:
......@@ -682,11 +674,7 @@ def main(context):
return {'FINISHED'}, me
# Note: setting bl_options to UNDO, INTERNAL since access through
# the Search Menu or Repeat History will cause a context loss and crash
# See a similar problem solution in commit b947810291b1
class BONE_OT_custom_shape(Operator):
class BONE_OT_custom_shape(Operator):
bl_idname = "object.skinify_rig"
bl_label = "Skinify Rig"
bl_description = "Creates a mesh object at the selected bones positions"
......@@ -830,4 +818,4 @@ def unregister():
if __name__ == "__main__":
register()
register()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment