Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- if __name__ != "__main__": exit()
- from tkinter import Tk, messagebox as msg
- from tkinter.simpledialog import askinteger, askstring
- from tkinter.filedialog import askopenfilename, asksaveasfilename
- Tk().withdraw()
- ''' (method examples so i don't need to look at tkinter documentation again)
- askinteger()
- askstring()
- fileNameI = askopenfilename(title="Choose a .obj to convert to .ttm",initialdir=".")
- fileNameO = asksaveasfilename(title="Save As",initialdir=".")
- msg.showinfo(title="cool info",message="??")
- msg.showerror(title="cool error",message="??")
- msg.askyesno(title="?", message="??")
- '''
- from struct import pack
- from math import ceil,log
- from os.path import exists
- from pprint import pprint
- import numpy as np
- def listEval(listI): return [eval(i) for i in listI]
- def loadmtl(fileName):
- if not exists(fileName):
- msg.showerror(
- title=".mtl file is missing!",
- message=("Library \"{}\" could not be found!").format(fileName)
- )
- return None
- try:
- mtlFile=open(fileName,"r")
- mtlLines=mtlFile.read().split("\n")
- mtlFile.close()
- except Exception as err:
- msg.showerror(
- title=("An error occurred while trying to open \"{}\"!").format(fileName),
- message=str(err)+"\n(Is this even a .mtl file?)"
- )
- #print(err)
- return None
- try:
- mtl={"_name_":fileName,"_reverseIndex_":[]}
- for line in mtlLines:
- lineSplit=line.split(" ")
- lineCommand=lineSplit[0]; lineArgs=lineSplit[1:]
- #this doesn't handle or account for texture map options like -bm
- if lineCommand == "newmtl":
- mtlName=lineArgs[0]
- mtl[mtlName]={}; mtl["_reverseIndex_"].append(mtlName)
- elif lineCommand == "Tr": #(Tr)ansparency; inverse of d (alpha)
- mtl[mtlName]["d"]=[1.0-float(lineArgs[0])]
- elif lineCommand[0:1] in ["K","N"] or lineCommand in ["d","Tf","illum"]:
- mtl[mtlName][lineCommand]=listEval(lineArgs)
- elif lineCommand[0:4] == "map_" or lineCommand in ["bump","disp","decal"]:
- mtl[mtlName][lineCommand]=[lineArgs[0]]
- return mtl
- except Exception as err:
- msg.showerror(
- title="Encountered an error while parsing .mtl!",
- message=("Error parsing \"{}\": \"{}\"\n (Is this even a .mtl?)").format(fileName,err)
- )
- return None
- def loadobj(fileName):
- #(relative indexing for faces isn't supported, due to issues with signedness)
- zeroIndexed=True #for face indexes (new_index=original_index-zeroIndexed)
- allowNonTriangles=False
- if not exists(fileName):
- msg.showerror(
- title=".obj file is missing!",
- message=("\"{}\" could not be found!").format(fileName)
- )
- return None
- try:
- objFile=open(fileName,"r")
- objLines=objFile.read().split("\n")
- objFile.close()
- except Exception as err:
- msg.showerror(
- title=("Encountered an error while trying to open \"{}\"!").format(fileName),
- message=("Error: \"{}\"\n (Is this even a .obj?)").format(err)
- )
- return None
- try:
- obj={"_META_":{"_name_":fileName,"_reverseIndex_":[],"mtllib":[], "vMax":0,"vtMax":0,"vnMax":0},
- "v":[],"vt":[],"vn":[]}
- lineCount=1
- for line in objLines:
- lineSplit=line.split(" ")
- lineCommand=lineSplit[0]; lineArgs=lineSplit[1:]
- #group names not supported
- if lineCommand == "o": #(o)bject
- objectName=lineArgs[0]
- obj[objectName]={}; obj["_META_"]["_reverseIndex_"].append(objectName)
- elif lineCommand in ["v","vt","vn"]: #(v)ertex coord., (v)ertex (t)exture coord., (v)ertex (n)ormals
- #put all of the objects' verts in one list, as it seems like they're
- #treated as global values, which every face in every other object can use
- #if not obj.get(lineCommand): obj[lineCommand]=[] (redundant)
- obj[lineCommand].append(listEval(lineArgs))
- elif lineCommand == "f": #(f)ace
- currentFace=[]
- for point in lineArgs:
- p=point.split("/")
- currentFace+=[[int(i or 0) for i in p]]
- currentFace[-1]=[i-zeroIndexed for i in currentFace[-1]]
- #1 at minimum so python won't throw a math domain error by trying log(0,2)
- obj["_META_"]["vMax" ]=max(1, obj["_META_"]["vMax" ], currentFace[-1][0] )
- obj["_META_"]["vtMax"]=max(1, obj["_META_"]["vtMax"], currentFace[-1][1] )
- obj["_META_"]["vnMax"]=max(1, obj["_META_"]["vnMax"], currentFace[-1][2] )
- #print(currentFace[-1])
- if not obj[objectName].get("f"): obj[objectName]["f"]=[]
- obj[objectName]["f"].append(currentFace)
- if len(currentFace) != 3 and not allowNonTriangles:
- msg.showerror(
- title="Non-triangle detected!",
- message=("A face with {} sides was found at line {}!").format(len(currentFace),lineCount)
- )
- return None
- #elif lineCommand == "l": (line elements not supported)
- elif lineCommand == "mtllib":
- #using a list here, so multiple material libraries can be used
- #(problems might occur if a path with spaces is used...)
- obj["_META_"]["mtllib"].append(lineArgs[0])
- elif not lineCommand in ["#",""]:
- obj[objectName][lineCommand]=[lineArgs[0]]
- lineCount+=1
- obj["_META_"]["vBytes" ]=ceil(log(obj["_META_"]["vMax" ],2)/8)
- obj["_META_"]["vtBytes"]=ceil(log(obj["_META_"]["vtMax"],2)/8)
- obj["_META_"]["vnBytes"]=ceil(log(obj["_META_"]["vnMax"],2)/8)
- return obj
- except Exception as err:
- msg.showerror(
- title="Encountered an error while parsing .obj!",
- message=("Error parsing \"{}\": \"{}\"\n (Is this even a .obj?)").format(fileName,err)
- )
- return None
- ''' some info about obj tags:
- "v" = vertex data
- "vt" = texture coordinates (uv coords)
- "vn" = vertex normals
- "Ka" = ambient color
- "Kd" = diffuse color
- "Ks" = specular color
- "Ns" = specular exponent
- "d" = alpha (opaqueness)
- #"Tr" = transparency, inverse of d (alpha); not used
- "Tf" = transmission filter color
- "Ni" = optical density (or, index of refraction)
- "illum" = illumination model
- "map_Ka" = ambient texture map
- "map_Kd" = diffuse texture map
- "map_Ks" = specular color texture map
- "map_Ns" = specular highlight component
- "map_d" = alpha texture map
- "map_bump" = bump map
- "bump" = same as map_bump
- "disp" = displacement map
- "decal" = stencil decal texture
- "Ke" = emissive color (this is a format extension iirc)
- "s" = smooth shading
- '''
- '''
- fileNameI = askopenfilename(title="Choose a .obj to convert to .ttm",initialdir=".")
- if fileNameI=="": exit()
- fileNameO=savettm(loadobj(fileNameI)) #automatically converts linked .mtl(s)
- if fileNameO:
- msg.showinfo(
- title="Success!",
- message=("Converted file successfully saved as \"{}\"").format(fileNameO)
- )
- '''
- def float2fixed_single(floatValue): return int((floatValue*256)+0.5).to_bytes(4,"little",signed=True)
- def float2fixed_list(listI): return [float2fixed_single(i) for i in listI]
- def int2bytes_single(intValue,blen=4,s=False): return intValue.to_bytes(blen,"little",signed=s)
- def int2bytes_list(listI,blen=4,s=False): return [int2bytes_single(i,blen,s) for i in listI]
- def changeFileExtension(name,ext): return ".".join(name.split(".")[:-1]+[ext])
- #unused for now
- #def bin2byte(b,l=1,s=False): return int(b,2).to_bytes(l,"little",signed=s)
- def getFaceNormal(a, b, c):
- #move points b&c to origin relative to a
- u = np.subtract(b, a)
- v = np.subtract(c, a)
- #get surface normal vector via cross product
- n = (
- u[1]*v[2] - u[2]*v[1],
- u[2]*v[0] - u[0]*v[2],
- u[0]*v[1] - u[1]*v[0],
- )
- #magnitude of normal vector
- w = (n[0]**2 + n[1]**2 + n[2]**2)**.5 #sqrt(a^2 + b^2 + c^2)
- #unit surface normal
- t = np.divide(n,w)
- return list(t)
- def getMidpoint(a, b, c):
- return [(a[0]+b[0]+c[0])/3, (a[1]+b[1]+c[1])/3, (a[1]+b[1]+c[1])/3]
- '''
- struct fx_vec3 {
- fx16_8 x,y,z;
- };
- struct Triangle {
- fx_vec3 norm;
- fx_vec3 mid;
- u32 a,b,c;
- Color color;
- };
- struct HeaderFSM {
- u32 magic; // = "FSM\x00" (0x004D5346)
- fx16_8 scale; //a multiplier for the model's default scale
- u32 verts_len; //number of total vertices
- u32 tris_len; //number of total triangles
- fx_vec3* verts; //are offsets when stored in fsm files; must be
- Triangle* tris; //+= with the struct's address to use as a pointer
- };
- '''
- def createTriangle(faceData, vertices):
- #face index data goes as: vertex, texture coordinate (UV), normal vector
- #the only one we're after is the vertex data, as neither uv coords or vertex
- #normals are used. however, a FACE normal is calculated using the vertex data
- vertIndexes = [ faceData[0][0], faceData[1][0], faceData[2][0] ]
- vertex_a = vertices[vertIndexes[0]]
- vertex_b = vertices[vertIndexes[1]]
- vertex_c = vertices[vertIndexes[2]]
- faceNorm = getFaceNormal(vertex_a, vertex_b, vertex_c)
- midpoint = getMidpoint(vertex_a, vertex_b, vertex_c)
- faceNormBytes = float2fixed_list(faceNorm)
- midpointBytes = float2fixed_list(midpoint)
- vertIndexBytes = int2bytes_list(vertIndexes)
- colorBytes = [ b'\xCC\xCC\xCC\xCC' ] #tbd: i'll implement this later
- print(faceNormBytes + midpointBytes + vertIndexBytes + colorBytes)
- return faceNormBytes + midpointBytes + vertIndexBytes + colorBytes
- def getFsmData(objFileData, scalingFactor = 1.0):
- metaInfo = objFileData["_META_"] #information from the obj that is inferred rather than read
- vBytes = metaInfo["vBytes" ] #the minimum number of bytes needed to store vertice indexes
- vMax = metaInfo["vMax" ] #the highest vertice index (its length - 1)
- name = metaInfo["_reverseIndex_"][0] #get first object in the file data
- vertsRaw = objFileData["v"] #list of float triplets (which is also a list)
- facesRaw = objFileData[name]["f"] #list of lists of integer triplets (which is also a list)
- vertsSize = len(vertsRaw) * 4 * 3 #equal to the number of vertices, * sizeof(fx16_8) * 3 axes
- #pprint(objFileData) #debug thingy
- if vBytes > 4: #if you somehow go higher than this, you're doing something terribly wrong
- msg.showerror(
- title = "Encountered an error while converting obj data to fsm!",
- message = ("Error converting \"{}\": \"{}\"\n").format(fileName, "vBytes > 4")
- ); return None
- magic = [ b'FSM\x00' ] #magic number (file signature)
- scale = [ float2fixed_single(scalingFactor) ] #global scale multiplier
- verts_len = [ int2bytes_single(len(vertsRaw)) ] #number of vertices
- tris_len = [ int2bytes_single(len(facesRaw)) ] #number of triangles
- verts = [ int2bytes_single(32, 8) ] #offset to the start of vertex data
- tris = [ int2bytes_single(32+vertsSize, 8) ] #offset to the start of triangle data
- verts_bytes = []
- tris_bytes = []
- for vert in vertsRaw: verts_bytes += float2fixed_list(vert)
- for face in facesRaw: tris_bytes += createTriangle(face, vertsRaw)
- #next destination: concatenation station (LMAOOO)
- return magic + scale + verts_len + tris_len + verts + tris+ verts_bytes + tris_bytes
- fileNameI = askopenfilename(title="Choose a .obj to convert to .fsm", initialdir=".")
- if fileNameI=="": exit()
- fileNameO = changeFileExtension(fileNameI,"fsm")
- dataI = loadobj(fileNameI)
- if dataI == None: exit()
- dataO = getFsmData(dataI)
- if dataO == None: exit()
- try:
- with open(fileNameO, "wb") as fileO:
- for chunk in dataO:
- fileO.write(chunk)
- msg.showinfo(
- title="Success!",
- message=("Converted file successfully saved as \"{}\"").format(fileNameO)
- )
- except Exception as err:
- msg.showerror(
- title = "Encountered an error while writing fsm data to file!",
- message = ("Error writing to \"{}\": \"{}\"\n").format(fileNameO, str(err))
- )
Advertisement
Comments
-
- thanks for uploading the code, this saves us some time, not every time does gpt give good code advices..
Add Comment
Please, Sign In to add comment
Advertisement