Spaces:
Sleeping
Sleeping
| #!/usr/bin/python | |
| """ | |
| This Version: $Id: obj2egg.py,v 1.7 2008/05/26 17:42:53 andyp Exp $ | |
| Info: info >at< pfastergames.com | |
| Extended from: http://panda3d.org/phpbb2/viewtopic.php?t=3378 | |
| .___..__ .___.___.___.__..__ . . | |
| | [__)[__ [__ [__ | |[__)|\/| | |
| | | \[___[___| |__|| \| | | |
| obj2egg.py [n##][b][t][s] filename1.obj ... | |
| -n regenerate normals with # degree smoothing | |
| exaple -n30 (normals at less 30 degrees will be smoothed) | |
| -b make binarmals | |
| -t make tangents | |
| -s show in pview | |
| licensed under WTFPL (http://sam.zoy.org/wtfpl/) | |
| """ | |
| from pandac.PandaModules import * | |
| import math | |
| import string | |
| import getopt | |
| import sys, os | |
| def floats(float_list): | |
| """coerce a list of strings that represent floats into a list of floats""" | |
| return [ float(number) for number in float_list ] | |
| def ints(int_list): | |
| """coerce a list of strings that represent integers into a list of integers""" | |
| return [ int(number) for number in int_list ] | |
| class ObjMaterial: | |
| """a wavefront material""" | |
| def __init__(self): | |
| self.filename = None | |
| self.name = "default" | |
| self.eggdiffusetexture = None | |
| self.eggmaterial = None | |
| self.attrib = {} | |
| self.attrib["Ns"] = 100.0 | |
| self.attrib["d"] = 1.0 | |
| self.attrib["illum"] = 2 | |
| # "magenta" | |
| self.attrib["Kd"] = [1.0, 1.0, 1.0] | |
| self.attrib["Ka"] = [0.0, 0.0, 0.0] | |
| self.attrib["Ks"] = [0.0, 0.0, 0.0] | |
| self.attrib["Ke"] = [0.0, 0.0, 0.0] | |
| def put(self, key, value): | |
| self.attrib[key] = value | |
| return self | |
| def get(self, key): | |
| if self.attrib.has_key(key): | |
| return self.attrib[key] | |
| return None | |
| def has_key(self, key): | |
| return self.attrib.has_key(key) | |
| def isTextured(self): | |
| # for k in ("map_Kd", "map_Bump", "map_Ks"): <-- NOT YET | |
| if self.attrib.has_key("map_Kd"): | |
| return True; | |
| return False; | |
| def getEggTexture(self): | |
| if self.eggdiffusetexture: | |
| return self.eggdiffusetexture | |
| if not self.isTextured(): | |
| return None | |
| m = EggTexture(self.name + "_diffuse", self.get("map_Kd")) | |
| m.setFormat(EggTexture.FRgb) | |
| m.setMagfilter(EggTexture.FTLinearMipmapLinear) | |
| m.setMinfilter(EggTexture.FTLinearMipmapLinear) | |
| m.setWrapU(EggTexture.WMRepeat) | |
| m.setWrapV(EggTexture.WMRepeat) | |
| self.eggdiffusetexture = m | |
| return self.eggdiffusetexture | |
| def getEggMaterial(self): | |
| if self.eggmaterial: | |
| return self.eggmaterial | |
| m = EggMaterial(self.name + "_mat") | |
| # XXX TODO: add support for specular, and obey illum setting | |
| # XXX as best as we can | |
| rgb = self.get("Kd") | |
| if rgb is not None: | |
| m.setDiff(Vec4(rgb[0], rgb[1], rgb[2], 1.0)) | |
| rgb = self.get("Ka") | |
| if rgb is not None: | |
| m.setAmb(Vec4(rgb[0], rgb[1], rgb[2], 1.0)) | |
| rgb = self.get("Ks") | |
| if rgb is not None: | |
| m.setSpec(Vec4(rgb[0], rgb[1], rgb[2], 1.0)) | |
| ns = self.get("Ns") | |
| if ns is not None: | |
| m.setShininess(ns) | |
| self.eggmaterial = m | |
| return self.eggmaterial | |
| class MtlFile: | |
| """an object representing all Wavefront materials in a .mtl file""" | |
| def __init__(self, filename=None): | |
| self.filename = None | |
| self.materials = {} | |
| self.comments = {} | |
| if filename is not None: | |
| self.read(filename) | |
| def read(self, filename, verbose=False): | |
| self.filename = filename | |
| self.materials = {} | |
| self.comments = {} | |
| try: | |
| file = open(filename) | |
| except: | |
| return self | |
| linenumber = 0 | |
| mat = None | |
| for line in file.readlines(): | |
| line = line.strip() | |
| linenumber = linenumber + 1 | |
| if not line: | |
| continue | |
| if line[0] == '#': | |
| self.comments[linenumber] = line | |
| print line | |
| continue | |
| tokens = line.split() | |
| if not tokens: | |
| continue | |
| if verbose: print "tokens[0]:", tokens | |
| if tokens[0] == "newmtl": | |
| mat = ObjMaterial() | |
| mat.filename = filename | |
| mat.name = tokens[1] | |
| self.materials[mat.name] = mat | |
| if verbose: print "newmtl:", mat.name | |
| continue | |
| if tokens[0] in ("Ns", "d", "Tr"): | |
| # "d factor" - specifies the dissovle for the current material, | |
| # 1.0 is full opaque | |
| # "Ns exponent" - specifies the specular exponent. A high exponent | |
| # results in a tight, concentrated highlight. | |
| mat.put(tokens[0], float(tokens[1])) | |
| continue | |
| if tokens[0] in ("illum"): | |
| # according to http://www.fileformat.info/format/material/ | |
| # 0 = Color on and Ambient off | |
| # 1 = Color on and Ambient on | |
| # 2 = Highlight on | |
| # 3 = Reflection on and Ray trace on | |
| # 4 = Transparency: Glass on, Reflection: Ray trace on | |
| # 5 = Reflection: Fesnel on and Ray trace on | |
| # 6 = Transparency: Refraction on, Reflection: Fresnel off and Ray trace on | |
| # 7 = Transparency: Refraction on, Refelction: Fresnel on and Ray Trace on | |
| # 8 = Reflection on and Ray trace off | |
| # 9 = Transparency: Glass on, Reflection: Ray trace off | |
| # 10 = Casts shadows onto invisible surfaces | |
| mat.put(tokens[0], int(tokens[1])) | |
| continue | |
| if tokens[0] in ("Kd", "Ka", "Ks", "Ke"): | |
| mat.put(tokens[0], floats(tokens[1:])) | |
| continue | |
| if tokens[0] in ("map_Kd", "map_Bump", "map_Ks", "map_bump", "bump"): | |
| # Ultimate Unwrap 3D Pro emits these: | |
| # map_Kd == diffuse | |
| # map_Bump == bump | |
| # map_Ks == specular | |
| mat.put(tokens[0], pathify(tokens[1])) | |
| if verbose: print "map:", mat.name, tokens[0], mat.get(tokens[0]) | |
| continue | |
| if tokens[0] in ("Ni"): | |
| # blender's .obj exporter can emit this "Ni 1.000000" | |
| mat.put(tokens[0], float(tokens[1])) | |
| continue | |
| print "file \"%s\": line %d: unrecognized:" % (filename, linenumber), tokens | |
| file.close() | |
| if verbose: print "%d materials" % len(self.materials), "loaded from", filename | |
| return self | |
| class ObjFile: | |
| """a representation of a wavefront .obj file""" | |
| def __init__(self, filename=None): | |
| self.filename = None | |
| self.objects = ["defaultobject"] | |
| self.groups = ["defaultgroup"] | |
| self.points = [] | |
| self.uvs = [] | |
| self.normals = [] | |
| self.faces = [] | |
| self.polylines = [] | |
| self.matlibs = [] | |
| self.materialsbyname = {} | |
| self.comments = {} | |
| self.currentobject = self.objects[0] | |
| self.currentgroup = self.groups[0] | |
| self.currentmaterial = None | |
| if filename is not None: | |
| self.read(filename) | |
| def read(self, filename, verbose=False): | |
| if verbose: print "ObjFile.read:", "filename:", filename | |
| self.filename = filename | |
| self.objects = ["defaultobject"] | |
| self.groups = ["defaultgroup"] | |
| self.points = [] | |
| self.uvs = [] | |
| self.normals = [] | |
| self.faces = [] | |
| self.polylines = [] | |
| self.matlibs = [] | |
| self.materialsbyname = {} | |
| self.comments = {} | |
| self.currentobject = self.objects[0] | |
| self.currentgroup = self.groups[0] | |
| self.currentmaterial = None | |
| try: | |
| file = open(filename) | |
| except: | |
| return self | |
| linenumber = 0 | |
| for line in file.readlines(): | |
| line = line.strip() | |
| linenumber = linenumber + 1 | |
| if not line: | |
| continue | |
| if line[0] == '#': | |
| self.comments[linenumber] = line | |
| print line | |
| continue | |
| tokens = line.split() | |
| if not tokens: | |
| continue | |
| if tokens[0] == "mtllib": | |
| if verbose: print "mtllib:", tokens[1:] | |
| mtllib = MtlFile(tokens[1]) | |
| # if verbose: print mtllib | |
| self.matlibs.append(mtllib) | |
| self.indexmaterials(mtllib) | |
| continue | |
| if tokens[0] == "g": | |
| if verbose: print "g:", tokens[1:] | |
| self.__newgroup("".join(tokens[1:])) | |
| continue | |
| if tokens[0] == "o": | |
| if verbose: print "o:", tokens[1:] | |
| self.__newobject("".join(tokens[1:])) | |
| continue | |
| if tokens[0] == "usemtl": | |
| if verbose: print "usemtl:", tokens[1:] | |
| self.__usematerial(tokens[1]) | |
| continue | |
| if tokens[0] == "v": | |
| if verbose: print "v:", tokens[1:] | |
| self.__newv(tokens[1:]) | |
| continue | |
| if tokens[0] == "vn": | |
| if verbose: print "vn:", tokens[1:] | |
| self.__newnormal(tokens[1:]) | |
| continue | |
| if tokens[0] == "vt": | |
| if verbose: print "vt:", tokens[1:] | |
| self.__newuv(tokens[1:]) | |
| continue | |
| if tokens[0] == "f": | |
| if verbose: print "f:", tokens[1:] | |
| self.__newface(tokens[1:]) | |
| continue | |
| if tokens[0] == "s": | |
| # apparently, this enables/disables smoothing | |
| print "%s:%d:" % (filename, linenumber), "ignoring:", tokens | |
| continue | |
| if tokens[0] == "l": | |
| if verbose: print "l:", tokens[1:] | |
| self.__newpolyline(tokens[1:]) | |
| continue | |
| print "%s:%d:" % (filename, linenumber), "unknown:", tokens | |
| file.close() | |
| return self | |
| def __vertlist(self, lst): | |
| res = [] | |
| for vert in lst: | |
| vinfo = vert.split("/") | |
| vlen = len(vinfo) | |
| vertex = {'v':None, 'vt':None, 'vn':None} | |
| if vlen == 1: | |
| vertex['v'] = int(vinfo[0]) | |
| elif vlen == 2: | |
| if vinfo[0] != '': | |
| vertex['v'] = int(vinfo[0]) | |
| if vinfo[1] != '': | |
| vertex['vt'] = int(vinfo[1]) | |
| elif vlen == 3: | |
| if vinfo[0] != '': | |
| vertex['v'] = int(vinfo[0]) | |
| if vinfo[1] != '': | |
| vertex['vt'] = int(vinfo[1]) | |
| if vinfo[2] != '': | |
| vertex['vn'] = int(vinfo[2]) | |
| else: | |
| print "aborting..." | |
| raise UNKNOWN, res | |
| res.append(vertex) | |
| if False: print res | |
| return res | |
| def __enclose(self, lst): | |
| mdata = (self.currentobject, self.currentgroup, self.currentmaterial) | |
| return (lst, mdata) | |
| def __newpolyline(self, l): | |
| polyline = self.__vertlist(l) | |
| if False: print "__newline:", polyline | |
| self.polylines.append(self.__enclose(polyline)) | |
| return self | |
| def __newface(self, f): | |
| face = self.__vertlist(f) | |
| if False: print face | |
| self.faces.append(self.__enclose(face)) | |
| return self | |
| def __newuv(self, uv): | |
| self.uvs.append(floats(uv)) | |
| return self | |
| def __newnormal(self, normal): | |
| self.normals.append(floats(normal)) | |
| return self | |
| def __newv(self, v): | |
| # capture the current metadata with vertices | |
| vdata = floats(v) | |
| mdata = (self.currentobject, self.currentgroup, self.currentmaterial) | |
| vinfo = (vdata, mdata) | |
| self.points.append(vinfo) | |
| return self | |
| def indexmaterials(self, mtllib, verbose=False): | |
| # traverse the materials defined in mtllib, indexing | |
| # them by name. | |
| for mname in mtllib.materials: | |
| mobj = mtllib.materials[mname] | |
| self.materialsbyname[mobj.name] = mobj | |
| if verbose: | |
| print "indexmaterials:", mtllib.filename, "materials:", self.materialsbyname.keys() | |
| return self | |
| def __closeobject(self): | |
| self.currentobject = "defaultobject" | |
| return self | |
| def __newobject(self, object): | |
| self.__closeobject() | |
| if False: print "__newobject:", "object:", object | |
| self.currentobject = object | |
| self.objects.append(object) | |
| return self | |
| def __closegroup(self): | |
| self.currentgroup = "defaultgroup" | |
| return self | |
| def __newgroup(self, group): | |
| self.__closegroup() | |
| if False: print "__newgroup:", "group:", group | |
| self.currentgroup = group | |
| self.groups.append(group) | |
| return self | |
| def __usematerial(self, material): | |
| if False: print "__usematerial:", "material:", material | |
| if self.materialsbyname.has_key(material): | |
| self.currentmaterial = material | |
| else: | |
| print "warning:", "__usematerial:", "unknown material:", material | |
| return self | |
| def __itemsby(self, itemlist, objname, groupname): | |
| res = [] | |
| for item in itemlist: | |
| vlist, mdata = item | |
| wobj, wgrp, wmat = mdata | |
| if (wobj == objname) and (wgrp == groupname): | |
| res.append(item) | |
| return res | |
| def __facesby(self, objname, groupname): | |
| return self.__itemsby(self.faces, objname, groupname) | |
| def __linesby(self, objname, groupname): | |
| return self.__itemsby(self.polylines, objname, groupname) | |
| def __eggifyverts(self, eprim, evpool, vlist): | |
| for vertex in vlist: | |
| ixyz = vertex['v'] | |
| vinfo = self.points[ixyz-1] | |
| vxyz, vmeta = vinfo | |
| ev = EggVertex() | |
| ev.setPos(Point3D(vxyz[0], vxyz[1], vxyz[2])) | |
| iuv = vertex['vt'] | |
| if iuv is not None: | |
| vuv = self.uvs[iuv-1] | |
| ev.setUv(Point2D(vuv[0], vuv[1])) | |
| inormal = vertex['vn'] | |
| if inormal is not None: | |
| vn = self.normals[inormal-1] | |
| ev.setNormal(Vec3D(vn[0], vn[1], vn[2])) | |
| evpool.addVertex(ev) | |
| eprim.addVertex(ev) | |
| return self | |
| def __eggifymats(self, eprim, wmat): | |
| if self.materialsbyname.has_key(wmat): | |
| mtl = self.materialsbyname[wmat] | |
| if mtl.isTextured(): | |
| eprim.setTexture(mtl.getEggTexture()) | |
| # NOTE: it looks like you almost always want to setMaterial() | |
| # for textured polys.... [continued below...] | |
| eprim.setMaterial(mtl.getEggMaterial()) | |
| rgb = mtl.get("Kd") | |
| if rgb is not None: | |
| # ... and some untextured .obj's store the color of the | |
| # material # in the Kd settings... | |
| eprim.setColor(Vec4(rgb[0], rgb[1], rgb[2], 1.0)) | |
| # [continued...] but you may *not* always want to assign | |
| # materials to untextured polys... hmmmm. | |
| if False: | |
| eprim.setMaterial(mtl.getEggMaterial()) | |
| return self | |
| def __facestoegg(self, egg, objname, groupname): | |
| selectedfaces = self.__facesby(objname, groupname) | |
| if len(selectedfaces) == 0: | |
| return self | |
| eobj = EggGroup(objname) | |
| egg.addChild(eobj) | |
| egrp = EggGroup(groupname) | |
| eobj.addChild(egrp) | |
| evpool = EggVertexPool(groupname) | |
| egrp.addChild(evpool) | |
| for face in selectedfaces: | |
| vlist, mdata = face | |
| wobj, wgrp, wmat = mdata | |
| epoly = EggPolygon() | |
| egrp.addChild(epoly) | |
| self.__eggifymats(epoly, wmat) | |
| self.__eggifyverts(epoly, evpool, vlist) | |
| #; each matching face | |
| return self | |
| def __polylinestoegg(self, egg, objname, groupname): | |
| selectedlines = self.__linesby(objname, groupname) | |
| if len(selectedlines) == 0: | |
| return self | |
| eobj = EggGroup(objname) | |
| egg.addChild(eobj) | |
| egrp = EggGroup(groupname) | |
| eobj.addChild(egrp) | |
| evpool = EggVertexPool(groupname) | |
| egrp.addChild(evpool) | |
| for line in selectedlines: | |
| vlist, mdata = line | |
| wobj, wgrp, wmat = mdata | |
| eline = EggLine() | |
| egrp.addChild(eline) | |
| self.__eggifymats(eline, wmat) | |
| self.__eggifyverts(eline, evpool, vlist) | |
| #; each matching line | |
| return self | |
| def toEgg(self, verbose=True): | |
| if verbose: print "converting..." | |
| # make a new egg | |
| egg = EggData() | |
| # convert polygon faces | |
| if len(self.faces) > 0: | |
| for objname in self.objects: | |
| for groupname in self.groups: | |
| self.__facestoegg(egg, objname, groupname) | |
| # convert polylines | |
| if len(self.polylines) > 0: | |
| for objname in self.objects: | |
| for groupname in self.groups: | |
| self.__polylinestoegg(egg, objname, groupname) | |
| return egg | |
| def pathify(path): | |
| if os.path.isfile(path): | |
| return path | |
| # if it was written on win32, it may have \'s in it, and | |
| # also a full rather than relative pathname (Hexagon does this... ick) | |
| orig = path | |
| path = path.lower() | |
| path = path.replace("\\", "/") | |
| h, t = os.path.split(path) | |
| if os.path.isfile(t): | |
| return t | |
| print "warning: can't make sense of this map file name:", orig | |
| return t | |
| def main(argv=None): | |
| if argv is None: | |
| argv = sys.argv | |
| try: | |
| opts, args = getopt.getopt(argv[1:], "hn:bs", ["help", "normals", "binormals", "show"]) | |
| except getopt.error, msg: | |
| print msg | |
| print __doc__ | |
| return 2 | |
| show = False | |
| for o, a in opts: | |
| if o in ("-h", "--help"): | |
| print __doc__ | |
| return 0 | |
| elif o in ("-s", "--show"): | |
| show = True | |
| for infile in args: | |
| try: | |
| if ".obj" not in infile: | |
| print "WARNING", finfile, "does not look like a valid obj file" | |
| continue | |
| obj = ObjFile(infile) | |
| egg = obj.toEgg() | |
| f, e = os.path.splitext(infile) | |
| outfile = f + ".egg" | |
| for o, a in opts: | |
| if o in ("-n", "--normals"): | |
| egg.recomputeVertexNormals(float(a)) | |
| elif o in ("-b", "--binormals"): | |
| egg.recomputeTangentBinormal(GlobPattern("")) | |
| egg.removeUnusedVertices(GlobPattern("")) | |
| if True: | |
| egg.triangulatePolygons(EggData.TConvex & EggData.TPolygon) | |
| if True: | |
| egg.recomputePolygonNormals() | |
| egg.writeEgg(Filename(outfile)) | |
| if show: | |
| os.system("pview " + outfile) | |
| except Exception,e: | |
| print e | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |