The script uses Python 3 modules, syntax and objects so can only be used as is with 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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") |
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