### Blender->COD export script ### # $Id: blenderExportCOD.py,v 1.2 2003/09/05 18:36:51 reality Exp $ # # This script will export your Blender scene to a VOS COD (set the filename # below). This COD can be loaded by the "vosworld" server, and other tools. # # This script is written for Blender version 2.28. Other versions may not work. # # To run this script, load it into the Blender text editor, (use the [-] button # in the header of the text editor window) and press ALT+P. # Unless you have set the OutputFilename variable below, a file selector will # appear. Unfortunately we have no control over the default filename: be careful # not to overwrite your .blend file! ####################### ### "static" geometry will have better lighting but will be slower to load, and ### cannot be moved. To override this setting for any particular object, ### put a $ at the beginning of the object's name for static geometry, and put ### an ! at the beginning for dynamic geometry. outputStaticGeom = True ### "Hard" transforms are not affected by transformations in parent 3D objects. outputHardTransform = False ### Name of the World object to use for some sector proporties. If it doesn't ### exist, then no sector will be included in the COD. ### There is also one special hack to make the Blender and VOS "defaults" line ### up: if this value is "World", then the sector will be created with the ### name "world" (lower case). (But the Blender object should be named "World". WorldName = "World" ### Set an output file. If empty (""), then you can select one ### with a file selector OutputFilename = "" ####################### # end of user configuration # Blender 2.28 tries to use the Fink Python paths on Mac OSX, # so the script will fail if you don't have Python installed # using Fink. This adds the standard paths. import sys if (sys.platform == "darwin"): sys.path.append("/usr/lib/python2.2") sys.path.append("/usr/lib/python2.2/plat-darwin") sys.path.append("/usr/lib/python2.2/lib-dynload") sys.path.append("/usr/lib/python2.2/site-packages") import struct, math, random, os, Blender class Message: def __init__(self): self._method = "" self._fields = [] def setMethod(self, method): self._method = method def addField(self, key, value): self._fields.append([key, value]) def writeToCOD(self, thefile): thefile.write(struct.pack(('>H%isH' % len(self._method)), len(self._method), self._method, len(self._fields))) for f in self._fields: thefile.write(struct.pack(('>H%isL%is' % (len(f[0]), len(f[1]))), len(f[0]), f[0], len(f[1]), f[1])) Vobject_names = {} def uniqueName(name): if name == "": name = str(random.randint(1,1000000)) while Vobject_names.has_key(name): name = name + "_" + str(random.randint(1,1000000)) return name class Vobject: def __init__(self, name): self._name = uniqueName(name) Vobject_names[name] = self self._types = [] self._messages = [] self._children = [] self._codposition = 0 def getName(self): return self._name def addType(self, type): self._types.append(type) def addMessage(self, msg): self._messages.append(msg) def addChild(self, contextname, child): self._children.append([contextname, child]) def getChildren(self): return self._children def setCODPosition(self, pos): self._codposition = pos def writeDescToCOD(self, thefile): thefile.write(struct.pack(('>H%isLH' % len(self._name)), len(self._name), self._name, self._codposition, len(self._types))) for t in self._types: thefile.write(struct.pack(('>H%is' % len(t)), len(t), t)) thefile.write(struct.pack('>H', len(self._messages))) for m in self._messages: m.writeToCOD(thefile) def writeChildrenToCOD(self, thefile): thefile.write(struct.pack(('>H%isL' % len(self._name)), len(self._name), self._name, len(self._children))) for c in self._children: thefile.write(struct.pack(('>H%isH%is' % (len(c[0]), len(c[1].getName()))), len(c[0]), c[0], len(c[1].getName()), c[1].getName())) class COD: def __init__(self): self._vobjects = [] def addVobject(self, v): self._vobjects.append(v) def addVobjectTree(self, v): self._vobjects.append(v) for c in v.getChildren(): self.addVobjectTree(c[1]) def writeCOD(self, filename): codoutput = file(filename, "w") codoutput.write(struct.pack(">4sbbL","VOS", 1, 0, len(self._vobjects))) for v in self._vobjects: v.writeDescToCOD(codoutput) codoutput.write(struct.pack(">L", len(self._vobjects))) for v in self._vobjects: v.writeChildrenToCOD(codoutput) codoutput.close() class Property(Vobject): def __init__(self, name): Vobject.__init__(self, name) self.addType("property:property") def setPropValue(self, val, datatype = "?"): m = Message() m.setMethod("property:replace") m.addField("data", val) m.addField("datatype", datatype) self.addMessage(m) class Texture(Vobject): def __init__(self, name): Vobject.__init__(self, name) self.addType("a3dl:texture") def setImage(self, val, datatype = "?"): v = Property("image") v.setPropValue(val, datatype) self.addChild("a3dl:image", v) class Material(Vobject): def __init__(self, name): Vobject.__init__(self, name) self.addType("a3dl:material") def setColor(self, r, g, b): v = Property("color") v.setPropValue(("%f %f %f" % (r, g, b)), "text/x-vector-float") self.addChild("a3dl:color", v) def setTexture(self, texture): self.addChild("a3dl:texture", texture) class Object3D(Vobject): def __init__(self, name): Vobject.__init__(self, name) self.addType("a3dl:object3D") def setPosition(self, pos): v = Property("position") v.setPropValue(pos, "text/x-vector-float") if(outputHardTransform): self.addChild("a3dl:hardposition", v) else: self.addChild("a3dl:position", v) def setOrientation(self, ori): v = Property("orientation") v.setPropValue(ori, "text/x-vector-float") if(outputHardTransform): self.addChild("a3dl:hardorientation", v) else: self.addChild("a3dl:orientation", v) def setScaling(self, scale): v = Property("scaling") v.setPropValue(scale, "text/x-vector-float") if(outputHardTransform): self.addChild("a3dl:hardscaling", v) else: self.addChild("a3dl:scaling", v) def addMaterial(self, material): self.addChild("a3dl:material", material) class PolygonMesh(Object3D): def __init__(self, name): Object3D.__init__(self, name) self._types = [] self.addType("a3dl:object3D.polygonmesh") if (name[0] == '$') or ( (name[0] != '!') and outputStaticGeom ): print " (static mesh)" self.addType("a3dl:static") else: print " (dynamic mesh)" def setVertices(self, verts): vertstr = "" for v in verts: vertstr += struct.pack(">fff", v[0], v[2], v[1]) p = Property("vertices") p.setPropValue(vertstr) self.addChild("a3dl:vertices", p) def setNormals(self, verts): vertstr = "" for v in verts: vertstr += struct.pack(">fff", v[0], v[2], v[1]) p = Property("normals") p.setPropValue(vertstr) self.addChild("a3dl:normals", p) def setTexels(self, texels): vertstr = "" for t in texels: vertstr += struct.pack(">ff", t[0], t[1]) p = Property("texels") p.setPropValue(vertstr) self.addChild("a3dl:texels", p) def setTriangles(self, tris): tristr = "" for t in tris: tristr += struct.pack(">lll", t[0], t[1], t[2]) p = Property("polygons") p.setPropValue(tristr, "packed/triangle-array") self.addChild("a3dl:polygons", p) def setPolygons(self, tris): pass #tristr = "" #for t in tris: # tristr += struct.pack(">lll", t[0], t[1], t[2]) #p = Property("polygons") #p.setPropValue(tristr, "packed/triangle-array") #self.addChild("a3dl:polygons", p) class Sector(Vobject): # Uses "world" for the object name if passed a value of "World" # for name (this makes the Blender and VOS default names # line up) def __init__(self, name): Vobject.__init__(self, name) self.addType("a3dl:sector") print "created sector named \"" + name + "\"." # Sector is different. It adds it's properties individually # to the COD. Then you can add the sector to the COD yourself, # when it's ready. def exportSectorProperties(self, worldObj, cod): try: amb = worldObj.getAmb() if( (amb[0] != 0) and (amb[1] != 0) and (amb[2] != 0) ): p = Property("ambient-light") p.setPropValue(("%f %f %f" % (amb[0], amb[1], amb[2])), "text/x-float-vector") self.addChild("a3dl:ambient-light", p) cod.addVobject(p) except: print "Error getting ambient light!" try: mist = worldObj.getMist()[0] if(mist != 0): c = Property("mist-color") c.setPropValue(("0.0 0.0 0.0"), "text/x-float-vector") self.addChild("a3dl:fog-color", c) cod.addVobject(c) d = Property("mist-density") d.setPropValue(("%f" % mist), "text/x-float") self.addChild("a3dl:fog-density", d) cod.addVobject(d) except: print "Error getting mist!" def matrixToQuaternion(m): tr = m[0][0] + m[1][1] + m[2][2] + 1 if tr > 0: s = 0.5 / math.sqrt (tr) w = 0.25 / s x = (m[2][1] - m[1][2]) * s y = (m[0][2] - m[2][0]) * s z = (m[1][0] - m[0][1]) * s else: i = 0 if m[1][1] > m[0][0]: i = 1 if m[2][2] > m[i][i]: i = 2 if i == 0: s = math.sqrt ((m[0][0] - (mat.m[1][1] + mat.m[2][2])) + 1.0) x = s * 0.5 if s != 0.0: s = 0.5 / s y = (m[1][2] + m[1][0]) * s z = (m[0][2] + m[2][0]) * s w = (m[1][2] - m[2][1]) * s elif i == 1: s = math.sqrt ((m[1][1] - (m[2][2] + m[0][0])) + 1.0) y = 0.5 * s if s != 0.0: s = 0.5 / s x = (m[0][1] + m[1][0]) * s z = (m[1][2] + m[2][1]) * s w = (m[0][2] - m[2][0]) * s elif i == 2: s = math.sqrt ((m[2][2] - (m[0][0] + m[1][1])) + 1.0) z = 0.5 * s if s != 0.0: s = 0.5 / s x = (m[0][2] + m[2][0]) * s y = (m[1][2] + m[2][1]) * s w = (m[0][1] - m[1][0]) * s square= x*x + y*y + z*z if square > 0.0: dist = 1.0 / math.sqrt(square) else: dist = 1 x *= dist; y *= dist; z *= dist; if w > 1: w = w - 2 elif w < -1: w = w + 2 return [x, y, z, w] def quaternionToAxisAngle(q): x, y, z, w = q[0], q[1], q[2], q[3] w = 2.0 * math.acos(w) sp = math.sin(w/2.0) if sp > .0000000001: x = x / sp y = y / sp z = z / sp w *= 180.0/math.pi else: x = y = 0 z = 1 w = 0 # d = math.sqrt(x*x + y*y + z*z) # if d > 0: # x = x / d # y = y / d # z = z / d # else: # x = y = 0 # z = 1 return [x, y, z, w] def invertQuat(q): d = 1.0 / (q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]) q[0] = -q[0] * d q[1] = -q[1] * d q[2] = -q[2] * d q[3] = -q[3] * d return q def matrixToAxisAngle(m): return quaternionToAxisAngle(matrixToQuaternion(m)) def matrixToInvAxisAngle(m): return quaternionToAxisAngle(invertQuat(matrixToQuaternion(m))) def compareVertices(a, b): return cmp(a[1:4], b[1:4]) def getGeometry(faces): tmpvertices = [] triangles = [] for face in faces: firstvert = len(tmpvertices) for f in range(len(face)): uv = (0, 0) if len(face.uv) > f: uv = (face.uv[f][0], face.uv[f][1]) tmpvertices.append((len(tmpvertices), (face[f][0], face[f][1], face[f][2]), uv, (face[f].no[0], face[f].no[1], face[f].no[2]))) for f in range(2, len(face)): triangles.append([firstvert, firstvert + f, firstvert + f - 1]) tmpvertices.sort(compareVertices) vertexmap = range(len(tmpvertices)) cur = [] vertices = [] texels = [] normals = [] for tv in tmpvertices: if compareVertices(tv, cur): vertices.append(tv[1]) texels.append(tv[2]) normals.append(tv[3]) cur = tv vertexmap[tv[0]] = len(vertices) - 1 for t in triangles: (t[0], t[1], t[2]) = (vertexmap[t[0]], vertexmap[t[1]], vertexmap[t[2]]) return (vertices, texels, normals, triangles) def exportMesh(obj, cod): mesh = obj.data if(mesh == 0): print " Error: no object data" return 0 print "object name is: " + mesh.name trimesh = PolygonMesh(obj.name) loc = obj.getLocation() trimesh.setPosition("%f %f %f" % (loc[0], loc[2], loc[1])) aa = matrixToAxisAngle(obj.getMatrix()) trimesh.setOrientation("%f %f %f %f" % (aa[0], aa[2], aa[1], aa[3])) trimesh.setScaling("%f %f %f" % (obj.size[0], obj.size[2], obj.size[1])) print mesh.hasFaceUV() if(mesh.hasFaceUV() == False): print " Warning: mesh does not have face UV coordinates!" material = Material("meshmaterial") if mesh.faces[0].image: filename = mesh.faces[0].image.getFilename() print "opening file: " + filename + "..." image = file(filename, "rb") if(image): texture = Texture("meshtexture") texture.setImage(image.read()) material.setTexture(texture) image.close() else: print "ERROR opening file: " + filename else: if len(mesh.materials) > 0: material.setColor(mesh.materials[0].R, mesh.materials[0].G, mesh.materials[0].B) else: material.setColor(.6, .6, .6) trimesh.addMaterial(material) print " (geometry...)" (vertices, texels, normals, triangles) = getGeometry(mesh.faces) trimesh.setVertices(vertices) trimesh.setNormals(normals) trimesh.setTexels(texels) trimesh.setTriangles(triangles) cod.addVobjectTree(trimesh) return trimesh def exportLight(obj, cod): lamp = obj.data light = Vobject(obj.name) light.addType("a3dl:light") pos = Property("lightposition") loc = obj.getLocation() pos.setPropValue("%f %f %f" % (loc[0], loc[2], loc[1]), "text/x-vector-float") light.addChild("a3dl:position", pos) radius = Property("lightradius") radius.setPropValue("%f" % (lamp.dist), "text/x-float"); light.addChild("a3dl:radius", radius) static = Property("staticlight") static.setPropValue("yes", "text/x-yes-or-no"); light.addChild("a3dl:static", static) color = Property("lightcolor") color.setPropValue("%f %f %f" % (lamp.R, lamp.G, lamp.B), "text/x-vector-float"); light.addChild("a3dl:color", color) cod.addVobjectTree(light) return light def exportCamera(obj, cod): viewpoint = Vobject(obj.name) viewpoint.addType("a3dl:viewpoint") pos = Property("cameraposition") loc = obj.getLocation() pos.setPropValue("%f %f %f" % (loc[0], loc[2], loc[1]), "text/x-vector-float") viewpoint.addChild("a3dl:position", pos) pos = Property("cameraorientation") aa = matrixToInvAxisAngle(obj.getMatrix()) pos.setPropValue("%f %f %f %f" % (aa[0], aa[2], aa[1], aa[3]), "text/x-vector-float") #pos.setPropValue("%f %f %f %f" % (0, 1, 0, 90), "text/x-vector-float") viewpoint.addChild("a3dl:orientation", pos) cod.addVobjectTree(viewpoint) return viewpoint def exportObject(obj, cod): print "exporting object type="+obj.getType() if obj.getType() == "Mesh": if obj.data: return exportMesh(obj, cod) elif obj.getType() == "Lamp": if obj.data: return exportLight(obj, cod) elif obj.getType() == "Camera": if obj.data: return exportCamera(obj, cod) ### Main begins here def doExport(filename): print("-- Begin blender COD export --") cod = COD() # Find a "World" object to use for a sector try: world = Blender.World.Get("World") #if(WorldName == "World"): sector = Sector("world") #else: # sector = Sector(WorldName) sector.exportSectorProperties(world, cod) except NameError: print "No World object named " + WorldName + " was found." sector = 0 # Now get all objects in the current scene and put them # in the COD. If we have a "World"/sector, also link # the to that. scene = Blender.Scene.getCurrent() n = len(scene.getChildren()) i = 0 for obj in scene.getChildren(): print "adding \"" + obj.name + "\" to COD..." i = i + 1 Blender.Window.DrawProgressBar(n/i, "Adding "+obj.name+" to COD...") vobj = exportObject(obj, cod) if(sector != 0): print " (adding to sector)" sector.addChild(obj.name, vobj) if(sector != 0): cod.addVobject(sector) cod.writeCOD(filename) print("-- Wrote COD: "+filename) if(OutputFilename == ""): Blender.Window.FileSelector(doExport, "Write COD") else: doExport(OutputFilename)