Newer
Older
# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Project Name: MakeHuman
# Product Home Page: http://www.makehuman.org/
# Code Home Page: http://code.google.com/p/makehuman/
# Authors: Thomas Larsson
# Script copyright (C) MakeHuman Team 2001-2011
# Coding Standards: See http://sites.google.com/site/makehumandocs/developers-guide
Abstract
MHX (MakeHuman eXchange format) importer for Blender 2.5x.
This script should be distributed with Blender.
If not, place it in the .blender/scripts/addons dir
Activate the script in the "Addons" tab (user preferences).
Access from the File > Import menu.
Alternatively, run the script in the script editor (Alt-P), and access from the File > Import menu
'name': 'Import: MakeHuman (.mhx)',
'author': 'Thomas Larsson',
Thomas Larsson
committed
"blender": (2, 6, 3),
'location': "File > Import > MakeHuman (.mhx)",
'description': 'Import files in the MakeHuman eXchange format (.mhx)',
'warning': '',
Thomas Larsson
committed
'wiki_url': 'http://sites.google.com/site/makehumandocs/blender-export-and-mhx',
'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
MAJOR_VERSION = 1
MINOR_VERSION = 12
SUB_VERSION = 0
#
#
#
import bpy
import os
import time
Thomas Larsson
committed
import math
Thomas Larsson
committed
from mathutils import Vector, Matrix
from bpy.props import *
MHX249 = False
Blender24 = False
Blender25 = True
TexDir = "~/makehuman/exports"
#
#
#
theScale = 1.0
One = 1.0/theScale
useMesh = 1
verbosity = 2
warnedTextureDir = False
warnedVersion = False
true = True
false = False
Epsilon = 1e-6
nErrors = 0
theTempDatum = None
T_EnforceVersion = 0x01
T_Clothes = 0x02
T_HardParents = 0x0
Thomas Larsson
committed
T_CrashSafe = 0x0
T_Diamond = 0x10
T_Replace = 0x20
T_Face = 0x40
T_Shape = 0x80
T_Mesh = 0x100
T_Armature = 0x200
T_Proxy = 0x400
T_Cage = 0x800
T_Opcns = 0x2000
toggle = (T_EnforceVersion + T_Mesh + T_Armature +
T_Face + T_Shape + T_Proxy + T_Clothes + T_Rigify)
def initLoadedData():
global loadedData
loadedData = {
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
'NONE' : {},
'Object' : {},
'Mesh' : {},
'Armature' : {},
'Lamp' : {},
'Camera' : {},
'Lattice' : {},
'Curve' : {},
'Text' : {},
'Material' : {},
'Image' : {},
'MaterialTextureSlot' : {},
'Texture' : {},
'Bone' : {},
'BoneGroup' : {},
'Rigify' : {},
'Action' : {},
'Group' : {},
'MeshTextureFaceLayer' : {},
'MeshColorLayer' : {},
'VertexGroup' : {},
'ShapeKey' : {},
'ParticleSystem' : {},
'ObjectConstraints' : {},
'ObjectModifiers' : {},
'MaterialSlot' : {},
}
return
def reinitGlobalData():
global loadedData
for key in [
'MeshTextureFaceLayer', 'MeshColorLayer', 'VertexGroup', 'ShapeKey',
'ParticleSystem', 'ObjectConstraints', 'ObjectModifiers', 'MaterialSlot']:
loadedData[key] = {}
return
'Object' : 'objects',
'Mesh' : 'meshes',
'Lattice' : 'lattices',
'Curve' : 'curves',
'Text' : 'texts',
'Group' : 'groups',
'Empty' : 'empties',
'Armature' : 'armatures',
'Bone' : 'bones',
'BoneGroup' : 'bone_groups',
'Pose' : 'poses',
'PoseBone' : 'pose_bones',
'Material' : 'materials',
'Texture' : 'textures',
'Image' : 'images',
'Camera' : 'cameras',
'Lamp' : 'lamps',
'World' : 'worlds',
# readMhxFile(filePath):
def readMhxFile(filePath):
global todo, nErrors, theScale, defaultScale, One, toggle, warnedVersion, theMessage
defaultScale = theScale
warnedVersion = False
initLoadedData()
theMessage = ""
fileName = os.path.expanduser(filePath)
(shortName, ext) = os.path.splitext(fileName)
if ext.lower() != ".mhx":
print("Error: Not a mhx file: " + fileName)
return
print( "Opening MHX file "+ fileName )
time1 = time.clock()
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
stack = []
tokens = []
key = "toplevel"
level = 0
nErrors = 0
comment = 0
nesting = 0
file= open(fileName, "rU")
print( "Tokenizing" )
lineNo = 0
for line in file:
# print(line)
lineSplit= line.split()
lineNo += 1
if len(lineSplit) == 0:
pass
elif lineSplit[0][0] == '#':
if lineSplit[0] == '#if':
if comment == nesting:
try:
res = eval(lineSplit[1])
except:
res = False
if res:
comment += 1
nesting += 1
elif lineSplit[0] == '#else':
if comment == nesting-1:
comment += 1
elif comment == nesting:
comment -= 1
elif lineSplit[0] == '#endif':
if comment == nesting:
comment -= 1
nesting -= 1
elif comment < nesting:
pass
elif lineSplit[0] == 'end':
try:
sub = tokens
tokens = stack.pop()
if tokens:
tokens[-1][2] = sub
level -= 1
except:
print( "Tokenizer error at or before line %d" % lineNo )
print( line )
elif lineSplit[-1] == ';':
if lineSplit[0] == '\\':
key = lineSplit[1]
tokens.append([key,lineSplit[2:-1],[]])
else:
key = lineSplit[0]
tokens.append([key,lineSplit[1:-1],[]])
else:
key = lineSplit[0]
tokens.append([key,lineSplit[1:],[]])
stack.append(tokens)
level += 1
tokens = []
file.close()
if level != 0:
MyError("Tokenizer out of kilter %d" % level)
clearScene()
print( "Parsing" )
parse(tokens)
for (expr, glbals, lcals) in todo:
try:
print("Doing %s" % expr)
exec(expr, glbals, lcals)
except:
time2 = time.clock()
print("toggle = %x" % toggle)
msg = "File %s loaded in %g s" % (fileName, time2-time1)
if nErrors:
msg += " but there where %d errors. " % (nErrors)
print(msg)
return
#
# getObject(name, var, glbals, lcals):
try:
ob = loadedData['Object'][name]
except:
if name != "None":
pushOnTodoList(None, "ob = loadedData['Object'][name]" % globals(), locals())
ob = None
return ob
#
def checkMhxVersion(major, minor):
print("MHX", (major,minor), (MAJOR_VERSION, MINOR_VERSION), warnedVersion)
Thomas Larsson
committed
if major != MAJOR_VERSION or minor != MINOR_VERSION:
"Wrong MHX version\n" +
"Expected MHX %d.%d but the loaded file " % (MAJOR_VERSION, MINOR_VERSION) +
"has version MHX %d.%d\n" % (major, minor))
if minor < MINOR_VERSION:
msg += (
"You can disable this error message by deselecting the \n" +
"Enforce version option when importing. Better:\n" +
"Export the MHX file again with an updated version of MakeHuman.\n" +
"The most up-to-date version of MakeHuman is the nightly build.\n")
else:
msg += (
"Download the most recent Blender build from www.graphicall.org. \n" +
"The most up-to-date version of the import script is distributed\n" +
"with Blender. It can also be downloaded from MakeHuman. \n" +
"It is located in the importers/mhx/blender25x \n" +
"folder and is called import_scene_mhx.py. \n")
if (toggle & T_EnforceVersion or minor > MINOR_VERSION):
else:
print(msg)
warnedVersion = True
return
#
ifResult = False
def parse(tokens):
global MHX249, ifResult, theScale, defaultScale, One
for (key, val, sub) in tokens:
print("Parse %s" % key)
data = None
if key == 'MHX':
checkMhxVersion(int(val[0]), int(val[1]))
elif key == 'MHX249':
MHX249 = eval(val[0])
print("Blender 2.49 compatibility mode is %s\n" % MHX249)
elif MHX249:
pass
elif key == 'print':
msg = concatList(val)
print(msg)
elif key == 'warn':
msg = concatList(val)
print(msg)
elif key == 'error':
msg = concatList(val)
elif key == 'NoScale':
if eval(val[0]):
theScale = 1.0
else:
theScale = defaultScale
One = 1.0/theScale
elif key == "Object":
parseObject(val, sub)
elif key == "Mesh":
reinitGlobalData()
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
data = parseMesh(val, sub)
elif key == "Armature":
data = parseArmature(val, sub)
elif key == "Pose":
data = parsePose(val, sub)
elif key == "Action":
data = parseAction(val, sub)
elif key == "Material":
data = parseMaterial(val, sub)
elif key == "Texture":
data = parseTexture(val, sub)
elif key == "Image":
data = parseImage(val, sub)
elif key == "Curve":
data = parseCurve(val, sub)
elif key == "TextCurve":
data = parseTextCurve(val, sub)
elif key == "Lattice":
data = parseLattice(val, sub)
elif key == "Group":
data = parseGroup(val, sub)
elif key == "Lamp":
data = parseLamp(val, sub)
elif key == "World":
data = parseWorld(val, sub)
elif key == "Scene":
data = parseScene(val, sub)
elif key == "DefineProperty":
parseDefineProperty(val, sub)
elif key == "PostProcess":
postProcess(val)
hideLayers(val)
elif key == "CorrectRig":
correctRig(val)
elif key == "Rigify":
if toggle & T_Rigify:
Thomas Larsson
committed
rigifyMhx(bpy.context, val[0])
elif key == 'AnimationData':
try:
ob = loadedData['Object'][val[0]]
except:
ob = None
if ob:
bpy.context.scene.objects.active = ob
parseAnimationData(ob, val, sub)
elif key == 'MaterialAnimationData':
try:
ob = loadedData['Object'][val[0]]
except:
ob = None
if ob:
bpy.context.scene.objects.active = ob
mat = ob.data.materials[int(val[2])]
print("matanim", ob, mat)
parseAnimationData(mat, val, sub)
elif key == 'ShapeKeys':
try:
ob = loadedData['Object'][val[0]]
except:
MyError("ShapeKeys object %s does not exist" % val[0])
parseShapeKeys(ob, ob.data, val, sub)
else:
data = parseDefaultType(key, val, sub)
if data and key != 'Mesh':
print( data )
return
#
# parseDefaultType(typ, args, tokens):
name = args[0]
data = None
expr = "bpy.data.%s.new('%s')" % (Plural[typ], name)
# print(expr)
# print(" ok", data)
bpyType = typ.capitalize()
print(bpyType, name, data)
loadedData[bpyType][name] = data
if data is None:
for (key, val, sub) in tokens:
#print("%s %s" % (key, val))
defaultKey(key, val, sub, 'data', [], globals(), locals())
print("Done ", data)
return data
string = ""
for elt in elts:
string += " %s" % elt
return string
# parseAction(args, tokens):
# parseFCurve(fcu, args, tokens):
# parseKeyFramePoint(pt, args, tokens):
name = args[0]
if invalid(args[1]):
return
ob = bpy.context.object
bpy.ops.object.mode_set(mode='POSE')
if ob.animation_data:
ob.animation_data.action = None
created = {}
for (key, val, sub) in tokens:
if key == 'FCurve':
prepareActionFCurve(ob, created, val, sub)
act = ob.animation_data.action
loadedData['Action'][name] = act
if act is None:
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
print("Ignoring action %s" % name)
return act
act.name = name
print("Action", name, act, ob)
for (key, val, sub) in tokens:
if key == 'FCurve':
fcu = parseActionFCurve(act, ob, val, sub)
else:
defaultKey(key, val, sub, 'act', [], globals(), locals())
ob.animation_data.action = None
bpy.ops.object.mode_set(mode='OBJECT')
return act
def prepareActionFCurve(ob, created, args, tokens):
dataPath = args[0]
index = args[1]
(expr, channel) = channelFromDataPath(dataPath, index)
try:
if channel in created[expr]:
return
else:
created[expr].append(channel)
except:
created[expr] = [channel]
times = []
for (key, val, sub) in tokens:
if key == 'kp':
times.append(int(val[0]))
try:
data = eval(expr)
except:
print("Ignoring illegal expression: %s" % expr)
return
n = 0
for t in times:
#bpy.context.scene.current_frame = t
bpy.ops.anim.change_frame(frame = t)
try:
data.keyframe_insert(channel)
n += 1
except:
pass
#print("failed", data, expr, channel)
if n != len(times):
print("Mismatch", n, len(times), expr, channel)
return
words = dataPath.split(']')
if len(words) == 1:
# location
expr = "ob"
channel = dataPath
elif len(words) == 2:
# pose.bones["tongue"].location
expr = "ob.%s]" % (words[0])
cwords = words[1].split('.')
channel = cwords[1]
elif len(words) == 3:
# pose.bones["brow.R"]["mad"]
expr = "ob.%s]" % (words[0])
cwords = words[1].split('"')
channel = cwords[1]
# print(expr, channel, index)
return (expr, channel)
dataPath = args[0]
index = args[1]
(expr, channel) = channelFromDataPath(dataPath, index)
index = int(args[1])
success = False
for fcu in act.fcurves:
(expr1, channel1) = channelFromDataPath(fcu.data_path, fcu.array_index)
if expr1 == expr and channel1 == channel and fcu.array_index == index:
success = True
break
if not success:
return None
n = 0
for (key, val, sub) in tokens:
if key == 'kp':
try:
pt = fcu.keyframe_points[n]
pt.interpolation = 'LINEAR'
pt = parseKeyFramePoint(pt, val, sub)
n += 1
except:
pass
#print(tokens)
#MyError("kp", fcu, n, len(fcu.keyframe_points), val)
else:
defaultKey(key, val, sub, 'fcu', [], globals(), locals())
return fcu
pt.co = (float(args[0]), float(args[1]))
if len(args) > 2:
pt.handle1 = (float(args[2]), float(args[3]))
pt.handle2 = (float(args[3]), float(args[5]))
return pt
# parseAnimationData(rna, args, tokens):
# parseDriver(drv, args, tokens):
# parseDriverVariable(var, args, tokens):
#
def parseAnimationData(rna, args, tokens):
print("Parse Animation data")
if rna.animation_data is None:
rna.animation_data_create()
adata = rna.animation_data
for (key, val, sub) in tokens:
if key == 'FCurve':
fcu = parseAnimDataFCurve(adata, rna, val, sub)
else:
defaultKey(key, val, sub, 'adata', [], globals(), locals())
print(adata)
def parseAnimDataFCurve(adata, rna, args, tokens):
if invalid(args[2]):
return
dataPath = args[0]
index = int(args[1])
n = 1
for (key, val, sub) in tokens:
if key == 'Driver':
fcu = parseDriver(adata, dataPath, index, rna, val, sub)
fmod = fcu.modifiers[0]
fcu.modifiers.remove(fmod)
elif key == 'FModifier':
parseFModifier(fcu, val, sub)
elif key == 'kp':
pt = fcu.keyframe_points.insert(n, 0)
pt.interpolation = 'LINEAR'
pt = parseKeyFramePoint(pt, val, sub)
n += 1
else:
defaultKey(key, val, sub, 'fcu', [], globals(), locals())
return fcu
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
"""
def parseDriver(adata, dataPath, index, rna, args, tokens):
if dataPath[-1] == ']':
words = dataPath.split(']')
expr = "rna." + words[0] + ']'
pwords = words[1].split('"')
prop = pwords[1]
#print("prop", expr, prop)
bone = eval(expr)
return None
else:
words = dataPath.split('.')
channel = words[-1]
expr = "rna"
for n in range(len(words)-1):
expr += "." + words[n]
expr += ".driver_add('%s', index)" % channel
#print("expr", rna, expr)
fcu = eval(expr)
drv = fcu.driver
#print(" Driver type", drv, args[0])
#print(" ->", drv.type)
for (key, val, sub) in tokens:
if key == 'DriverVariable':
var = parseDriverVariable(drv, rna, val, sub)
else:
defaultKey(key, val, sub, 'drv', [], globals(), locals())
return fcu
def parseDriverVariable(drv, rna, args, tokens):
#print(" Var type", var, args[1])
#print(" ->", var.type)
nTarget = 0
for (key, val, sub) in tokens:
if key == 'Target':
parseDriverTarget(var, nTarget, rna, val, sub)
nTarget += 1
else:
defaultKey(key, val, sub, 'var', [], globals(), locals())
return var
fmod = fcu.modifiers.new(args[0])
#fmod = fcu.modifiers[0]
for (key, val, sub) in tokens:
defaultKey(key, val, sub, 'fmod', [], globals(), locals())
return fmod
var = driver.variables.new()
var.name = target_bone
var.targets[0].id_type = 'OBJECT'
var.targets[0].id = obj
var.targets[0].rna_path = driver_path
"""
def parseDriverTarget(var, nTarget, rna, args, tokens):
name = args[0]
#targ.id_type = args[1]
dtype = args[1].capitalize()
dtype = 'Object'
targ.id = loadedData[dtype][name]
#print(" ->", targ.id)
for (key, val, sub) in tokens:
defaultKey(key, val, sub, 'targ', [], globals(), locals())
return targ
# parseMaterial(args, ext, tokens):
# parseMTex(mat, args, tokens):
# parseTexture(args, tokens):
global todo
name = args[0]
mat = bpy.data.materials.new(name)
if mat is None:
return None
loadedData['Material'][name] = mat
for (key, val, sub) in tokens:
if key == 'MTex':
parseMTex(mat, val, sub)
elif key == 'Ramp':
parseRamp(mat, val, sub)
elif key == 'RaytraceTransparency':
parseDefault(mat.raytrace_transparency, sub, {}, [])
elif key == 'Halo':
parseDefault(mat.halo, sub, {}, [])
elif key == 'SSS':
parseDefault(mat.subsurface_scattering, sub, {}, [])
elif key == 'Strand':
parseDefault(mat.strand, sub, {}, [])
elif key == 'NodeTree':
mat.use_nodes = True
parseNodeTree(mat.node_tree, val, sub)
elif key == 'AnimationData':
parseAnimationData(mat, val, sub)
else:
exclude = ['specular_intensity', 'tangent_shading']
defaultKey(key, val, sub, 'mat', [], globals(), locals())
return mat
global todo
index = int(args[0])
texname = args[1]
texco = args[2]
mapto = args[3]
tex = loadedData['Texture'][texname]
mtex = mat.texture_slots.add()
mtex.texture_coords = texco
mtex.texture = tex
for (key, val, sub) in tokens:
defaultKey(key, val, sub, "mtex", [], globals(), locals())
global todo
if verbosity > 2:
print( "Parsing texture %s" % args )
name = args[0]
tex = bpy.data.textures.new(name=name, type=args[1])
loadedData['Texture'][name] = tex
for (key, val, sub) in tokens:
if key == 'Image':
try:
imgName = val[0]
img = loadedData['Image'][imgName]
tex.image = img
except:
msg = "Unable to load image '%s'" % val[0]
elif key == 'Ramp':
parseRamp(tex, val, sub)
elif key == 'NodeTree':
tex.use_nodes = True
parseNodeTree(tex.node_tree, val, sub)
else:
defaultKey(key, val, sub, "tex", ['use_nodes', 'use_textures', 'contrast'], globals(), locals())
return tex
nvar = "data.%s" % args[0]
use = "data.use_%s = True" % args[0]
exec(use)
ramp = eval(nvar)
elts = ramp.elements
n = 0
for (key, val, sub) in tokens:
# print("Ramp", key, val)
if key == 'Element':
elts[n].color = eval(val[0])
elts[n].position = eval(val[1])
n += 1
else:
defaultKey(key, val, sub, "tex", ['use_nodes', 'use_textures', 'contrast'], globals(), locals())
sss = mat.subsurface_scattering
for (key, val, sub) in tokens:
defaultKey(key, val, sub, "sss", [], globals(), locals())
strand = mat.strand
for (key, val, sub) in tokens:
defaultKey(key, val, sub, "strand", [], globals(), locals())
# parseNodeTree(tree, args, tokens):
# parseNode(node, args, tokens):
# parseSocket(socket, args, tokens):
#
def parseNodeTree(tree, args, tokens):
return
print("Tree", tree, args)
print(list(tree.nodes))
tree.name = args[0]
for (key, val, sub) in tokens:
if key == 'Node':
parseNodes(tree.nodes, val, sub)
else:
defaultKey(key, val, sub, "tree", [], globals(), locals())
def parseNodes(nodes, args, tokens):
print("Nodes", nodes, args)
print(list(nodes))
node.name = args[0]
for (key, val, sub) in tokens:
if key == 'Inputs':
parseSocket(node.inputs, val, sub)
elif key == 'Outputs':
parseSocket(node.outputs, val, sub)
else:
defaultKey(key, val, sub, "node", [], globals(), locals())
def parseNode(node, args, tokens):
print("Node", node, args)
print(list(node.inputs), list(node.outputs))
node.name = args[0]
for (key, val, sub) in tokens:
if key == 'Inputs':
parseSocket(node.inputs, val, sub)
elif key == 'Outputs':
parseSocket(node.outputs, val, sub)
else:
defaultKey(key, val, sub, "node", [], globals(), locals())
def parseSocket(socket, args, tokens):
print("Socket", socket, args)
socket.name = args[0]
for (key, val, sub) in tokens:
if key == 'Node':
parseNode(tree.nodes, val, sub)
else:
defaultKey(key, val, sub, "tree", [], globals(), locals())
#
# doLoadImage(filepath):
# loadImage(filepath):
# parseImage(args, tokens):
def doLoadImage(filepath):
path1 = os.path.expanduser(filepath)
file1 = os.path.realpath(path1)
if os.path.isfile(file1):
print( "Found file "+file1 )
try:
img = bpy.data.images.load(file1)
return img
except:
print( "Cannot read image" )
return None
else:
print( "No file "+file1 )
return None
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
global TexDir, warnedTextureDir, loadedData
texDir = os.path.expanduser(TexDir)
path1 = os.path.expanduser(filepath)
file1 = os.path.realpath(path1)
(path, filename) = os.path.split(file1)
(name, ext) = os.path.splitext(filename)
print( "Loading ", filepath, " = ", filename )
# img = doLoadImage(texDir+"/"+name+".png")
# if img:
# return img
img = doLoadImage(texDir+"/"+filename)
if img:
return img
# img = doLoadImage(path+"/"+name+".png")
# if img:
# return img
img = doLoadImage(path+"/"+filename)
if img:
return img
if warnedTextureDir:
return None
warnedTextureDir = True
return None
TexDir = Draw.PupStrInput("TexDir? ", path, 100)
texDir = os.path.expanduser(TexDir)
img = doLoadImage(texDir+"/"+name+".png")
if img:
return img
img = doLoadImage(TexDir+"/"+filename)
return img
global todo
imgName = args[0]
img = None
for (key, val, sub) in tokens:
if key == 'Filename':
filename = val[0]
for n in range(1,len(val)):
filename += " " + val[n]
img = loadImage(filename)
if img is None:
return None
img.name = imgName
else:
defaultKey(key, val, sub, "img", ['depth', 'dirty', 'has_data', 'size', 'type'], globals(), locals())
print ("Image %s" % img )
loadedData['Image'][imgName] = img
return img
#
# parseObject(args, tokens):
# createObject(type, name, data, datName):
# setObjectAndData(args, typ):
#
if verbosity > 2:
print( "Parsing object %s" % args )
name = args[0]
typ = args[1]
datName = args[2]
if typ == 'EMPTY':
ob = bpy.data.objects.new(name, None)
loadedData['Object'][name] = ob
linkObject(ob, None)
else:
try:
data = loadedData[typ.capitalize()][datName]
except:
MyError("Failed to find data: %s %s %s" % (name, typ, datName))
return
try:
ob = loadedData['Object'][name]
bpy.context.scene.objects.active = ob
#print("Found data", ob)
except:
ob = None
if ob is None:
print("Create", name, data, datName)
ob = createObject(typ, name, data, datName)
print("created", ob)
linkObject(ob, data)
for (key, val, sub) in tokens:
if key == 'Modifier':
parseModifier(ob, val, sub)
elif key == 'Constraint':
parseConstraint(ob.constraints, None, val, sub)
elif key == 'AnimationData':
parseAnimationData(ob, val, sub)
elif key == 'ParticleSystem':
parseParticleSystem(ob, val, sub)
elif key == 'FieldSettings':
parseDefault(ob.field, sub, {}, [])
else:
defaultKey(key, val, sub, "ob", ['type', 'data'], globals(), locals())
Thomas Larsson
committed
if ob.type == 'MESH':
print("Smooth shade", ob)
bpy.ops.object.shade_smooth()
else:
print("Context", ob, bpy.context.object, bpy.context.scene.objects.active)
return
# print( "Creating object %s %s %s" % (typ, name, data) )
ob = bpy.data.objects.new(name, data)
if data:
loadedData[typ.capitalize()][datName] = data
loadedData['Object'][name] = ob
return ob
if data and ob.data is None:
ob.data = data
print("Data linked", ob, ob.data)
scn = bpy.context.scene
scn.objects.link(ob)
scn.objects.active = ob
#print("Linked object", ob)
#print("Scene", scn)
#print("Active", scn.objects.active)
#print("Context", bpy.context.object)
return ob
def setObjectAndData(args, typ):
datName = args[0]
obName = args[1]
#bpy.ops.object.add(type=typ)
ob = bpy.context.object
ob.name = obName
ob.data.name = datName
loadedData[typ][datName] = ob.data
loadedData['Object'][obName] = ob
return ob.data
#
name = args[0]
typ = args[1]
if typ == 'PARTICLE_SYSTEM':
return None
mod = ob.modifiers.new(name, typ)
for (key, val, sub) in tokens:
if key == 'HookAssignNth':
if val[0] == 'CURVE':
hookAssignNth(mod, int(val[1]), True, ob.data.splines[0].points)
elif val[0] == 'LATTICE':
hookAssignNth(mod, int(val[1]), False, ob.data.points)
elif val[0] == 'MESH':
hookAssignNth(mod, int(val[1]), True, ob.data.vertices)
else:
else:
defaultKey(key, val, sub, 'mod', [], globals(), locals())
def hookAssignNth(mod, n, select, points):
if select:
for pt in points:
pt.select = False
points[n].select = True
sel = []
for pt in points:
sel.append(pt.select)
#print(mod, sel, n, points)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.hook_reset(modifier=mod.name)
bpy.ops.object.hook_select(modifier=mod.name)
bpy.ops.object.hook_assign(modifier=mod.name)
bpy.ops.object.mode_set(mode='OBJECT')
return
# parseParticleSystem(ob, args, tokens):
# parseParticles(particles, args, tokens):
# parseParticle(par, args, tokens):
#
def parseParticleSystem(ob, args, tokens):
print(ob, bpy.context.object)
pss = ob.particle_systems
print(pss, pss.values())
name = args[0]
typ = args[1]
#psys = pss.new(name, typ)
bpy.ops.object.particle_system_add()
print(pss, pss.values())
psys = pss[-1]
psys.name = name
psys.settings.type = typ
loadedData['ParticleSystem'][name] = psys
print("Psys", psys)
for (key, val, sub) in tokens:
if key == 'Particles':
parseParticles(psys, val, sub)
else:
defaultKey(key, val, sub, 'psys', [], globals(), locals())
return psys
particles = psys.particles
bpy.ops.particle.particle_edit_toggle()
n = 0
for (key, val, sub) in tokens:
if key == 'Particle':
parseParticle(particles[n], val, sub)
n += 1
else:
for par in particles:
defaultKey(key, val, sub, 'par', [], globals(), locals())
bpy.ops.particle.particle_edit_toggle()
return particles
n = 0
for (key, val, sub) in tokens:
if key == 'h':
h = par.hair[n]
h.location = eval(val[0])
h.time = int(val[1])
h.weight = float(val[2])
n += 1
elif key == 'location':
par.location = eval(val[0])
return
l = []
for t in list_of_tuples:
l.extend(t)
return l
global todo, BMeshAware
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
if verbosity > 2:
print( "Parsing mesh %s" % args )
mename = args[0]
obname = args[1]
me = bpy.data.meshes.new(mename)
ob = createObject('MESH', obname, me, mename)
verts = []
edges = []
faces = []
vertsTex = []
texFaces = []
for (key, val, sub) in tokens:
if key == 'Verts':
verts = parseVerts(sub)
elif key == 'Edges':
edges = parseEdges(sub)
elif key == 'Faces':
faces = parseFaces(sub)
if faces:
me.from_pydata(verts, [], faces)
else:
me.from_pydata(verts, edges, [])
me.update()
if faces:
try:
me.polygons
BMeshAware = True
print("Using BMesh")
except:
BMeshAware = False
print("Not using BMesh")
Thomas Larsson
committed
nuvlayers = 0
for (key, val, sub) in tokens:
if key == 'Verts' or key == 'Edges' or key == 'Faces':
pass
elif key == 'MeshTextureFaceLayer':
if BMeshAware:
parseUvTextureBMesh(val, sub, me)
else:
parseUvTextureNoBMesh(val, sub, me)
Thomas Larsson
committed
elif key == 'MeshColorLayer':
parseVertColorLayer(val, sub, me)
elif key == 'VertexGroup':
parseVertexGroup(ob, me, val, sub)
elif key == 'ShapeKeys':
parseShapeKeys(ob, me, val, sub)
elif key == 'Material':
try:
mat = loadedData['Material'][val[0]]
except:
mat = None
if mat:
me.materials.append(mat)
else:
defaultKey(key, val, sub, "me", [], globals(), locals())
for (key, val, sub) in tokens:
if key == 'Faces':
if BMeshAware:
parseFaces2BMesh(sub, me)
else:
parseFaces2NoBMesh(sub, me)
print(me)
return me
#
# parseVerts(tokens):
# parseEdges(tokens):
# parseFaces(tokens):
# parseFaces2(tokens, me):
verts = []
for (key, val, sub) in tokens:
if key == 'v':
verts.append( (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2])) )
return verts
edges = []
for (key, val, sub) in tokens:
if key == 'e':
edges.append((int(val[0]), int(val[1])))
return edges
def parseFaces(tokens):
faces = []
for (key, val, sub) in tokens:
if key == 'f':
if len(val) == 3:
face = [int(val[0]), int(val[1]), int(val[2])]
elif len(val) == 4:
face = [int(val[0]), int(val[1]), int(val[2]), int(val[3])]
faces.append(face)
return faces
def parseFaces2BMesh(tokens, me):
n = 0
for (key, val, sub) in tokens:
if key == 'ft':
Thomas Larsson
committed
f = me.polygons[n]
f.material_index = int(val[0])
f.use_smooth = int(val[1])
n += 1
elif key == 'ftn':
mn = int(val[1])
us = int(val[2])
npts = int(val[0])
for i in range(npts):
Thomas Larsson
committed
f = me.polygons[n]
f.material_index = mn
f.use_smooth = us
n += 1
elif key == 'mn':
fn = int(val[0])
mn = int(val[1])
Thomas Larsson
committed
f = me.polygons[fn]
f.material_index = mn
elif key == 'ftall':
mat = int(val[0])
smooth = int(val[1])
Thomas Larsson
committed
for f in me.polygons:
f.material_index = mat
f.use_smooth = smooth
return
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
def parseFaces2NoBMesh(tokens, me):
n = 0
for (key, val, sub) in tokens:
if key == 'ft':
f = me.faces[n]
f.material_index = int(val[0])
f.use_smooth = int(val[1])
n += 1
elif key == 'ftn':
mn = int(val[1])
us = int(val[2])
npts = int(val[0])
for i in range(npts):
f = me.faces[n]
f.material_index = mn
f.use_smooth = us
n += 1
elif key == 'mn':
fn = int(val[0])
mn = int(val[1])
f = me.faces[fn]
f.material_index = mn
elif key == 'ftall':
mat = int(val[0])
smooth = int(val[1])
for f in me.faces:
f.material_index = mat
f.use_smooth = smooth
return
Thomas Larsson
committed
# parseUvTexture(args, tokens, me,):
def parseUvTextureBMesh(args, tokens, me):
Thomas Larsson
committed
bpy.ops.mesh.uv_texture_add()
uvtex = me.uv_textures[-1]
uvtex.name = name
Thomas Larsson
committed
loadedData['MeshTextureFaceLayer'][name] = uvloop
parseUvTexDataBMesh(val, sub, uvloop.data)
else:
defaultKey(key, val, sub, "uvtex", [], globals(), locals())
return
def parseUvTexDataBMesh(args, tokens, data):
n = 0
for (key, val, sub) in tokens:
if key == 'vt':
Thomas Larsson
committed
data[n].uv = (float(val[0]), float(val[1]))
n += 1
data[n].uv = (float(val[2]), float(val[3]))
n += 1
data[n].uv = (float(val[4]), float(val[5]))
n += 1
Thomas Larsson
committed
data[n].uv = (float(val[6]), float(val[7]))
n += 1
return
def parseUvTextureNoBMesh(args, tokens, me):
name = args[0]
uvtex = me.uv_textures.new(name = name)
loadedData['MeshTextureFaceLayer'][name] = uvtex
for (key, val, sub) in tokens:
if key == 'Data':
parseUvTexDataNoBMesh(val, sub, uvtex.data)
defaultKey(key, val, sub, "uvtex", [], globals(), locals())
return
def parseUvTexDataNoBMesh(args, tokens, data):
n = 0
for (key, val, sub) in tokens:
if key == 'vt':
data[n].uv1 = (float(val[0]), float(val[1]))
data[n].uv2 = (float(val[2]), float(val[3]))
data[n].uv3 = (float(val[4]), float(val[5]))
if len(val) > 6:
data[n].uv4 = (float(val[6]), float(val[7]))
n += 1
return
#
# parseVertColorLayer(args, tokens, me):
# parseVertColorData(args, tokens, data):
#
def parseVertColorLayer(args, tokens, me):
name = args[0]
print("VertColorLayer", name)
vcol = me.vertex_colors.new(name)
loadedData['MeshColorLayer'][name] = vcol
for (key, val, sub) in tokens:
if key == 'Data':
parseVertColorData(val, sub, vcol.data)
else:
defaultKey(key, val, sub, "vcol", [], globals(), locals())
return
n = 0
for (key, val, sub) in tokens:
if key == 'cv':
data[n].color1 = eval(val[0])
data[n].color2 = eval(val[1])
data[n].color3 = eval(val[2])
data[n].color4 = eval(val[3])
n += 1
return
#
def parseVertexGroup(ob, me, args, tokens):
if verbosity > 2:
print( "Parsing vertgroup %s" % args )
grpName = args[0]
try:
res = eval(args[1])
except:
res = True
if not res:
return
if (toggle & T_Armature) or (grpName in ['Eye_L', 'Eye_R', 'Gums', 'Head', 'Jaw', 'Left', 'Middle', 'Right', 'Scalp']):
try:
group = loadedData['VertexGroup'][grpName]
except KeyError:
group = ob.vertex_groups.new(grpName)
loadedData['VertexGroup'][grpName] = group
for (key, val, sub) in tokens:
if key == 'wv':
group.add( [int(val[0])], float(val[1]), 'REPLACE' )
return
#
# parseShapeKeys(ob, me, args, tokens):
# parseShapeKey(ob, me, args, tokens):
# addShapeKey(ob, name, vgroup, tokens):
# doShape(name):
if (toggle & T_Shape+T_Face) and (name == 'Basis'):
return True
else:
return (toggle & T_Face)
for (key, val, sub) in tokens:
if key == 'ShapeKey':
parseShapeKey(ob, me, val, sub)
elif key == 'AnimationData':
if me.shape_keys:
parseAnimationData(me.shape_keys, val, sub)
ob.active_shape_key_index = 0
print("Shapekeys parsed")
if verbosity > 2:
print( "Parsing ob %s shape %s" % (bpy.context.object, args[0] ))
name = args[0]
lr = args[1]
if invalid(args[2]):
return
addShapeKey(ob, name, None, tokens)
elif lr == 'LR':
addShapeKey(ob, name+'_L', 'Left', tokens)
addShapeKey(ob, name+'_R', 'Right', tokens)
else:
skey = ob.shape_key_add(name=name, from_mix=False)
if name != 'Basis':
skey.relative_key = loadedData['ShapeKey']['Basis']
skey.name = name
if vgroup:
skey.vertex_group = vgroup
loadedData['ShapeKey'][name] = skey
for (key, val, sub) in tokens:
if key == 'sv':
index = int(val[0])
pt = skey.data[index].co
pt[0] += theScale*float(val[1])
pt[1] += theScale*float(val[2])
pt[2] += theScale*float(val[3])
else:
defaultKey(key, val, sub, "skey", [], globals(), locals())
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
global toggle
if verbosity > 2:
print( "Parsing armature %s" % args )
amtname = args[0]
obname = args[1]
mode = args[2]
amt = bpy.data.armatures.new(amtname)
ob = createObject('ARMATURE', obname, amt, amtname)
linkObject(ob, amt)
print("Linked")
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
heads = {}
tails = {}
for (key, val, sub) in tokens:
if key == 'Bone':
bname = val[0]
if not invalid(val[1]):
bone = amt.edit_bones.new(bname)
parseBone(bone, amt, sub, heads, tails)
loadedData['Bone'][bname] = bone
elif key == 'RecalcRoll':
for bone in amt.edit_bones:
bone.select = False
blist = eval(val[0])
for name in blist:
bone = amt.edit_bones[name]
bone.select = True
bpy.ops.armature.calculate_roll(type='Z')
for bone in amt.edit_bones:
rolls[bone.name] = bone.roll
bpy.ops.object.mode_set(mode='OBJECT')
for bone in amt.bones:
bone['Roll'] = rolls[bone.name]
bpy.ops.object.mode_set(mode='EDIT')
else:
defaultKey(key, val, sub, "amt", ['MetaRig'], globals(), locals())
bpy.ops.object.mode_set(mode='OBJECT')
return amt
#
# parseBone(bone, amt, tokens, heads, tails):
#
def parseBone(bone, amt, tokens, heads, tails):
for (key, val, sub) in tokens:
if key == "head":
bone.head = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
elif key == "tail":
bone.tail = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
#elif key == 'restrict_select':
# pass
elif key == 'hide' and val[0] == 'True':
name = bone.name
'''
#bpy.ops.object.mode_set(mode='OBJECT')
pbone = amt.bones[name]
pbone.hide = True
print("Hide", pbone, pbone.hide)
#bpy.ops.object.mode_set(mode='EDIT')
'''
else:
defaultKey(key, val, sub, "bone", [], globals(), locals())
return bone
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
global todo
name = args[0]
ob = loadedData['Object'][name]
bpy.context.scene.objects.active = ob
bpy.ops.object.mode_set(mode='POSE')
pbones = ob.pose.bones
nGrps = 0
for (key, val, sub) in tokens:
if key == 'Posebone':
parsePoseBone(pbones, ob, val, sub)
elif key == 'BoneGroup':
parseBoneGroup(ob.pose, nGrps, val, sub)
nGrps += 1
elif key == 'SetProp':
bone = val[0]
prop = val[1]
value = eval(val[2])
pb = pbones[bone]
print("Setting", pb, prop, val)
pb[prop] = value
print("Prop set", pb[prop])
else:
defaultKey(key, val, sub, "ob.pose", [], globals(), locals())
bpy.ops.object.mode_set(mode='OBJECT')
return ob
#
# parsePoseBone(pbones, args, tokens):
# parseArray(data, exts, args):
#
def parseBoneGroup(pose, nGrps, args, tokens):
if verbosity > 2:
print( "Parsing bonegroup %s" % args )
name = args[0]
bpy.ops.pose.group_add()
bg = pose.bone_groups.active
loadedData['BoneGroup'][name] = bg
for (key, val, sub) in tokens:
defaultKey(key, val, sub, "bg", [], globals(), locals())
return
global todo
if invalid(args[1]):
return
name = args[0]
pb = pbones[name]
amt = ob.data
amt.bones.active = pb.bone
for (key, val, sub) in tokens:
if key == 'Constraint':
amt.bones.active = pb.bone
cns = parseConstraint(pb.constraints, pb, val, sub)
amt.bones.active = pb.bone
print(expr)
exec(expr)
elif key == 'ik_dof':
parseArray(pb, ["ik_dof_x", "ik_dof_y", "ik_dof_z"], val)
elif key == 'ik_limit':
parseArray(pb, ["ik_limit_x", "ik_limit_y", "ik_limit_z"], val)
elif key == 'ik_max':
parseArray(pb, ["ik_max_x", "ik_max_y", "ik_max_z"], val)
elif key == 'ik_min':
parseArray(pb, ["ik_min_x", "ik_min_y", "ik_min_z"], val)
elif key == 'ik_stiffness':
parseArray(pb, ["ik_stiffness_x", "ik_stiffness_y", "ik_stiffness_z"], val)
elif key == 'hide':
#bpy.ops.object.mode_set(mode='OBJECT')
amt.bones[name].hide = eval(val[0])
#bpy.ops.object.mode_set(mode='POSE')
else:
defaultKey(key, val, sub, "pb", [], globals(), locals())
#print("pb %s done" % name)
return
n = 1
for ext in exts:
expr = "data.%s = %s" % (ext, args[n])
# print(expr)
exec(expr)
n += 1
return
# parseConstraint(constraints, pb, args, tokens)
def parseConstraint(constraints, pb, args, tokens):
if (toggle&T_Opcns and pb):
print("Active")
aob = bpy.context.object
print("ob", aob)
aamt = aob.data
print("amt", aamt)
apose = aob.pose
print("pose", apose)
abone = aamt.bones.active
print("bone", abone)
print('Num cns before', len(list(constraints)))
bpy.ops.pose.constraint_add(type=args[1])
cns = constraints.active
print('and after', pb, cns, len(list(constraints)))
else:
cns = constraints.new(args[1])
cns.name = args[0]
for (key,val,sub) in tokens:
if key == 'invert':
parseArray(cns, ["invert_x", "invert_y", "invert_z"], val)
elif key == 'use':
parseArray(cns, ["use_x", "use_y", "use_z"], val)
elif key == 'pos_lock':
parseArray(cns, ["lock_location_x", "lock_location_y", "lock_location_z"], val)
elif key == 'rot_lock':
parseArray(cns, ["lock_rotation_x", "lock_rotation_y", "lock_rotation_z"], val)
else:
defaultKey(key, val, sub, "cns", [], globals(), locals())
#
# parseCurve (args, tokens):
# parseSpline(cu, args, tokens):
# parseBezier(spline, n, args, tokens):
global todo
if verbosity > 2:
print( "Parsing curve %s" % args )
bpy.ops.object.add(type='CURVE')
cu = setObjectAndData(args, 'Curve')
for (key, val, sub) in tokens:
if key == 'Spline':
parseSpline(cu, val, sub)
else:
defaultKey(key, val, sub, "cu", [], globals(), locals())
return
def parseTextCurve (args, tokens):
global todo
if verbosity > 2:
print( "Parsing text curve %s" % args )
bpy.ops.object.text_add()
txt = setObjectAndData(args, 'Text')
for (key, val, sub) in tokens:
if key == 'Spline':
parseSpline(txt, val, sub)
elif key == 'BodyFormat':
parseCollection(txt.body_format, sub, [])
elif key == 'EditFormat':
parseDefault(txt.edit_format, sub, {}, [])
elif key == 'Font':
parseDefault(txt.font, sub, {}, [])
elif key == 'TextBox':
parseCollection(txt.body_format, sub, [])
else:
defaultKey(key, val, sub, "txt", [], globals(), locals())
return
def parseSpline(cu, args, tokens):
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
typ = args[0]
spline = cu.splines.new(typ)
nPointsU = int(args[1])
nPointsV = int(args[2])
#spline.point_count_u = nPointsU
#spline.point_count_v = nPointsV
if typ == 'BEZIER' or typ == 'BSPLINE':
spline.bezier_points.add(nPointsU)
else:
spline.points.add(nPointsU)
n = 0
for (key, val, sub) in tokens:
if key == 'bz':
parseBezier(spline.bezier_points[n], val, sub)
n += 1
elif key == 'pt':
parsePoint(spline.points[n], val, sub)
n += 1
else:
defaultKey(key, val, sub, "spline", [], globals(), locals())
return
def parseBezier(bez, args, tokens):
bez.co = eval(args[0])
bez.co = theScale*bez.co
bez.handle1 = eval(args[1])
bez.handle1_type = args[2]
bez.handle2 = eval(args[3])
bez.handle2_type = args[4]
return
def parsePoint(pt, args, tokens):
print(" pt", pt.co)
global todo
if verbosity > 2:
print( "Parsing lattice %s" % args )
bpy.ops.object.add(type='LATTICE')
lat = setObjectAndData(args, 'Lattice')
for (key, val, sub) in tokens:
if key == 'Points':
parseLatticePoints(val, sub, lat.points)
else:
defaultKey(key, val, sub, "lat", [], globals(), locals())
return
global todo
n = 0
for (key, val, sub) in tokens:
if key == 'pt':
v = points[n].co_deform
v.x = theScale*float(val[0])
v.y = theScale*float(val[1])
v.z = theScale*float(val[2])
# parseLamp (args, tokens):
# parseFalloffCurve(focu, args, tokens):
def parseLamp (args, tokens):
global todo
if verbosity > 2:
print( "Parsing lamp %s" % args )
bpy.ops.object.add(type='LAMP')
lamp = setObjectAndData(args, 'Lamp')
for (key, val, sub) in tokens:
if key == 'FalloffCurve':
parseFalloffCurve(lamp.falloff_curve, val, sub)
else:
defaultKey(key, val, sub, "lamp", [], globals(), locals())
return
def parseFalloffCurve(focu, args, tokens):
# parseGroup (args, tokens):
# parseGroupObjects(args, tokens, grp):
#
def parseGroup (args, tokens):
global todo
if verbosity > 2:
print( "Parsing group %s" % args )
grpName = args[0]
grp = bpy.data.groups.new(grpName)
loadedData['Group'][grpName] = grp
for (key, val, sub) in tokens:
if key == 'Objects':
parseGroupObjects(val, sub, grp)
else:
defaultKey(key, val, sub, "grp", [], globals(), locals())
return
def parseGroupObjects(args, tokens, grp):
Thomas Larsson
committed
rig = None
for (key, val, sub) in tokens:
if key == 'ob':
try:
ob = loadedData['Object'][val[0]]
grp.objects.link(ob)
except:
Thomas Larsson
committed
ob = None
if ob:
print(ob, ob.type, rig, ob.parent)
if ob.type == 'ARMATURE':
rig = ob
elif ob.type == 'EMPTY' and rig and not ob.parent:
ob.parent = rig
print("SSS")
#
def parseWorld (args, tokens):
global todo
if verbosity > 2:
print( "Parsing world %s" % args )
world = bpy.context.scene.world
for (key, val, sub) in tokens:
if key == 'Lighting':
parseDefault(world.lighting, sub, {}, [])
elif key == 'Mist':
parseDefault(world.mist, sub, {}, [])
elif key == 'Stars':
parseDefault(world.stars, sub, {}, [])
else:
defaultKey(key, val, sub, "world", [], globals(), locals())
return
#
# parseScene (args, tokens):
# parseRenderSettings(render, args, tokens):
# parseToolSettings(tool, args, tokens):
#
def parseScene (args, tokens):
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
global todo
if verbosity > 2:
print( "Parsing scene %s" % args )
scn = bpy.context.scene
for (key, val, sub) in tokens:
if key == 'NodeTree':
scn.use_nodes = True
parseNodeTree(scn, val, sub)
elif key == 'GameData':
parseDefault(scn.game_data, sub, {}, [])
elif key == 'KeyingSet':
pass
#parseDefault(scn.keying_sets, sub, {}, [])
elif key == 'ObjectBase':
pass
#parseDefault(scn.bases, sub, {}, [])
elif key == 'RenderSettings':
parseRenderSettings(scn.render, sub, [])
elif key == 'ToolSettings':
subkeys = {'ImagePaint' : "image_paint",
'Sculpt' : "sculpt",
'VertexPaint' : "vertex_paint",
'WeightPaint' : "weight_paint" }
parseDefault(scn.tool_settings, sub, subkeys, [])
elif key == 'UnitSettings':
parseDefault(scn.unit_settings, sub, {}, [])
else:
defaultKey(key, val, sub, "scn", [], globals(), locals())
return
def parseRenderSettings(render, args, tokens):
global todo
if verbosity > 2:
print( "Parsing RenderSettings %s" % args )
for (key, val, sub) in tokens:
if key == 'Layer':
pass
#parseDefault(scn.layers, sub, [])
else:
defaultKey(key, val, sub, "render", [], globals(), locals())
return
# parseDefineProperty(args, tokens):
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
def parseDefineProperty(args, tokens):
expr = "bpy.types.Object.%s = %sProperty" % (args[0], args[1])
c = '('
for option in args[2:]:
expr += "%s %s" % (c, option)
c = ','
expr += ')'
#print(expr)
exec(expr)
#print("Done")
return
#
# correctRig(args):
#
def correctRig(args):
human = args[0]
print("CorrectRig %s" % human)
try:
ob = loadedData['Object'][human]
except:
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
bpy.context.scene.objects.active = ob
bpy.ops.object.mode_set(mode='POSE')
amt = ob.data
cnslist = []
for pb in ob.pose.bones:
for cns in pb.constraints:
if cns.type == 'CHILD_OF':
cnslist.append((pb, cns, cns.influence))
cns.influence = 0
for (pb, cns, inf) in cnslist:
amt.bones.active = pb.bone
cns.influence = 1
#print("Childof %s %s %s %.2f" % (amt.name, pb.name, cns.name, inf))
bpy.ops.constraint.childof_clear_inverse(constraint=cns.name, owner='BONE')
bpy.ops.constraint.childof_set_inverse(constraint=cns.name, owner='BONE')
cns.influence = 0
for (pb, cns, inf) in cnslist:
cns.influence = inf
return
#
# postProcess(args)
#
def postProcess(args):
human = args[0]
print("Postprocess %s" % human)
ob = loadedData['Object'][human]
except:
ob = None
if toggle & T_Diamond == 0 and ob:
Thomas Larsson
committed
deleteDiamonds(ob)
return
#
# deleteDiamonds(ob)
# Delete joint diamonds in main mesh
# Invisio = material # 1
#
def deleteDiamonds(ob):
bpy.context.scene.objects.active = ob
if not bpy.context.object:
return
print("Delete helper geometry in %s" % bpy.context.object)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.mode_set(mode='OBJECT')
me = ob.data
invisioNum = -1
for mn,mat in enumerate(me.materials):
if "Invis" in mat.name:
invisioNum = mn
break
if invisioNum < 0:
print("WARNING: Nu Invisio material found. Cannot delete helper geometry")
elif BMeshAware:
Thomas Larsson
committed
for f in me.polygons:
if f.material_index >= invisioNum:
for vn in f.vertices:
me.vertices[vn].select = True
else:
for f in me.faces:
if f.material_index >= invisioNum:
for vn in f.vertices:
me.vertices[vn].select = True
if BMeshAware and toggle&T_CrashSafe:
theMessage = "\n *** WARNING ***\nHelper deletion turned off due to Blender crash.\nHelpers can be deleted by deleting all selected vertices in Edit mode\n **********\n"
print(theMessage)
else:
bpy.ops.object.mode_set(mode='EDIT')
print("Do delete")
bpy.ops.mesh.delete(type='VERT')
print("Verts deleted")
bpy.ops.object.mode_set(mode='OBJECT')
print("Back to object mode")
# defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
thePropTip = ""
def defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
global todo, thePropTip
thePropTip = ""
expr = '%s["%s"] = %s' % (var, args[0], args[1])
#print("Property", expr)
if expr:
exec(expr, glbals, lcals)
if len(args) > 2:
thePropTip = '"description":"%s"' % args[2].replace("_", " ")
return
elif ext == 'PropKeys':
if len(args) < 2:
values = '{%s}' % thePropTip
else:
values = '{%s%s}' % (args[1], thePropTip)
try:
expr = '%s["_RNA_UI"]["%s"] = %s' % (var, args[0], values)
except:
expr = None
#print("PropKeys", expr)
if ext == 'bpyops':
expr = "bpy.ops.%s" % args[0]
print(expr)
exec(expr)
return
nvar = "%s.%s" % (var, ext)
#print(ext)
if ext in exclude:
return
#print("D", nvar)
if len(args) == 0:
rnaType = args[0]
if rnaType == 'Add':
print("*** Cannot Add yet ***")
return
elif rnaType == 'Refer':
typ = args[1]
name = args[2]
data = "loadedData['%s']['%s']" % (typ, name)
elif rnaType == 'Struct' or rnaType == 'Define':
typ = args[1]
name = args[2]
try:
data = eval(nvar, glbals, lcals)
except:
data = None
# print("Old structrna", nvar, data)
if data is None:
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
try:
creator = args[3]
except:
creator = None
# print("Creator", creator, eval(var,glbals,lcals))
try:
rna = eval(var,glbals,lcals)
data = eval(creator)
except:
data = None
# print("New struct", nvar, typ, data)
if rnaType == 'Define':
loadedData[typ][name] = data
if data:
for (key, val, sub) in tokens:
defaultKey(key, val, sub, "data", [], globals(), locals())
print("Struct done", nvar)
return
elif rnaType == 'PropertyRNA':
#print("PropertyRNA ", ext, var)
for (key, val, sub) in tokens:
defaultKey(ext, val, sub, nvar, [], glbals, lcals)
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
return
elif rnaType == 'Array':
for n in range(1, len(args)):
expr = "%s[%d] = %s" % (nvar, n-1, args[n])
exec(expr, glbals, lcals)
if len(args) > 0:
expr = "%s[0] = %s" % (nvar, args[1])
exec(expr, glbals, lcals)
return
elif rnaType == 'List':
data = []
for (key, val, sub) in tokens:
elt = eval(val[1], glbals, lcals)
data.append(elt)
elif rnaType == 'Matrix':
return
i = 0
n = len(tokens)
for (key, val, sub) in tokens:
if key == 'row':
for j in range(n):
expr = "%s[%d][%d] = %g" % (nvar, i, j, float(val[j]))
exec(expr, glbals, lcals)
i += 1
return
else:
try:
data = loadedData[rnaType][args[1]]
#print("From loaded", rnaType, args[1], data)
return data
except:
data = rnaType
#print(var, ext, data)
expr = "%s = %s" % (nvar, data)
try:
exec(expr, glbals, lcals)
except:
pushOnTodoList(var, expr, glbals, lcals)
return
#
#
#
def pushOnTodoList(var, expr, glbals, lcals):
global todo
print("Tdo", var)
print(dir(eval(var, glbals, lcals)))
list = []
for c in mask:
if c == '0':
list.append(False)
else:
list.append(True)
return list
matrix = mathutils.Matrix()
i = 0
for (key, val, sub) in tokens:
if key == 'row':
matrix[i][0] = float(val[0])
matrix[i][1] = float(val[1])
matrix[i][2] = float(val[2])
matrix[i][3] = float(val[3])
i += 1
return matrix
def parseDefault(data, tokens, subkeys, exclude):
for (key, val, sub) in tokens:
if key in subkeys.keys():
for (key2, val2, sub2) in sub:
defaultKey(key2, val2, sub2, "data.%s" % subkeys[key], [], globals(), locals())
else:
defaultKey(key, val, sub, "data", exclude, globals(), locals())
def parseCollection(data, tokens, exclude):
typeSplit = str(type(data)).split("'")
if typeSplit[0] != '<class ':
return None
classSplit = typeSplit[1].split(".")
if classSplit[0] == 'bpy' and classSplit[1] == 'types':
return classSplit[2]
elif classSplit[0] == 'bpy_types':
return classSplit[1]
else:
return None
if string == 'True':
return True
elif string == 'False':
return False
else:
global rigLeg, rigArm, toggle
res = eval(condition, globals())
try:
res = eval(condition, globals())
#print("%s = %s" % (condition, res))
return not res
except:
#print("%s invalid!" % condition)
return True
global toggle
scn = bpy.context.scene
for n in range(len(scn.layers)):
scn.layers[n] = True
print("clearScene %s %s" % (toggle & T_Replace, scn))
if not toggle & T_Replace:
return scn
for ob in scn.objects:
Thomas Larsson
committed
if ob.type in ['MESH', 'ARMATURE', 'EMPTY', 'CURVE', 'LATTICE']:
Thomas Larsson
committed
ob.name = "#" + ob.name
try:
bpy.ops.object.mode_set(mode='OBJECT')
except:
pass
Thomas Larsson
committed
for grp in bpy.data.groups:
grp.name = "#" + grp.name
#
# hideLayers(args):
# args = sceneLayers sceneHideLayers boneLayers boneHideLayers or nothing
#
def hideLayers(args):
if len(args) > 1:
sceneLayers = int(args[2], 16)
sceneHideLayers = int(args[3], 16)
boneLayers = int(args[4], 16)
# boneHideLayers = int(args[5], 16)
boneHideLayers = 0
else:
sceneLayers = 0x00ff
sceneHideLayers = 0
boneLayers = 0
boneHideLayers = 0
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
mask = 1
hidelayers = []
for n in range(20):
scn.layers[n] = True if sceneLayers & mask else False
if sceneHideLayers & mask:
hidelayers.append(n)
mask = mask << 1
for ob in scn.objects:
for n in hidelayers:
if ob.layers[n]:
ob.hide = True
if boneLayers:
human = args[1]
try:
ob = loadedData['Object'][human]
except:
return
mask = 1
hidelayers = []
for n in range(32):
ob.data.layers[n] = True if boneLayers & mask else False
if boneHideLayers & mask:
hidelayers.append(n)
mask = mask << 1
for b in ob.data.bones:
for n in hidelayers:
if b.layers[n]:
b.hide = True
return
#
# readDefaults():
# writeDefaults():
#
ConfigFile = '~/mhx_import.cfg'
def readDefaults():
path = os.path.realpath(os.path.expanduser(ConfigFile))
try:
fp = open(path, 'rU')
print('Storing defaults')
except:
print('Cannot open "%s" for reading' % path)
return
bver = ''
for line in fp:
words = line.split()
if len(words) >= 3:
try:
toggle = int(words[0],16)
theScale = float(words[1])
except:
print('Configuration file "%s" is corrupt' % path)
fp.close()
return
def writeDefaults():
path = os.path.realpath(os.path.expanduser(ConfigFile))
try:
fp = open(path, 'w')
print('Storing defaults')
except:
print('Cannot open "%s" for writing' % path)
return
fp.write("%x %f Graphicall" % (toggle, theScale))
fp.close()
###################################################################################
#
# Postprocessing of rigify rig
#
Thomas Larsson
committed
# rigifyMhx(context, name):
#
###################################################################################
Thomas Larsson
committed
def rigifyMhx(context, name):
print("Modifying MHX rig to Rigify")
scn = context.scene
Thomas Larsson
committed
mhx = loadedData['Object'][name]
mhx['MhxRigify'] = True
bpy.context.scene.objects.active = mhx
# Delete old widgets
"""
for ob in scn.objects:
if ob.type == 'MESH' and ob.name[0:3] == "WGT":
scn.objects.unlink(ob)
Thomas Larsson
committed
"""
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
# Save mhx bone locations
heads = {}
tails = {}
rolls = {}
parents = {}
extras = {}
bpy.ops.object.mode_set(mode='EDIT')
newParents = {
'head' : 'DEF-head',
'ribs' : 'DEF-ribs',
'upper_arm.L' : 'DEF-upper_arm.L.02',
'thigh.L' : 'DEF-thigh.L.02',
'upper_arm.R' : 'DEF-upper_arm.R.02',
'thigh.R' : 'DEF-thigh.R.02',
}
for eb in mhx.data.edit_bones:
heads[eb.name] = eb.head.copy()
tails[eb.name] = eb.tail.copy()
rolls[eb.name] = eb.roll
if eb.parent:
par = eb.parent.name
try:
parents[eb.name] = newParents[par]
except:
parents[eb.name] = par
else:
parents[eb.name] = None
extras[eb.name] = not eb.layers[16]
bpy.ops.object.mode_set(mode='OBJECT')
# Find corresponding meshes. Can be several (clothes etc.)
meshes = []
for ob in scn.objects:
for mod in ob.modifiers:
if (mod.type == 'ARMATURE' and mod.object == mhx):
meshes.append((ob, mod))
if meshes == []:
# Rename Head vertex group
for (mesh, mod) in meshes:
try:
vg = mesh.vertex_groups['DfmHead']
vg.name = 'DEF-head'
except:
pass
Thomas Larsson
committed
# Change meta bone locations
scn.objects.active = None
try:
bpy.ops.object.armature_human_advanced_add()
success = True
except:
success = False
if not success:
MyError("Unable to create advanced human. \n" \
"Make sure that the Rigify addon is enabled. \n" \
"It is found under Rigging.")
Thomas Larsson
committed
meta = context.object
bpy.ops.object.mode_set(mode='EDIT')
Thomas Larsson
committed
for eb in meta.data.edit_bones:
eb.head = heads[eb.name]
eb.tail = tails[eb.name]
eb.roll = rolls[eb.name]
extras[eb.name] = False
fingerPlanes = [
('UP-thumb.L', 'thumb.01.L', 'thumb.03.L', ['thumb.02.L']),
('UP-index.L', 'finger_index.01.L', 'finger_index.03.L', ['finger_index.02.L']),
('UP-middle.L', 'finger_middle.01.L', 'finger_middle.03.L', ['finger_middle.02.L']),
('UP-ring.L', 'finger_ring.01.L', 'finger_ring.03.L', ['finger_ring.02.L']),
('UP-pinky.L', 'finger_pinky.01.L', 'finger_pinky.03.L', ['finger_pinky.02.L']),
('UP-thumb.R', 'thumb.01.R', 'thumb.03.R', ['thumb.02.R']),
('UP-index.R', 'finger_index.01.R', 'finger_index.03.R', ['finger_index.02.R']),
('UP-middle.R', 'finger_middle.01.R', 'finger_middle.03.R', ['finger_middle.02.R']),
('UP-ring.R', 'finger_ring.01.R', 'finger_ring.03.R', ['finger_ring.02.R']),
('UP-pinky.R', 'finger_pinky.01.R', 'finger_pinky.03.R', ['finger_pinky.02.R']),
]
for (upbone, first, last, middles) in fingerPlanes:
extras[upbone] = False
Thomas Larsson
committed
#lineateChain(upbone, first, last, middles, 0.01, meta, heads, tails)
ikPlanes = [
('UP-leg.L', 'thigh.L', 'shin.L'),
('UP-arm.L', 'upper_arm.L', 'forearm.L'),
('UP-leg.R', 'thigh.R', 'shin.R'),
('UP-arm.R', 'upper_arm.R', 'forearm.R'),
]
for (upbone, first, last) in ikPlanes:
extras[upbone] = False
Thomas Larsson
committed
lineateChain(upbone, first, last, [], 0.1, meta, heads, tails)
bpy.ops.object.mode_set(mode='OBJECT')
Thomas Larsson
committed
# Generate rigify rig
bpy.ops.pose.rigify_generate()
Thomas Larsson
committed
scn.objects.unlink(meta)
rigify = context.object
rigify.name = name+"Rig"
layers = 20*[False]
layers[1] = True
rigify.layers = layers
Thomas Larsson
committed
rigify.show_x_ray = True
Thomas Larsson
committed
mod.object = rigify
grp = loadedData['Group'][name]
grp.objects.link(rigify)
# Parent widgets under empty
empty = bpy.data.objects.new("Widgets", None)
scn.objects.link(empty)
empty.layers = 20*[False]
empty.layers[19] = True
Thomas Larsson
committed
empty.parent = rigify
for ob in scn.objects:
if ob.type == 'MESH' and ob.name[0:4] == "WGT-" and not ob.parent:
ob.parent = empty
Thomas Larsson
committed
grp.objects.link(ob)
elif ob.parent == mhx:
ob.parent = rigify
Thomas Larsson
committed
# Copy extra bones to rigify rig
bpy.ops.object.mode_set(mode='EDIT')
for name in heads.keys():
if extras[name]:
Thomas Larsson
committed
eb = rigify.data.edit_bones.new(name)
eb.head = heads[name]
eb.tail = tails[name]
eb.roll = rolls[name]
for name in heads.keys():
if extras[name] and parents[name]:
Thomas Larsson
committed
eb = rigify.data.edit_bones[name]
eb.parent = rigify.data.edit_bones[parents[name]]
# Copy constraints etc.
bpy.ops.object.mode_set(mode='POSE')
for name in heads.keys():
if extras[name]:
pb1 = mhx.pose.bones[name]
Thomas Larsson
committed
pb2 = rigify.pose.bones[name]
pb2.custom_shape = pb1.custom_shape
pb2.lock_location = pb1.lock_location
pb2.lock_rotation = pb1.lock_rotation
pb2.lock_scale = pb1.lock_scale
b1 = pb1.bone
b2 = pb2.bone
b2.use_deform = b1.use_deform
b2.hide_select = b1.hide_select
b2.show_wire = b1.show_wire
layers = 32*[False]
if b1.layers[8]:
layers[28] = True
else:
layers[29] = True
if b1.layers[10]:
layers[2] = True
b2.layers = layers
for cns1 in pb1.constraints:
Thomas Larsson
committed
cns2 = copyConstraint(cns1, pb1, pb2, mhx, rigify)
Thomas Larsson
committed
rigify.data.bones.active = pb2.bone
bpy.ops.constraint.childof_set_inverse(constraint=cns2.name, owner='BONE')
# Create animation data
if mhx.animation_data:
for fcu in mhx.animation_data.drivers:
Thomas Larsson
committed
rigify.animation_data.drivers.from_existing(src_driver=fcu)
Thomas Larsson
committed
fixDrivers(rigify.animation_data, mhx, rigify)
Thomas Larsson
committed
mesh.parent = rigify
skeys = mesh.data.shape_keys
if skeys:
Thomas Larsson
committed
fixDrivers(skeys.animation_data, mhx, rigify)
scn.objects.unlink(mhx)
print("Rigify rig complete")
return
#
Thomas Larsson
committed
# lineateChain(upbone, first, last, middles, minDist, rig, heads, tails):
# lineate(pt, start, minDist, normal, offVector):
#
Thomas Larsson
committed
def lineateChain(upbone, first, last, middles, minDist, rig, heads, tails):
fb = rig.data.edit_bones[first]
lb = rig.data.edit_bones[last]
uhead = heads[upbone]
utail = tails[upbone]
tang = lb.tail - fb.head
tangent = tang/tang.length
up = (uhead+utail)/2 - fb.head
norm = up - tangent*tangent.dot(up)
normal = norm/norm.length
offVector = tangent.cross(normal)
vec = utail - uhead
fb.tail = lineate(fb.tail, fb.head, minDist, normal, offVector)
lb.head = lineate(lb.head, fb.head, minDist, normal, offVector)
for bone in middles:
Thomas Larsson
committed
mb = rig.data.edit_bones[bone]
mb.head = lineate(mb.head, fb.head, minDist, normal, offVector)
mb.tail = lineate(mb.tail, fb.head, minDist, normal, offVector)
return
def lineate(pt, start, minDist, normal, offVector):
diff = pt - start
diff = diff - offVector*offVector.dot(diff)
dist = diff.dot(normal)
if dist < minDist:
diff += (minDist - dist)*normal
return start + diff
#
Thomas Larsson
committed
# fixDrivers(adata, mhx, rigify):
Thomas Larsson
committed
def fixDrivers(adata, mhx, rigify):
if not adata:
return
for fcu in adata.drivers:
for var in fcu.driver.variables:
for targ in var.targets:
if targ.id == mhx:
Thomas Larsson
committed
targ.id = rigify
Thomas Larsson
committed
# copyConstraint(cns1, pb1, pb2, mhx, rigify):
Thomas Larsson
committed
def copyConstraint(cns1, pb1, pb2, mhx, rigify):
substitute = {
'Head' : 'DEF-head',
'MasterFloor' : 'root',
'upper_arm.L' : 'DEF-upper_arm.L.01',
'upper_arm.R' : 'DEF-upper_arm.R.01',
'thigh.L' : 'DEF-thigh.L.01',
'thigh.R' : 'DEF-thigh.R.01',
'shin.L' : 'DEF-shin.L.01',
'shin.R' : 'DEF-shin.R.01'
}
cns2 = pb2.constraints.new(cns1.type)
for prop in dir(cns1):
if prop == 'target':
if cns1.target == mhx:
Thomas Larsson
committed
cns2.target = rigify
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
else:
cns2.target = cns1.target
elif prop == 'subtarget':
try:
cns2.subtarget = substitute[cns1.subtarget]
except:
cns2.subtarget = cns1.subtarget
elif prop[0] != '_':
try:
expr = "cns2.%s = cns1.%s" % (prop, prop)
#print(pb1.name, expr)
exec(expr)
except:
pass
return cns2
#
# class OBJECT_OT_RigifyMhxButton(bpy.types.Operator):
#
class OBJECT_OT_RigifyMhxButton(bpy.types.Operator):
bl_idname = "mhxrig.rigify_mhx"
bl_label = "Rigify MHX rig"
bl_options = {'UNDO'}
def execute(self, context):
Thomas Larsson
committed
rigifyMhx(context, context.object.name)
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
return{'FINISHED'}
#
# class RigifyMhxPanel(bpy.types.Panel):
#
class RigifyMhxPanel(bpy.types.Panel):
bl_label = "Rigify MHX"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
@classmethod
def poll(cls, context):
if context.object:
try:
return context.object['MhxRigify']
except:
return False
return False
def draw(self, context):
return
###################################################################################
###################################################################################
DEBUG = False
from bpy.props import StringProperty, FloatProperty, EnumProperty, BoolProperty
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
class ErrorOperator(bpy.types.Operator):
bl_idname = "mhx.error"
bl_label = "Error when loading MHX file"
def execute(self, context):
return {'RUNNING_MODAL'}
def invoke(self, context, event):
global theErrorLines
maxlen = 0
for line in theErrorLines:
if len(line) > maxlen:
maxlen = len(line)
width = 20+5*maxlen
height = 20+5*len(theErrorLines)
#self.report({'INFO'}, theMessage)
wm = context.window_manager
return wm.invoke_props_dialog(self, width=width, height=height)
def draw(self, context):
global theErrorLines
for line in theErrorLines:
self.layout.label(line)
def MyError(message):
global theMessage, theErrorLines, theErrorStatus
theMessage = message
theErrorLines = message.split('\n')
theErrorStatus = True
bpy.ops.mhx.error('INVOKE_DEFAULT')
raise MhxError(theMessage)
class MhxError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class SuccessOperator(bpy.types.Operator):
bl_idname = "mhx.success"
bl_label = "MHX file successfully loaded:"
message = StringProperty()
def execute(self, context):
return {'RUNNING_MODAL'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
self.layout.label(self.message + theMessage)
###################################################################################
#
# User interface
#
###################################################################################
Campbell Barton
committed
from bpy_extras.io_utils import ImportHelper
MhxBoolProps = [
("enforce", "Enforce version", "Only accept MHX files of correct version", T_EnforceVersion),
Thomas Larsson
committed
#("crash_safe", "Crash-safe", "Disable features that have caused Blender crashes", T_CrashSafe),
("mesh", "Mesh", "Use main mesh", T_Mesh),
("proxy", "Proxies", "Use proxies", T_Proxy),
("armature", "Armature", "Use armature", T_Armature),
#("replace", "Replace scene", "Replace scene", T_Replace),
("cage", "Cage", "Load mesh deform cage", T_Cage),
("clothes", "Clothes", "Include clothes", T_Clothes),
("face", "Face shapes", "Include facial shapekeys", T_Face),
("shape", "Body shapes", "Include body shapekeys", T_Shape),
#("symm", "Symmetric shapes", "Keep shapekeys symmetric", T_Symm),
("diamond", "Helper geometry", "Keep helper geometry", T_Diamond),
("rigify", "Rigify", "Create rigify control rig", T_Rigify),
]
class ImportMhx(bpy.types.Operator, ImportHelper):
'''Import from MHX file format (.mhx)'''
bl_idname = "import_scene.makehuman_mhx"
bl_description = 'Import from MHX file format (.mhx)'
bl_label = "Import MHX"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
scale = FloatProperty(name="Scale", description="Default meter, decimeter = 1.0", default = theScale)
filename_ext = ".mhx"
filter_glob = StringProperty(default="*.mhx", options={'HIDDEN'})
filepath = StringProperty(subtype='FILE_PATH')
for (prop, name, desc, flag) in MhxBoolProps:
expr = '%s = BoolProperty(name="%s", description="%s", default=toggle&%s)' % (prop, name, desc, flag)
exec(expr)
global toggle, theScale, MhxBoolProps
toggle = 0
for (prop, name, desc, flag) in MhxBoolProps:
expr = '(%s if self.%s else 0)' % (flag, prop)
toggle |= eval(expr)
print("execute flags %x" % toggle)
try:
readMhxFile(self.filepath)
bpy.ops.mhx.success('INVOKE_DEFAULT', message = self.filepath)
except MhxError:
print("Error when loading MHX file:\n" + theMessage)
writeDefaults()
return {'FINISHED'}
def invoke(self, context, event):
global toggle, theScale, MhxBoolProps
readDefaults()
for (prop, name, desc, flag) in MhxBoolProps:
expr = 'self.%s = toggle&%s' % (prop, flag)
exec(expr)
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
###################################################################################
#
# Lipsync panel
#
###################################################################################
#
# visemes
#
stopStaringVisemes = ({
'Rest' : [
('PMouth', (0,0)),
('PUpLip', (0,-0.1)),
('PLoLip', (0,0.1)),
('PJaw', (0,0.05)),
('PTongue', (0,0.0))],
'Etc' : [
('PMouth', (0,0)),
('PUpLip', (0,-0.1)),
('PLoLip', (0,0.1)),
('PJaw', (0,0.15)),
('PTongue', (0,0.0))],
'MBP' : [('PMouth', (-0.3,0)),
('PUpLip', (0,1)),
('PLoLip', (0,0)),
('PJaw', (0,0.1)),
('PTongue', (0,0.0))],
'OO' : [('PMouth', (-1.5,0)),
('PUpLip', (0,0)),
('PLoLip', (0,0)),
('PJaw', (0,0.2)),
('PTongue', (0,0.0))],
'O' : [('PMouth', (-1.1,0)),
('PUpLip', (0,0)),
('PLoLip', (0,0)),
('PJaw', (0,0.5)),
('PTongue', (0,0.0))],
'R' : [('PMouth', (-0.9,0)),
('PUpLip', (0,-0.2)),
('PLoLip', (0,0.2)),
('PJaw', (0,0.2)),
('PTongue', (0,0.0))],
'FV' : [('PMouth', (0,0)),
('PUpLip', (0,0)),
('PLoLip', (0,-0.8)),
('PJaw', (0,0.1)),
('PTongue', (0,0.0))],
'S' : [('PMouth', (0,0)),
('PUpLip', (0,-0.2)),
('PLoLip', (0,0.2)),
('PJaw', (0,0.05)),
('PTongue', (0,0.0))],
'SH' : [('PMouth', (-0.6,0)),
('PUpLip', (0,-0.5)),
('PLoLip', (0,0.5)),
('PJaw', (0,0)),
('PTongue', (0,0.0))],
'EE' : [('PMouth', (0.3,0)),
('PUpLip', (0,-0.3)),
('PLoLip', (0,0.3)),
('PJaw', (0,0.025)),
('PTongue', (0,0.0))],
'AH' : [('PMouth', (-0.1,0)),
('PUpLip', (0,-0.4)),
('PLoLip', (0,0)),
('PJaw', (0,0.35)),
('PTongue', (0,0.0))],
'EH' : [('PMouth', (0.1,0)),
('PUpLip', (0,-0.2)),
('PLoLip', (0,0.2)),
('PJaw', (0,0.2)),
('PTongue', (0,0.0))],
'TH' : [('PMouth', (0,0)),
('PUpLip', (0,-0.5)),
('PLoLip', (0,0.5)),
('PJaw', (-0.2,0.1)),
('PTongue', (0,-0.6))],
'L' : [('PMouth', (0,0)),
('PUpLip', (0,-0.2)),
('PLoLip', (0,0.2)),
('PJaw', (0.2,0.2)),
('PTongue', (0,-0.8))],
'G' : [('PMouth', (0,0)),
('PUpLip', (0,-0.1)),
('PLoLip', (0,0.1)),
('PJaw', (-0.3,0.1)),
('PTongue', (0,-0.6))],
'Blink' : [('PUpLid', (0,1.0)), ('PLoLid', (0,-1.0))],
'Unblink' : [('PUpLid', (0,0)), ('PLoLid', (0,0))],
})
bodyLanguageVisemes = ({
'Rest' : [
('PMouth', (0,0)),
('PMouthMid', (0,-0.6)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'Etc' : [
('PMouth', (0,0)),
('PMouthMid', (0,-0.4)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'MBP' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'OO' : [
('PMouth', (-1.0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0.4)),
('PTongue', (0,0))],
'O' : [
('PMouth', (-0.9,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0.8)),
('PTongue', (0,0))],
'R' : [
('PMouth', (-0.5,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.2)),
('PLoLipMid', (0,0.2)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'FV' : [
('PMouth', (-0.2,0)),
('PMouthMid', (0,1.0)),
('PUpLipMid', (0,0)),
('PLoLipMid', (-0.6,-0.3)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'S' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.5)),
('PLoLipMid', (0,0.7)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'SH' : [
('PMouth', (-0.8,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-1.0)),
('PLoLipMid', (0,1.0)),
('PJaw', (0,0)),
('PTongue', (0,0))],
'EE' : [
('PMouth', (0.2,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.6)),
('PLoLipMid', (0,0.6)),
('PJaw', (0,0.05)),
('PTongue', (0,0))],
'AH' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.4)),
('PLoLipMid', (0,0)),
('PJaw', (0,0.7)),
('PTongue', (0,0))],
'EH' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.5)),
('PLoLipMid', (0,0.6)),
('PJaw', (0,0.25)),
('PTongue', (0,0))],
'TH' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,0)),
('PLoLipMid', (0,0)),
('PJaw', (0,0.2)),
('PTongue', (1.0,1.0))],
'L' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.5)),
('PLoLipMid', (0,0.5)),
('PJaw', (0,-0.2)),
('PTongue', (1.0,1.0))],
'G' : [
('PMouth', (0,0)),
('PMouthMid', (0,0)),
('PUpLipMid', (0,-0.5)),
('PLoLipMid', (0,0.5)),
('PJaw', (0,-0.2)),
('PTongue', (-1.0,0))],
'Blink' : [('PUpLid', (0,1.0)), ('PLoLid', (0,-1.0))],
'Unblink' : [('PUpLid', (0,0)), ('PLoLid', (0,0))],
})
VisemeList = [
('Rest', 'Etc', 'AH'),
('MBP', 'OO', 'O'),
('R', 'FV', 'S'),
('SH', 'EE', 'EH'),
('TH', 'L', 'G')
]
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
#
# makeVisemes(ob, scn):
# class VIEW3D_OT_MhxMakeVisemesButton(bpy.types.Operator):
#
def makeVisemes(ob, scn):
if ob.type != 'MESH':
print("Active object %s is not a mesh" % ob)
return
if not ob.data.shape_keys:
print("%s has no shapekeys" % ob)
return
adata = ob.data.shape_keys.animation_data
if not adata:
print("Shapekeys have not drivers")
return
try:
ob.data.shape_keys.key_blocks["VIS_Rest"]
print("Visemes already created")
return
except:
pass
rig = ob.parent
scale = rig.data.bones['PFace'].length*0.2
boneShapes = {}
for fcu in adata.drivers:
name = fcu.data_path.split('"')[1]
for var in fcu.driver.variables:
if var.type == 'TRANSFORMS':
targ = var.targets[0]
fmod = fcu.modifiers[0]
try:
list = boneShapes[targ.bone_target]
except:
list = []
boneShapes[targ.bone_target] = list
list.append((targ.transform_type, fmod, ob.data.shape_keys.key_blocks[name]))
verts = ob.data.vertices
for (vis,bones) in bodyLanguageVisemes.items():
if vis in ['Blink', 'Unblink']:
continue
vkey = ob.shape_key_add(name="VIS_%s" % vis)
print(vkey.name)
for n,v in enumerate(verts):
vkey.data[n].co = v.co
for (bone, xz) in bones:
try:
boneShapes[bone]
single = True
except:
single = False
if single:
addToVisShapeKey(boneShapes[bone], vkey, verts, xz, 1, scale)
else:
try:
boneShapes[bone+"_L"]
double = True
except:
double = False
if double:
addToVisShapeKey(boneShapes[bone+"_L"], vkey, verts, xz, 1, scale)
#addToVisShapeKey(boneShapes[bone+"_R"], vkey, verts, xz, -1, scale)
print("Visemes made")
return
def addToVisShapeKey(shapes, vkey, verts, xz, sign, scale):
for (typ, fmod, skey) in shapes:
factor = fmod.coefficients[1]*scale
(x,z) = xz
if typ == 'LOC_X':
k = factor*sign*x
elif typ == 'LOC_Z':
k = factor*z
if k < skey.slider_min:
k = skey.slider_min
if k > skey.slider_max:
k = skey.slider_max
if abs(k) < 0.001:
continue
print(" %s %.3f %.3f %.3f %.3f" % (skey.name, factor, x, z, k))
for n,v in enumerate(verts):
vkey.data[n].co += k*(skey.data[n].co - v.co)
return
class VIEW3D_OT_MhxMakeVisemesButton(bpy.types.Operator):
bl_idname = "mhx.make_visemes"
bl_label = "Generate viseme shapekeys"
def execute(self, context):
makeVisemes(context.object, context.scene)
return{'FINISHED'}
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
#
# mohoVisemes
# magpieVisemes
#
mohoVisemes = dict({
'rest' : 'Rest',
'etc' : 'Etc',
'AI' : 'AH',
'O' : 'O',
'U' : 'OO',
'WQ' : 'AH',
'L' : 'L',
'E' : 'EH',
'MBP' : 'MBP',
'FV' : 'FV',
})
magpieVisemes = dict({
"CONS" : "t,d,k,g,T,D,s,z,S,Z,h,n,N,j,r,tS",
"AI" : "i,&,V,aU,I,0,@,aI",
"E" : "eI,3,e",
"O" : "O,@U,oI",
"UW" : "U,u,w",
"MBP" : "m,b,p",
"L" : "l",
"FV" : "f,v",
"Sh" : "dZ",
})
#
# setViseme(context, vis, setKey, frame):
# setBoneLocation(context, pbone, loc, mirror, setKey, frame):
# class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
#
def getVisemeSet(context, rig):
try:
visset = rig['MhxVisemeSet']
except:
return bodyLanguageVisemes
if visset == 'StopStaring':
return stopStaringVisemes
elif visset == 'BodyLanguage':
return bodyLanguageVisemes
else:
raise MhxError("Unknown viseme set %s" % visset)
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
def setViseme(context, vis, setKey, frame):
rig = getMhxRig(context.object)
pbones = rig.pose.bones
try:
scale = pbones['PFace'].bone.length
except:
return
visemes = getVisemeSet(context, rig)
for (b, (x, z)) in visemes[vis]:
loc = mathutils.Vector((float(x),0,float(z)))
try:
pb = pbones[b]
except:
pb = None
if pb:
setBoneLocation(context, pb, scale, loc, False, setKey, frame)
else:
setBoneLocation(context, pbones[b+'_L'], scale, loc, False, setKey, frame)
setBoneLocation(context, pbones[b+'_R'], scale, loc, True, setKey, frame)
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
return
def setBoneLocation(context, pb, scale, loc, mirror, setKey, frame):
if mirror:
loc[0] = -loc[0]
pb.location = loc*scale*0.2
if setKey or context.tool_settings.use_keyframe_insert_auto:
for n in range(3):
pb.keyframe_insert('location', index=n, frame=frame, group=pb.name)
return
class VIEW3D_OT_MhxVisemeButton(bpy.types.Operator):
bl_idname = 'mhx.pose_viseme'
bl_label = 'Viseme'
viseme = StringProperty()
def invoke(self, context, event):
setViseme(context, self.viseme, False, context.scene.frame_current)
return{'FINISHED'}
#
# openFile(context, filepath):
# readMoho(context, filepath, offs):
# readMagpie(context, filepath, offs):
#
def openFile(context, filepath):
(path, fileName) = os.path.split(filepath)
(name, ext) = os.path.splitext(fileName)
return open(filepath, "rU")
def readMoho(context, filepath, offs):
rig = getMhxRig(context.object)
context.scene.objects.active = rig
bpy.ops.object.mode_set(mode='POSE')
fp = openFile(context, filepath)
for line in fp:
words= line.split()
if len(words) < 2:
pass
else:
vis = mohoVisemes[words[1]]
setViseme(context, vis, True, int(words[0])+offs)
fp.close()
setInterpolation(rig)
print("Moho file %s loaded" % filepath)
return
def readMagpie(context, filepath, offs):
rig = getMhxRig(context.object)
context.scene.objects.active = rig
bpy.ops.object.mode_set(mode='POSE')
fp = openFile(context, filepath)
for line in fp:
words= line.split()
if len(words) < 3:
pass
elif words[2] == 'X':
vis = magpieVisemes[words[3]]
setViseme(context, vis, True, int(words[0])+offs)
fp.close()
setInterpolation(rig)
print("Magpie file %s loaded" % filepath)
return
#
# class VIEW3D_OT_MhxLoadMohoButton(bpy.types.Operator):
#
class VIEW3D_OT_MhxLoadMohoButton(bpy.types.Operator):
bl_idname = "mhx.pose_load_moho"
bl_label = "Moho (.dat)"
filepath = StringProperty(subtype='FILE_PATH')
startFrame = IntProperty(name="Start frame", description="First frame to import", default=1)
def execute(self, context):
import bpy, os, mathutils
readMoho(context, self.properties.filepath, self.properties.startFrame-1)
return{'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
#
# class VIEW3D_OT_MhxLoadMagpieButton(bpy.types.Operator):
#
class VIEW3D_OT_MhxLoadMagpieButton(bpy.types.Operator):
bl_idname = "mhx.pose_load_magpie"
bl_label = "Magpie (.mag)"
filepath = StringProperty(subtype='FILE_PATH')
startFrame = IntProperty(name="Start frame", description="First frame to import", default=1)
def execute(self, context):
import bpy, os, mathutils
readMagpie(context, self.properties.filepath, self.properties.startFrame-1)
return{'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
#
# class MhxLipsyncPanel(bpy.types.Panel):
#
class MhxLipsyncPanel(bpy.types.Panel):
bl_label = "MHX Lipsync"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {'DEFAULT_CLOSED'}
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
@classmethod
def poll(cls, context):
return context.object
def draw(self, context):
rig = getMhxRig(context.object)
if not rig:
return
layout = self.layout
layout.label(text="Visemes")
for (vis1, vis2, vis3) in VisemeList:
row = layout.row()
row.operator("mhx.pose_viseme", text=vis1).viseme = vis1
row.operator("mhx.pose_viseme", text=vis2).viseme = vis2
row.operator("mhx.pose_viseme", text=vis3).viseme = vis3
layout.separator()
row = layout.row()
row.operator("mhx.pose_viseme", text="Blink").viseme = 'Blink'
row.operator("mhx.pose_viseme", text="Unblink").viseme = 'Unblink'
layout.label(text="Load file")
row = layout.row()
row.operator("mhx.pose_load_moho")
row.operator("mhx.pose_load_magpie")
layout.operator("mhx.update")
layout.separator()
layout.operator("mhx.make_visemes")
#
# class VIEW3D_OT_MhxUpdateButton(bpy.types.Operator):
#
def updatePose(context):
scn = context.scene
Thomas Larsson
committed
scn.frame_current = scn.frame_current
bpy.ops.object.posemode_toggle()
bpy.ops.object.posemode_toggle()
Loading
Loading full blame...