Saturday, 12 July 2014

Metasequoia Script – Import MilkShape3D *.ms3d

Here is a script that imports a model from a MilkShape3D *.ms3d file.

The script uses Python 3 modules, syntax and objects so can only be used as is with Metasequoia 4.

MS3D model imported into Metasequoia 4



There are two Python scripts.

The first script which must be named “ms3dreader.py” is the script that has the function to read the *.ms3d file and return the data.

“ms3dreader.py” is imported into the main script so must be copied to the same folder as the main script.

# copy this program and save as "ms3dreader.py"
# these are all built in Python modules
import struct
import math
import os
import io # Python 3
def readMS3D(filename, verbose=False):
'''
Reads a MilkShape3D *.ms3d file
Only returns vertices, faces and uvs
Set verbose parameter to True to print info
'''
s = ""
data = {} # store data in dictionary
#print("Opening %s"%filename)
if not os.path.exists(filename):
print("File not found")
return data
with open(filename,"rb") as g: # Python 2.6+
f = io.BytesIO(g.read()) # Python 3
if not f:
return data
bytes_header = f.read(10)
header = bytes_header.decode("ascii") # Python 3
if verbose:
#print ('Header "%s"'%header)
s += 'Magic "%s"\n'%header
if not header == "MS3D000000":
return data
version, = struct.unpack('i',f.read(4))
if verbose:
s += "Version %d\n"%version
if not version == 4:
return data
countV, = struct.unpack('H',f.read(2))
if verbose:
s += "%d vertices\n"%countV
vertices = []
for i in range(countV):
f.read(1)
x,y,z = struct.unpack('3f',f.read(12))
vertices.append([x,y,z])
f.read(2)
data["vertices"] = vertices
countT,= struct.unpack('H',f.read(2))
if verbose:
plural = "s"
if countT ==1:
plural = ""
s += "%d triangle%s\n"%(countT, plural)
faces = []
uvs = []
for i in range(countT):
f.read(2)
v1,v2,v3 = struct.unpack('3H',f.read(6))
faces.append([v1,v2,v3])
f.read(36)
s1,s2,s3 = struct.unpack('3f',f.read(12))
t1,t2,t3 = struct.unpack('3f',f.read(12))
uvs.append([[s1,t1],[s2,t2],[s3,t3]])
f.read(2)
data["faces"] = faces
data["uvs"] = uvs
countG, =struct.unpack('H',f.read(2))
if verbose:
plural = "s"
if countG ==1:
plural = ""
s += "%d group%s\n"%(countG, plural)
for i in range(countG):
f.read(33)
numT, = struct.unpack('H',f.read(2))
for j in range(numT):
f.read(2)
f.read(1)
countM, = struct.unpack('H',f.read(2))
if verbose:
plural = "s"
if countM ==1:
plural = ""
s += "%d material%s\n"%(countM, plural)
for i in range(countM):
f.read(361)
fps, = struct.unpack('f', f.read(4))
current, = struct.unpack('f', f.read(4))
totframes, = struct.unpack('i', f.read(4))
s += "%f fps, time=%f, %d frames\n"%(fps,current,totframes)
countJ, = struct.unpack('H',f.read(2))
if verbose:
plural = "s"
if countJ ==1:
plural = ""
s += "%d joint%s\n"%(countJ, plural)
for i in range(countJ):
f.read(1)
n = f.read(32)
name = n.decode("ascii") # Python 3
name = name.split('\0',1)[0]
if verbose:
s += '"%s"\n'%name
p = f.read(32)
parent = p.decode("ascii") # Python 3
parent = parent.split('\0',1)[0]
if verbose:
s += '\tparent: "%s"\n'%parent
rx,ry,rz = struct.unpack('fff',f.read(12))
if verbose:
s += "\tbind rot (Rx Ry Rz): %f %f %f degrees\n"%(math.degrees(rx),math.degrees(ry),math.degrees(rz))
tx,ty,tz = struct.unpack('fff',f.read(12))
if verbose:
s += "\tbind pos (x y z): %f %f %f\n"%(tx,ty,tz)
countKR, = struct.unpack('H',f.read(2))
if verbose:
plural = "s"
if countKR ==1:
plural = ""
s += "\t%d rotation key%s\n"%(countKR, plural)
countKT, = struct.unpack('H',f.read(2))
for j in range(countKR):
t,rx,ry,rz = struct.unpack('4f',f.read(16))
if verbose and j <20: # maximum of 20 printed
s += "\t\tt=%fs (%f %f %f)\n"%(t,math.degrees(rx),math.degrees(ry),math.degrees(rz))
#s += "\t\tt=%fs (%f %f %f)\n"%(t,rx,ry,rz)
if verbose:
plural = "s"
if countKT ==1:
plural = ""
s += "\t%d translation key%s\n"%(countKT, plural)
for j in range(countKT):
t,x,y,z= struct.unpack('4f',f.read(16))
if verbose and j < 20: # maximum of 20 printed
s += "\t\tt=%fs (%f %f %f)\n"%(t,x,y,z)
more = f.read(4)
if more:
subV, = struct.unpack('i',more)
if verbose:
s += "Comments Subversion = %d\n"%subV
#s += "Subversion data will be skipped\n"
numGC, = struct.unpack('I',f.read(4))
s += "\t%d group comments\n"%numGC
for i in range(numGC):
f.read(4)
length = struct.unpack('i',f.read(4))
f.read(length)
numMaC, = struct.unpack('i',f.read(4))
s += "\t%d material comments\n"%numMaC
for i in range(numMaC):
f.read(4)
length = struct.unpack('i',f.read(4))
f.read(length)
numJC, = struct.unpack('I',f.read(4))
s += "\t%d joint comments\n"%numJC
for i in range(numJC):
f.read(4)
length = struct.unpack('i',f.read(4))
f.read(length)
numMoC, = struct.unpack('I',f.read(4))
s += "\t%d model comments\n"%numMoC
for i in range(numMoC):
f.read(4)
length = struct.unpack('i',f.read(4))
f.read(length)
more = f.read(4)
if more:
subV, = struct.unpack('i',more)
s += "Vertex Extra Subversion = %d\n"%subV
if subV == 1:
if countV >0:
a,b,c = struct.unpack('3b',f.read(3))
s += "\tVert1\n\tBone indices %d %d %d\n"%(a,b,c)
a,b,c = struct.unpack('3B',f.read(3))
s += "\tweights %d %d %d\n"%(a,b,c)
s += "\t...\n"
f.read((countV-1)*6)
elif subV == 2:
if countV >0:
a,b,c = struct.unpack('3b',f.read(3))
s += "\tVert1\n\tBone indices %d %d %d\n"%(a,b,c)
a,b,c = struct.unpack('3B',f.read(3))
s += "\tweights %d %d %d\n"%(a,b,c)
a, = struct.unpack('I',f.read(4))
s += "\textra1 %d\n\t...\n"%(a)
f.read((countV-1)*10)
elif subV == 3:
if countV >0:
a,b,c = struct.unpack('3b',f.read(3))
s += "\tVert1\n\tBone indices %d %d %d\n"%(a,b,c)
a,b,c = struct.unpack('3B',f.read(3))
s += "\tweights %d %d %d\n"%(a,b,c)
a,b = struct.unpack('2I',f.read(8))
s += "\textra1 %d\n\textra2 %d\n\t...\n"%(a,b)
f.read((countV-1)*14)
more = f.read(4)
if more:
subV, = struct.unpack('i',more)
if subV == 1:
s += "Joint Extra Subversion = %d\n"%subV
f.read(countJ*12)
more = f.read(4)
if more:
subV, = struct.unpack('i',more)
s += "Model Extra Subversion = %d\n"%subV
jointSize, = struct.unpack('f',f.read(4))
s += "\tJoint size %f\n"%jointSize
transmode, = struct.unpack('i',f.read(4))
s += "\tTransparency Mode %d\n"%transmode
alphaRef, = struct.unpack('f',f.read(4))
s += "\tAlpha Ref %f\n"%alphaRef
else:
s += "More data exists but unknown Joint Extra Subversion (%d) found\n"%subV
else:
s += "No more data\n"
else:
s += "No more data\n"
else:
s += "No more data\n"
if verbose:
print(s)
return data
if __name__=="__main__":
data = readMS3D("test.ms3d", True) # need a MilkShape3D file named "test.ms3d" in same folder as this script if running standalone
if data:
print("Extracted %d vertices, %d faces, %d uvs"%(len(data["vertices"]),len(data["faces"]),len(data["uvs"])*3))
else:
print("No data")
view raw ms3dreader.py hosted with ❤ by GitHub


The script to open and run in Metasequoia’s Script Editor is listed below.

# Metasequoia 4 Python script
# ReadMS3D.py
'''
Imports a model from a MilkShape3D *.ms3d file
No scaling –> model may be small

'''

# import Python's os.path module so can extract filename
# and file extension from full path
import os.path

# import module that has function to read *.ms3d
# ms3dreader.py must be in same folder as this script
import ms3dreader

# global variables
doc = MQSystem.getDocument()

# function definitions
def mqprint(message):
 try:
  print(message)
 except:
  MQSystem.println(repr(message))
 return


# main script function
def main():

 # create the OpenFile dialog as a child control of Metasequoia
 od = MQWidget.OpenFileDialog(MQWidget.getMainWindow())
 
 # add filter for MilkShape3D files
 od.addFilter("MilkShape3D files (*.ms3d)|*.ms3d")
 
 # good practise to always have All Files option
 od.addFilter("All Files (*.*)|*.*")
 
 # show the dialog
 if od.execute():
  # user selected a file and clicked ok
  
  # store path string in variable named f
  f = od.filename
  
  # extract file's extension using an os.path function
  path,ext = os.path.splitext(f)
  
  
  if ext.lower() in [".ms3d"]:
   # correct extension so extract filename and store in name
   path,name = os.path.split(f) # unused
   
   # print path to file
   mqprint(f)
   
   # read the file
   data = ms3dreader.readMS3D(f,True)
   if not data:
    return
   obj = MQSystem.newObject()
   for v in data["vertices"]:
    obj.addVertex(*v)
   # need to reverse winding of faces otherwise all inverted
   faces = [[t[0],t[2],t[1]] for t in data["faces"]]
   uvs = [[t[0],t[2],t[1]] for t in data["uvs"]]
   
   for t in faces:
    obj.addFace(t)
   for index in range(obj.numFace):
    if index < len(uvs)-1:
     uv = MQSystem.newCoordinate(uvs[index][0][0],uvs[index][0][1])
     obj.face[index].setCoord(0, uv)
     uv = MQSystem.newCoordinate(uvs[index][1][0],uvs[index][1][1])
     obj.face[index].setCoord(1, uv)
     uv = MQSystem.newCoordinate(uvs[index][2][0],uvs[index][2][1])
     obj.face[index].setCoord(2, uv)
    else:
     pass        
   
   # hide objects in document
   for ob in doc.object:
    ob.visible = 0
   idx = doc.addObject(obj)

   # make imported model current object
   doc.currentObjectIndex = idx       
   
  else:
   # user selected incorrect file type
   mqprint("Not an *.ms3d file")
   
 else:
  # user clicked "Cancel" 
  mqprint("No file selected")
 return
 

if __name__ == "__main__":
 # clear output window
 MQSystem.clearLog()
 
 # run main function
 main()
 
 # let user know script finished
 mqprint("Script finished")

 
 

This post discusses some of the basics of using the Script Editor in Metasequoia. [link]



No comments:

Post a Comment