clintonman
03-13-2012, 12:19 AM
This script is a WIP, but I can import some meshes. The head shows the lostsoul mesh which is the simplest one I could find in the game. All the surfaces were recreated by hand using textures from the game. The script creates the surfaces and names them but that's all it does. The man shown in the second image causes the script to error out because of not enough empty layers or something like that.
My first goal is to fix the errors, add error catching to the code and use better variable names. After that is skeleton import in Layout and then animation import. The final goal is to have full skeletal mesh and animation export.
#! /usr/bin/env python
# -*- Mode: Python -*-
# -*- coding: ascii -*-
"""
This is a LightWave Command Sequence plug-in (Modeler) that
loads an md5mesh file
"""
import sys
import math
import lwsdk
__author__ = "Clinton Reese"
__date__ = "Mar 7 2012"
__copyright__ = "Copyright (C) 2012 Clinton's 3D Creations"
__version__ = "1.0"
__maintainer__ = "Clinton Reese"
__email__ = "[email protected]"
__status__ = "Example modified 3"
__lwver__ = "11"
#position
class xyz(object):
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
#quaternion rotation
class xyzw(object):
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
#calculate quaternion w term
wq = 1.0 - x*x - y*y - z*z
if wq < 0.0:
self.w = 0.0
else:
self.w = -math.sqrt(wq)
class joint(object):
def __init__(self,dataline):
self.name = dataline[0].strip('"')
self.parentindex = int(dataline[1])
self.position = xyz(float(dataline[3]),float(dataline[4]),float(dataline[5]))
self.rotation = xyzw(float(dataline[8]),float(dataline[9]),float(dataline[10]))
#vertex data - uv values, weight index and number of consecutive weights influencing the vertex
class vert(object):
def __init__(self,dataline):
self.u = float(dataline[3])
self.v = float(dataline[4])
self.weightIndex = int(dataline[6])
self.numWeights = int(dataline[7])
#mesh triangles vertex indices
class tri(object):
def __init__(self,dataline):
self.i = int(dataline[2])
self.j = int(dataline[3])
self.k = int(dataline[4])
#vertex weight value from the joint and xyz position relative to the joint position
class weight(object):
def __init__(self,dataline):
self.jointindex = int(dataline[2])
self.weight = float(dataline[3])
self.x = float(dataline[5])
self.y = float(dataline[6])
self.z = float(dataline[7])
class import_md5mesh(lwsdk.ICommandSequence):
def __init__(self, context):
super(import_md5mesh, self).__init__()
self._filepath = ""
def createSkelegons(self, joints, mod_command):
edit_op_result = lwsdk.EDERR_NONE
# /LightWave11.0/sdk/lwsdk11.0/html/classes/me.html
# /LightWave11.0/sdk/lwsdk11.0/include/lwmeshedt.h
mesh_edit_op = mod_command.editBegin(0, 0, lwsdk.OPSEL_USER)
#TODO: fix fat mesh error
#TODO: new lwo on load
#TODO: better ui?
#TODO: any way to auto install the script?
#TODO: document locations in html docs
#TODO: **** error checking *****
if not mesh_edit_op:
print >>sys.stderr, 'Failed to engage mesh edit operations!'
# return lwsdk.AFUNC_OK
allptID = [] #LWPntID
for j in joints:
pt = [j.position.x, j.position.y, j.position.z]
allptID.append(mesh_edit_op.addPoint(mesh_edit_op. state, pt))
if j.parentindex != -1:
skelptID = []
skelptID.append(allptID[j.parentindex])
skelptID.append(allptID[-1])
#note: looks like skelegon names go into Parts
#problem is joints != bones so don't develope skelegons
# or use fixed size floating bones and use orientation data
# no need a meaningful heirarchy
polygon = mesh_edit_op.addPoly(mesh_edit_op.state, lwsdk.LWPOLTYPE_BONE, None, "Default", skelptID)
#didn't work - got parts but skelegons still called "Bone 0"
mesh_edit_op.polTag(mesh_edit_op.state, polygon, lwsdk.LWPTAG_PART, j.name+"SUB")
print 'joint: ',j.name
mesh_edit_op.done(mesh_edit_op.state, edit_op_result, 0)
def createMesh(self, shader, meshname, joints, verts, tris, weights, mod_command):
edit_op_result = lwsdk.EDERR_NONE
allptID = []
mesh_edit_op = mod_command.editBegin(0, 0, lwsdk.OPSEL_USER)
for j in verts:
weightCount = j.numWeights
posX = 0.0
posY = 0.0
posZ = 0.0
while weightCount > 0:
weightCount -= 1
#get weight index and position values
wtX = weights[j.weightIndex + weightCount].x
wtY = weights[j.weightIndex + weightCount].y
wtZ = weights[j.weightIndex + weightCount].z
jointIndex = weights[j.weightIndex + weightCount].jointindex
weightValue = weights[j.weightIndex + weightCount].weight
#get joint rotations
qx = joints[jointIndex].rotation.x
qy = joints[jointIndex].rotation.y
qz = joints[jointIndex].rotation.z
qw = joints[jointIndex].rotation.w
#quaternion rotation matrix
rm11=1.0 - 2.0*qy*qy - 2.0*qz*qz
rm12=2.0*qx*qy - 2.0*qw*qz
rm13=2.0*qx*qz+2.0*qw*qy
rm21=2.0*qx*qy+2.0*qw*qz
rm22=1.0 - 2.0*qx*qx - 2.0*qz*qz
rm23=2.0*qy*qz - 2.0*qw*qx
rm31=2.0*qx*qz - 2.0*qw*qy
rm32=2.0*qy*qz+2.0*qw*qx
rm33=1.0 - 2.0*qx*qx - 2.0*qy*qy
#rotate point position relative to joint
#rposX = rm11*wtX + rm21*wtY + rm31*wtZ
#rposY = rm12*wtX + rm22*wtY + rm32*wtZ
#rposZ = rm13*wtX + rm23*wtY + rm33*wtZ
rposX = rm11*wtX + rm12*wtY + rm13*wtZ
rposY = rm21*wtX + rm22*wtY + rm23*wtZ
rposZ = rm31*wtX + rm32*wtY + rm33*wtZ # inverse rotation
#final position is the weighted sum of the vertex relative to each joint that influences it
posX = posX +(joints[jointIndex].position.x + rposX)*weightValue;
posY = posY +(joints[jointIndex].position.y + rposY)*weightValue;
posZ = posZ +(joints[jointIndex].position.z + rposZ)*weightValue;
pt = [posX, posY, posZ]
allptID.append(mesh_edit_op.addPoint(mesh_edit_op. state, pt))
#set uv texture map values, note that the v coordinate is reversed
#the final result looks creased down the center so maybe more adjustment needed?
uv = []
uv.append(j.u)
uv.append(1.0-j.v)
mesh_edit_op.pntVMap(mesh_edit_op.state, allptID[-1], lwsdk.LWVMAP_TXUV, meshname+"_UV", uv)
#create weight maps for each joint
weightCount = j.numWeights
while weightCount > 0:
weightCount -= 1
jointIndex = weights[j.weightIndex + weightCount].jointindex
weightValue = weights[j.weightIndex + weightCount].weight
jointName = joints[jointIndex].name
mesh_edit_op.pntVMap(mesh_edit_op.state, allptID[-1], lwsdk.LWVMAP_WGHT, jointName+"_WT", weightValue)
# create the triangle mesh faces
for t in tris:
polyptID = []
polyptID.append(allptID[t.k])
polyptID.append(allptID[t.j])
polyptID.append(allptID[t.i])
polygon = mesh_edit_op.addPoly(mesh_edit_op.state, lwsdk.LWPOLTYPE_FACE, None, shader, polyptID)
mesh_edit_op.done(mesh_edit_op.state, edit_op_result, 0)
def get_commands(self, mod_command):
command_list = {} #LWCommandCode
# /LightWave11.0/sdk/lwsdk11.0/html/commands/modeler.html
for command in ["SETLAYER",
"SETLAYERNAME"]:
command_list[command] = mod_command.lookup(mod_command.data, command)
return command_list
# LWCommandSequence -----------------------------------
def process(self, mod_command):
# panels
# /LightWave11.0/sdk/lwsdk11.0/html/globals/panel.html
# /LightWave11.0/sdk/lwsdk11.0/include/lwpanel.h
ui = lwsdk.LWPanels()
panel = ui.create('MD5Mesh Import')
controlWidth = 128
c1 = panel.load_ctl('Select File',controlWidth)
#c1 = panel.loadbutton_ctl('LoadIt',controlWidth)
c1.set_str(self._filepath)
if panel.open(lwsdk.PANF_BLOCKING | lwsdk.PANF_CANCEL) == 0:
ui.destroy(panel)
return lwsdk.AFUNC_OK
self._filepath = c1.get_str()
print self._filepath
fileObject = open(self._filepath)
fileline = fileObject.readline()
if fileline != 'MD5Version 10\n':
print >>sys.stderr, 'Not a valid MD5 version 10 file!'
print fileline
return lwsdk.AFUNC_OK
cs_dict = self.get_commands(mod_command)
# limited to 4 layers??
empty_layers = lwsdk.LWStateQueryFuncs().layerList(lwsdk.OPLYR_EM PTY, None)
empty_layers_list = empty_layers.split()
joints = []
jointmode = False
meshmode = False
meshbegin = False
meshname = ''
shader = ''
layerindex = 0
numverts = 0
numtris = 0
numweights = 0
verts = []
tris = []
weights = []
for fileline in fileObject:
if fileline == '\n': continue
dataline = fileline.split()
if dataline[0] == 'numJoints':
numJoints = dataline[1]
print 'numJoints = ', numJoints
if dataline[0] == "numMeshes":
numMeshes = dataline[1]
print 'numMeshes = ', numMeshes
if fileline.startswith('}'):
if jointmode:
self.createSkelegons(joints, mod_command)
if meshmode:
self.createMesh(shader, meshname, joints, verts, tris, weights, mod_command)
print 'Exit mesh mode'
jointmode = False
meshmode = False
if jointmode:
joints.append(joint(dataline))
if meshmode:
if meshbegin:
meshbegin = False
if dataline[1] == 'meshes:':
meshname = dataline[2]
layerindex += 1
cs_options = lwsdk.marshall_dynavalues(empty_layers_list[layerindex])
result = mod_command.execute(mod_command.data, cs_dict["SETLAYER"], cs_options, lwsdk.OPSEL_USER)
cs_options = lwsdk.marshall_dynavalues(meshname)
result = mod_command.execute(mod_command.data, cs_dict["SETLAYERNAME"], cs_options, lwsdk.OPSEL_USER)
if dataline[0] == 'shader':
shader = dataline[1].strip('"')
print shader
if dataline[0] == 'numverts':
numverts = dataline[1]
print 'numverts ',numverts
if dataline[0] == 'vert':
verts.append(vert(dataline))
if dataline[0] == 'numtris':
numtris = dataline[1]
print 'numtris ',numtris
if dataline[0] == 'tri':
tris.append(tri(dataline))
if dataline[0] == 'numweights':
numweights = dataline[1]
print 'numweights ',numweights
if dataline[0] == 'weight':
weights.append(weight(dataline))
#read joint data
if fileline.startswith('joint'):
jointmode = True
cs_options = lwsdk.marshall_dynavalues(empty_layers_list[layerindex])
result = mod_command.execute(mod_command.data, cs_dict["SETLAYER"], cs_options, lwsdk.OPSEL_USER)
cs_options = lwsdk.marshall_dynavalues('theSkelegons')
result = mod_command.execute(mod_command.data, cs_dict["SETLAYERNAME"], cs_options, lwsdk.OPSEL_USER)
print 'enter joint mode'
#read mesh data
if fileline.startswith('mesh'):
meshmode = True
meshbegin= True
verts = []
tris = []
weights = []
print 'enter mesh mode'
fileObject.close()
print 'import complete'
return lwsdk.AFUNC_OK
# /LightWave11.0/sdk/lwsdk11.0/html/server.html
ServerTagInfo = [
( "Python ImportMD5Mesh", lwsdk.SRVTAG_USERNAME | lwsdk.LANGID_USENGLISH ),
( "Import MD5Mesh", lwsdk.SRVTAG_BUTTONNAME | lwsdk.LANGID_USENGLISH ),
( "Utilities/Python", lwsdk.SRVTAG_MENU | lwsdk.LANGID_USENGLISH )
]
ServerRecord = { lwsdk.CommandSequenceFactory("LW_PyImportMD5Mesh", import_md5mesh) : ServerTagInfo }
My first goal is to fix the errors, add error catching to the code and use better variable names. After that is skeleton import in Layout and then animation import. The final goal is to have full skeletal mesh and animation export.
#! /usr/bin/env python
# -*- Mode: Python -*-
# -*- coding: ascii -*-
"""
This is a LightWave Command Sequence plug-in (Modeler) that
loads an md5mesh file
"""
import sys
import math
import lwsdk
__author__ = "Clinton Reese"
__date__ = "Mar 7 2012"
__copyright__ = "Copyright (C) 2012 Clinton's 3D Creations"
__version__ = "1.0"
__maintainer__ = "Clinton Reese"
__email__ = "[email protected]"
__status__ = "Example modified 3"
__lwver__ = "11"
#position
class xyz(object):
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
#quaternion rotation
class xyzw(object):
def __init__(self,x,y,z):
self.x = x
self.y = y
self.z = z
#calculate quaternion w term
wq = 1.0 - x*x - y*y - z*z
if wq < 0.0:
self.w = 0.0
else:
self.w = -math.sqrt(wq)
class joint(object):
def __init__(self,dataline):
self.name = dataline[0].strip('"')
self.parentindex = int(dataline[1])
self.position = xyz(float(dataline[3]),float(dataline[4]),float(dataline[5]))
self.rotation = xyzw(float(dataline[8]),float(dataline[9]),float(dataline[10]))
#vertex data - uv values, weight index and number of consecutive weights influencing the vertex
class vert(object):
def __init__(self,dataline):
self.u = float(dataline[3])
self.v = float(dataline[4])
self.weightIndex = int(dataline[6])
self.numWeights = int(dataline[7])
#mesh triangles vertex indices
class tri(object):
def __init__(self,dataline):
self.i = int(dataline[2])
self.j = int(dataline[3])
self.k = int(dataline[4])
#vertex weight value from the joint and xyz position relative to the joint position
class weight(object):
def __init__(self,dataline):
self.jointindex = int(dataline[2])
self.weight = float(dataline[3])
self.x = float(dataline[5])
self.y = float(dataline[6])
self.z = float(dataline[7])
class import_md5mesh(lwsdk.ICommandSequence):
def __init__(self, context):
super(import_md5mesh, self).__init__()
self._filepath = ""
def createSkelegons(self, joints, mod_command):
edit_op_result = lwsdk.EDERR_NONE
# /LightWave11.0/sdk/lwsdk11.0/html/classes/me.html
# /LightWave11.0/sdk/lwsdk11.0/include/lwmeshedt.h
mesh_edit_op = mod_command.editBegin(0, 0, lwsdk.OPSEL_USER)
#TODO: fix fat mesh error
#TODO: new lwo on load
#TODO: better ui?
#TODO: any way to auto install the script?
#TODO: document locations in html docs
#TODO: **** error checking *****
if not mesh_edit_op:
print >>sys.stderr, 'Failed to engage mesh edit operations!'
# return lwsdk.AFUNC_OK
allptID = [] #LWPntID
for j in joints:
pt = [j.position.x, j.position.y, j.position.z]
allptID.append(mesh_edit_op.addPoint(mesh_edit_op. state, pt))
if j.parentindex != -1:
skelptID = []
skelptID.append(allptID[j.parentindex])
skelptID.append(allptID[-1])
#note: looks like skelegon names go into Parts
#problem is joints != bones so don't develope skelegons
# or use fixed size floating bones and use orientation data
# no need a meaningful heirarchy
polygon = mesh_edit_op.addPoly(mesh_edit_op.state, lwsdk.LWPOLTYPE_BONE, None, "Default", skelptID)
#didn't work - got parts but skelegons still called "Bone 0"
mesh_edit_op.polTag(mesh_edit_op.state, polygon, lwsdk.LWPTAG_PART, j.name+"SUB")
print 'joint: ',j.name
mesh_edit_op.done(mesh_edit_op.state, edit_op_result, 0)
def createMesh(self, shader, meshname, joints, verts, tris, weights, mod_command):
edit_op_result = lwsdk.EDERR_NONE
allptID = []
mesh_edit_op = mod_command.editBegin(0, 0, lwsdk.OPSEL_USER)
for j in verts:
weightCount = j.numWeights
posX = 0.0
posY = 0.0
posZ = 0.0
while weightCount > 0:
weightCount -= 1
#get weight index and position values
wtX = weights[j.weightIndex + weightCount].x
wtY = weights[j.weightIndex + weightCount].y
wtZ = weights[j.weightIndex + weightCount].z
jointIndex = weights[j.weightIndex + weightCount].jointindex
weightValue = weights[j.weightIndex + weightCount].weight
#get joint rotations
qx = joints[jointIndex].rotation.x
qy = joints[jointIndex].rotation.y
qz = joints[jointIndex].rotation.z
qw = joints[jointIndex].rotation.w
#quaternion rotation matrix
rm11=1.0 - 2.0*qy*qy - 2.0*qz*qz
rm12=2.0*qx*qy - 2.0*qw*qz
rm13=2.0*qx*qz+2.0*qw*qy
rm21=2.0*qx*qy+2.0*qw*qz
rm22=1.0 - 2.0*qx*qx - 2.0*qz*qz
rm23=2.0*qy*qz - 2.0*qw*qx
rm31=2.0*qx*qz - 2.0*qw*qy
rm32=2.0*qy*qz+2.0*qw*qx
rm33=1.0 - 2.0*qx*qx - 2.0*qy*qy
#rotate point position relative to joint
#rposX = rm11*wtX + rm21*wtY + rm31*wtZ
#rposY = rm12*wtX + rm22*wtY + rm32*wtZ
#rposZ = rm13*wtX + rm23*wtY + rm33*wtZ
rposX = rm11*wtX + rm12*wtY + rm13*wtZ
rposY = rm21*wtX + rm22*wtY + rm23*wtZ
rposZ = rm31*wtX + rm32*wtY + rm33*wtZ # inverse rotation
#final position is the weighted sum of the vertex relative to each joint that influences it
posX = posX +(joints[jointIndex].position.x + rposX)*weightValue;
posY = posY +(joints[jointIndex].position.y + rposY)*weightValue;
posZ = posZ +(joints[jointIndex].position.z + rposZ)*weightValue;
pt = [posX, posY, posZ]
allptID.append(mesh_edit_op.addPoint(mesh_edit_op. state, pt))
#set uv texture map values, note that the v coordinate is reversed
#the final result looks creased down the center so maybe more adjustment needed?
uv = []
uv.append(j.u)
uv.append(1.0-j.v)
mesh_edit_op.pntVMap(mesh_edit_op.state, allptID[-1], lwsdk.LWVMAP_TXUV, meshname+"_UV", uv)
#create weight maps for each joint
weightCount = j.numWeights
while weightCount > 0:
weightCount -= 1
jointIndex = weights[j.weightIndex + weightCount].jointindex
weightValue = weights[j.weightIndex + weightCount].weight
jointName = joints[jointIndex].name
mesh_edit_op.pntVMap(mesh_edit_op.state, allptID[-1], lwsdk.LWVMAP_WGHT, jointName+"_WT", weightValue)
# create the triangle mesh faces
for t in tris:
polyptID = []
polyptID.append(allptID[t.k])
polyptID.append(allptID[t.j])
polyptID.append(allptID[t.i])
polygon = mesh_edit_op.addPoly(mesh_edit_op.state, lwsdk.LWPOLTYPE_FACE, None, shader, polyptID)
mesh_edit_op.done(mesh_edit_op.state, edit_op_result, 0)
def get_commands(self, mod_command):
command_list = {} #LWCommandCode
# /LightWave11.0/sdk/lwsdk11.0/html/commands/modeler.html
for command in ["SETLAYER",
"SETLAYERNAME"]:
command_list[command] = mod_command.lookup(mod_command.data, command)
return command_list
# LWCommandSequence -----------------------------------
def process(self, mod_command):
# panels
# /LightWave11.0/sdk/lwsdk11.0/html/globals/panel.html
# /LightWave11.0/sdk/lwsdk11.0/include/lwpanel.h
ui = lwsdk.LWPanels()
panel = ui.create('MD5Mesh Import')
controlWidth = 128
c1 = panel.load_ctl('Select File',controlWidth)
#c1 = panel.loadbutton_ctl('LoadIt',controlWidth)
c1.set_str(self._filepath)
if panel.open(lwsdk.PANF_BLOCKING | lwsdk.PANF_CANCEL) == 0:
ui.destroy(panel)
return lwsdk.AFUNC_OK
self._filepath = c1.get_str()
print self._filepath
fileObject = open(self._filepath)
fileline = fileObject.readline()
if fileline != 'MD5Version 10\n':
print >>sys.stderr, 'Not a valid MD5 version 10 file!'
print fileline
return lwsdk.AFUNC_OK
cs_dict = self.get_commands(mod_command)
# limited to 4 layers??
empty_layers = lwsdk.LWStateQueryFuncs().layerList(lwsdk.OPLYR_EM PTY, None)
empty_layers_list = empty_layers.split()
joints = []
jointmode = False
meshmode = False
meshbegin = False
meshname = ''
shader = ''
layerindex = 0
numverts = 0
numtris = 0
numweights = 0
verts = []
tris = []
weights = []
for fileline in fileObject:
if fileline == '\n': continue
dataline = fileline.split()
if dataline[0] == 'numJoints':
numJoints = dataline[1]
print 'numJoints = ', numJoints
if dataline[0] == "numMeshes":
numMeshes = dataline[1]
print 'numMeshes = ', numMeshes
if fileline.startswith('}'):
if jointmode:
self.createSkelegons(joints, mod_command)
if meshmode:
self.createMesh(shader, meshname, joints, verts, tris, weights, mod_command)
print 'Exit mesh mode'
jointmode = False
meshmode = False
if jointmode:
joints.append(joint(dataline))
if meshmode:
if meshbegin:
meshbegin = False
if dataline[1] == 'meshes:':
meshname = dataline[2]
layerindex += 1
cs_options = lwsdk.marshall_dynavalues(empty_layers_list[layerindex])
result = mod_command.execute(mod_command.data, cs_dict["SETLAYER"], cs_options, lwsdk.OPSEL_USER)
cs_options = lwsdk.marshall_dynavalues(meshname)
result = mod_command.execute(mod_command.data, cs_dict["SETLAYERNAME"], cs_options, lwsdk.OPSEL_USER)
if dataline[0] == 'shader':
shader = dataline[1].strip('"')
print shader
if dataline[0] == 'numverts':
numverts = dataline[1]
print 'numverts ',numverts
if dataline[0] == 'vert':
verts.append(vert(dataline))
if dataline[0] == 'numtris':
numtris = dataline[1]
print 'numtris ',numtris
if dataline[0] == 'tri':
tris.append(tri(dataline))
if dataline[0] == 'numweights':
numweights = dataline[1]
print 'numweights ',numweights
if dataline[0] == 'weight':
weights.append(weight(dataline))
#read joint data
if fileline.startswith('joint'):
jointmode = True
cs_options = lwsdk.marshall_dynavalues(empty_layers_list[layerindex])
result = mod_command.execute(mod_command.data, cs_dict["SETLAYER"], cs_options, lwsdk.OPSEL_USER)
cs_options = lwsdk.marshall_dynavalues('theSkelegons')
result = mod_command.execute(mod_command.data, cs_dict["SETLAYERNAME"], cs_options, lwsdk.OPSEL_USER)
print 'enter joint mode'
#read mesh data
if fileline.startswith('mesh'):
meshmode = True
meshbegin= True
verts = []
tris = []
weights = []
print 'enter mesh mode'
fileObject.close()
print 'import complete'
return lwsdk.AFUNC_OK
# /LightWave11.0/sdk/lwsdk11.0/html/server.html
ServerTagInfo = [
( "Python ImportMD5Mesh", lwsdk.SRVTAG_USERNAME | lwsdk.LANGID_USENGLISH ),
( "Import MD5Mesh", lwsdk.SRVTAG_BUTTONNAME | lwsdk.LANGID_USENGLISH ),
( "Utilities/Python", lwsdk.SRVTAG_MENU | lwsdk.LANGID_USENGLISH )
]
ServerRecord = { lwsdk.CommandSequenceFactory("LW_PyImportMD5Mesh", import_md5mesh) : ServerTagInfo }