forked from addam/Export-Paper-Model-from-Blender
-
Notifications
You must be signed in to change notification settings - Fork 0
/
object_convert_to_armature.py
204 lines (182 loc) · 6.74 KB
/
object_convert_to_armature.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# -*- coding: utf-8 -*-
# mesh_convert_to_armature.py
#
# Generate an armature with a single bone controlling each face.
# The mesh must be a tree-like structure for this to make sense.
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
bl_info = {
"name": "Convert Mesh to Armature",
"author": "Addam Dominec",
"version": (0,3),
"blender": (2, 6, 3),
"api": 49166,
"location": "Object > Convert to Armature",
"warning": "",
"description": "Generate an armature with a single bone controlling each face",
"category": "Object",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Object/Convert_to_Armature",
"tracker_url": ""}
import bpy
from itertools import repeat
from functools import reduce
from mathutils import Vector
from math import asin, pi
def pairs(sequence):
return zip(sequence, sequence[1:]+sequence[:1])
faces_by_edge=dict();
edge_by_verts=dict();
def get_edge(verts):
global edge_by_verts
va, vb = verts
if va > vb:
va, vb = vb, va
if (va, vb) not in edge_by_verts:
print("Verts %i %i not in list" % (va, vb))
return edge_by_verts.get((va, vb), None)
def get_edges(face, exclude=None):
edges = map(get_edge, pairs(face.vertices))
return filter(lambda edge: edge and edge is not exclude, edges)
def get_faces(edge, exclude=None):
global faces_by_edge
if exclude:
return [face for face in faces_by_edge.get(edge, ()) if face != exclude]
else:
return faces_by_edge.get(edge, [])
def vertex_avg(mesh, indices):
return reduce(Vector.__add__, [mesh.vertices[i].co for i in indices]) / len(indices)
def add_armature(name):
ar = bpy.data.armatures.new(name)
ob = bpy.data.objects.new(name, ar)
bpy.context.scene.objects.link(ob)
return ob
def add_bone(name, armature, head, tail):
bo = armature.edit_bones.new(name)
bo.head = head
bo.tail = tail
return bo
def main(context):
global faces_by_edge
global edge_by_verts
faces_by_edge.clear()
edge_by_verts.clear()
visited_faces = set()
loops = 0
bpy.ops.object.mode_set()
mesh_object = context.active_object
mesh = mesh_object.data
#Create a nicer structure above the mesh data
for edge in mesh.edges:
if not edge.use_seam:
va, vb = edge.vertices
if va > vb:
va, vb = vb, va
edge_by_verts[(va, vb)] = edge
for face in mesh.polygons:
for edge in get_edges(face):
if edge not in faces_by_edge:
faces_by_edge[edge]=list()
faces_by_edge[edge].append(face)
#Create an armature
armature_object = add_armature("Arma")
armature_object.matrix_local = mesh_object.matrix_local
armature = armature_object.data
modifier = mesh_object.modifiers.new("Folding Armature", 'ARMATURE')
modifier.use_bone_envelopes = False
modifier.use_vertex_groups = True
modifier.object = armature_object
#Generate the bones
context.scene.objects.active = armature_object
bpy.ops.object.mode_set(mode='EDIT')
angles = dict() #bone -> folding angle
queue = [(None, mesh.polygons[mesh.polygons.active], None, None)] #Edge, face and a bone connecting these two and parent in the tree
while queue:
edge, face, parent_bone, parent_face = queue.pop()
if face in visited_faces:
loops += 1 #We went to the same face from two different directions
continue
visited_faces.add(face)
vgroup = mesh_object.vertex_groups.new("Face_%i" % face.index) #Create a vertex group for this face
vgroup.add(face.vertices, 1, 'ADD')
if edge:
edge_vector = mesh.vertices[edge.vertices[0]].co - mesh.vertices[edge.vertices[1]].co
head = vertex_avg(mesh, edge.vertices)
tail = vertex_avg(mesh, face.vertices)
tail -= (tail-head).project(edge_vector)
else: #root bone
tail = vertex_avg(mesh, face.vertices)
head = tail.copy()
head.z -= 1
bone = add_bone(vgroup.name, armature, head, tail)
bone.align_roll(face.normal)
bone.parent = parent_bone
if edge: #all except the root bone
depth = bone.vector.dot(parent_face.normal)/parent_face.normal.length
clamped = min(1, max(-1, depth/bone.vector.length))
try:
angle = asin(clamped)
except ValueError:
print("Depth: {}, |vector|: {}".format(depth, bone.vector.length))
angle = 0
if face.normal.dot(parent_face.normal) < 0:
angle = pi - angle
angles[bone.name] = angle
for next_edge in get_edges(face, exclude=edge):
queue += zip(repeat(next_edge), get_faces(next_edge, exclude=face), repeat(bone), repeat(face))
bpy.ops.object.mode_set()
#Set transform locks (must be done in object mode)
pose_bone = armature_object.pose.bones[0]
pose_bone.lock_scale[0:3] = True, True, True
for pose_bone in armature_object.pose.bones[1:]:
pose_bone.lock_rotation[0:3] = False, True, True
pose_bone.lock_location[0:3] = True, True, True
pose_bone.lock_scale[0:3] = True, True, True
pose_bone.lock_ik_x, pose_bone.lock_ik_y, pose_bone.lock_ik_z = False, True, True
pose_bone.rotation_mode = 'XYZ'
pose_bone.rotation_euler.x = -angles[pose_bone.name]
context.scene.objects.active = mesh_object
if loops:
raise ValueError(loops)
class OBJECT_OT_convert_to_armature(bpy.types.Operator):
'''Generate an armature with a single bone controlling each face. The mesh must be a tree-like structure for this to make sense. Active face is used for main bone.'''
bl_idname = "object.convert_to_armature"
bl_label = "Convert to Armature"
bl_description = "Generate an armature from the active mesh"
@classmethod
def poll(cls, context):
return context.active_object.type == 'MESH'
def execute(self, context):
try:
main(context)
except ValueError as E:
if isinstance(E.args[0], int):
self.report({'ERROR', 'ERROR_INVALID_INPUT'}, "There is a loop of connected faces. Use Export Paper Model add-on and EdgeSplit modifier to eliminate them. Otherwise, the armature may be unusable.")
else:
raise
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(OBJECT_OT_convert_to_armature.bl_idname, text="Convert to Armature")
def register():
bpy.utils.register_module(__name__)
bpy.types.VIEW3D_MT_object.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.VIEW3D_MT_object.remove(menu_func)
if __name__ == "__main__":
register()