 |
Home | Changes | Index | Search | Go
This is a copy of Adrian Cheater's X3D? Exporter plugin for Blender.
Posted here because the original link from blender.org is out of service.
Copy everything between the horizontal rules into a file called x3d_acheater.py in your Blender plugins directory. This location varies by OS and install-time choices. If the first Windows directory doesn't exist, it's the other one! Consult the "Creating a script" section of the Python Scripting wikibook.
#!BPY
"""Registration info for Blender menus:
Name: 'X3D'
Blender: 232
Group: 'Export'
Tooltip: 'Export to Extensible 3D (.x3d) or VRML97 (.wrl)'
"""
#-----------------------------------------------------------------
# X3D exporter for blender 2.32 or above
# It may work with earlier versions
#
# Version 0.16 - June 12, 2004
# Version 0.15 - May 22, 2004
# Version 0.14 - May 18, 2004
# Version 0.13 - May 15, 2004
# Version 0.12 - April 5, 2004
# Version 0.11 - March 28, 2004
# Version 0.1 - March 27, 2004
#
# Source: http://www.bitbucket.ca/~acheater/blender/
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Copyright (C) 2004: Adrian Cheater acheater@bitbucket.ca
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
####################################
# Global Variables
####################################
# Public, you may change these
_askQuestions = True # If true, will prompt user for next two questions
# These are settings which only matter if the line above is false
_writeVRML = False # If true, writes VRML. If true writes XML
_exportSelected = True # If false, exports the entire scene
_safeOverwrite = True # If false, will overwrite files without asking.
# Private, don't change these
_doc = None
_fatalError = False
_disableVRML = False
####################################
# Library dependancies
####################################
import sys
from math import *
from os.path import exists, join
try:
import Blender
from Blender import Object, NMesh, Lamp, Draw, BGL
from Blender.Mathutils import *
except:
print "Fatal Error! Unable to find Blender modules!"
print "Are you running this script from within blender?"
_fatalError = True
try:
from xml.dom.minidom import Document, DocumentType, Element, Comment
except:
Draw.PupMenu("Fatal Error! See console for details%t")
print """\n\n
***************************************
Fatal Error!! Couldn't find XML package. You may need to install Python
Please visit http://www.bitbucket.ca/~acheater/blender/install.html
for further instructions
***************************************\n\n"""
_fatalError = True
try:
from Ft.Xml.Xslt.Processor import Processor
from Ft.Xml.InputSource import DefaultFactory, Uri
except:
print """\
Warning! Couldn't find XSLT processor. VRML exporting disabled"
Please visit http://www.bitbucket.ca/~acheater/blender/install.html
for further instructions"""
_disableVRML = True
##########################################################
# Callbacks, needed before Main
##########################################################
def select_file(filename):
if exists(filename) and _safeOverwrite:
result = Draw.PupMenu("File Already Exists, Overwrite?%t|Yes%x1|No%x0")
if(result != 1):
return
export_file(filename)
def askExportVRML():
result = Draw.PupMenu("Export as?%t|X3D%x0|VRML%x1")
if(result == 1): return True
return False
def askExportSelected():
result = Draw.PupMenu("Export...%t|All Objects%x0|Selected Objects%x1")
if(result == 1): return True
return False
def createX3DPath():
filename = Blender.Get('filename')
print filename
if filename.find('.') != -1:
filename = filename.split('.')[0]
if(_writeVRML):
filename += ".wrl"
else:
filename += ".x3d"
return filename
########################################
# Main
########################################
if(not _fatalError):
if Blender.Get('version') < 232:
print "Warning: this exporter may not function with versions of blender"
print "older than 2.32. It if fails please download the latest version"
print "from http://blender.org"
if sys.hexversion < 0x020300F0:
print "NOTICE: Python 2.2 or older, applying Document.writexml patch"
Document.writexml = patch2_2_Document_writexml
DocumentType.writexml = patch2_2_DocumentType_writexml
if(_askQuestions):
_exportSelected = askExportSelected()
if(not _disableVRML): _writeVRML = askExportVRML()
Blender.Window.FileSelector(select_file,"Export",createX3DPath())
###########################################################
# Functions for writing output file
##########################################################
def export_file(filename):
global _doc
_doc = Document()
print "*** Exporting X3D to:", filename
writeDocument(_doc)
if(_writeVRML):
writeToVRML(filename)
else:
writeToXML(filename)
print "*** Done"
def writeToVRML(filename):
if(_disableVRML):
print "ERROR: XSLT processor not found, aborting export"
return
xsltPath = findFile(sys.path,"X3dToVrml97.xsl")
if(not xsltPath):
print "ERROR: Could not find X3dToVrml97.xsl at any of the following locations:"
print sys.path
else:
result = translateDocument(_doc.toxml(),xsltPath)
file = open(filename,"w")
file.write(result)
def writeToXML(filename):
file = open(filename,"w")
_doc.writexml(file,""," ","\n")
def findFile(paths,filename):
for path in paths:
if exists(join(path,filename)):
return join(path,filename)
return None
def translateDocument(document,stylesheet):
processor = Processor()
xslt = DefaultFactory.fromUri(Uri.OsPathToUri(stylesheet))
processor.appendStylesheet(xslt)
xmlDoc = DefaultFactory.fromString(document)
return processor.run(xmlDoc)
########################################################
# Functions for writing X3D scene structure
########################################################
def writeDocument(doc):
writeHeader(doc)
writeBody(doc)
def writeHeader(doc):
writeComments(doc)
writeDocType(doc)
def writeBody(doc):
root = doc.createElement("X3D")
root.setAttribute("Profile","Interchange")
doc.appendChild(root)
scene = _doc.createElement("Scene")
writeScene(scene)
root.appendChild(scene)
def writeComments(doc):
doc.appendChild(Comment("This file was authored with Blender\
(http://www.blender.org/)"))
doc.appendChild(Comment("Exported using the X3DExporter, written by\
Adrian Cheater (http://www.bitbucket.ca/~acheater/blender)"))
def writeDocType(doc):
docType = DocumentType("X3D")
# The URL seems valid now, but FreeWRL doesn't like having this information
# included.
# docType.publicId = "ISO//Web3D//DTD X3D 3.0//EN"
# docType.systemId = "http://www.web3d.org/specifications/x3d-3.0.dtd"
doc.appendChild(docType)
def writeScene(scene):
objects = getObjectList()
needHeadlight = True
for obj in objects:
if obj.getType() == "Mesh":
writeMesh(scene,obj)
elif obj.getType() == "Lamp":
needHeadlight = False
writeLamp(scene,obj)
elif obj.getType() == "Camera":
writeCamera(scene,obj)
writeNavigationInfo(scene,needHeadlight)
def getObjectList():
objects = None
if(_exportSelected): objects = Object.GetSelected()
if(not objects):
print "No objects selected: Exporting entire scene"
objects = Object.Get()
return objects
def writeMesh(scene,obj):
tr = createTransform(obj)
tr.appendChild(createMesh(obj.getData()))
scene.appendChild(tr)
def writeLamp(scene,obj):
lamp = createLight(obj)
if(lamp):
scene.appendChild(lamp)
def writeCamera(scene,obj):
camera = createViewpoint(obj)
scene.appendChild(camera)
def writeNavigationInfo(scene,headlight):
nav = _doc.createElement("NavigationInfo")
if(headlight):
nav.setAttribute("headlight","true")
else:
nav.setAttribute("headlight","false")
nav.setAttribute("type","ANY")
scene.appendChild(nav)
#############################################################
# Functions for converting Blender objects to XML
# The create* functions will return completed element tags/trees
# The get* functions perform formatting/data conversion
#############################################################
#############################################################
# Functions for exporting Mesh Objects
#############################################################
def createMesh(mesh):
print "Exporting Mesh : \"%s\" %i vertices, %i faces" %\
(mesh.name,len(mesh.verts),len(mesh.faces))
if(mesh.getMode() & NMesh.Modes['AUTOSMOOTH']):
print "Warning: Autosmoothing is not supported."
print "Export will behave as if this feature were off"
shape = _doc.createElement("Shape")
shape.appendChild(createGeometry(mesh))
if( mesh.materials ):
shape.appendChild(createAppearance(mesh.materials[0]))
return shape
###########################################################
# Functions for exporting geometry data
###########################################################
def createGeometry(mesh):
geom = _doc.createElement("IndexedFaceSet")
# geom.setAttribute("DEF",mesh.name)
if( mesh.getMode() & NMesh.Modes['TWOSIDED'] ):
geom.setAttribute("solid","false")
writeVertexData(geom,mesh)
writeNormalData(geom,mesh)
if(mesh.hasFaceUV()):
writeTextureData(geom,mesh)
return geom
##########################################################
# Functions for exporting vertex data
##########################################################
def writeVertexData(geom,mesh):
coords = _doc.createElement("Coordinate")
coords.setAttribute("point",getVertices(mesh))
geom.setAttribute("coordIndex",getIndices(mesh))
geom.appendChild(coords)
def getVertices(mesh):
s = ""
for vert in mesh.verts:
s += "%.3f %.3f %.3f " % (vert.co[0], vert.co[1], vert.co[2])
return s
def getIndices(mesh):
s = ""
for face in mesh.faces:
for vert in face.v:
s += "%i " % vert.index
s += "-1 "
return s
##########################################################
# Functions for exporting Normal data
##########################################################
def writeNormalData(geom,mesh):
normals = _doc.createElement("Normal")
normals.setAttribute("vector",getNormals(mesh))
geom.setAttribute("normalIndex",getNormalIndices(mesh))
geom.appendChild(normals)
def getNormals(mesh):
s = ""
for face in mesh.faces:
if(face.smooth):
for v in face.v:
s += "%.3f %.3f %.3f " % (v.no[0],v.no[1],v.no[2])
else:
for i in range(len(face.v)):
s += "%.3f %.3f %.3f " % (face.no[0],face.no[1],face.no[2])
return s
def getNormalIndices(mesh):
s = ""
offset = 0
for face in mesh.faces:
for i in range(len(face.v)):
s += "%i " % (offset)
offset += 1
s += "-1 "
return s
#########################################################
# Functions for exporting Texture data
#########################################################
def writeTextureData(geom,mesh):
texCoords = _doc.createElement("TextureCoordinate")
texCoords.setAttribute("point",getTexCoords(mesh))
geom.setAttribute("texCoordIndex",getTextureIndices(mesh))
geom.appendChild(texCoords)
def getTexCoords(mesh):
s = ""
for face in mesh.faces:
for f in face.uv:
s += "%.3f %.3f " % f
return s
def getTextureIndices(mesh):
s = ""
offset = 0
for face in mesh.faces:
for i in range(len(face.uv)):
s += "%i " % (offset)
offset += 1
s += "-1 "
return s
############################################################
# Functions for exporting appearance data
############################################################
def createAppearance(mat):
app = _doc.createElement("Appearance")
# app.setAttribute("DEF",mat.name)
app.appendChild(createMaterial(mat))
tex = createTextures(mat)
if( tex != None ):
app.appendChild(tex)
return app
############################################################
# Functions for exporting material data
############################################################
def createMaterial(mat):
e = _doc.createElement("Material")
e.setAttribute("diffuseColor",getColor(getDiffuse(mat)))
e.setAttribute("ambientIntensity","%.3f" % mat.amb)
e.setAttribute("emissiveColor",getColor(getEmissive(mat)))
e.setAttribute("shininess","%.3f" % (min(mat.hard,255)/255.0) )
e.setAttribute("specularColor",getColor(getSpecular(mat)))
e.setAttribute("transparency","%.3f" % (1.0 - mat.alpha) )
return e
def getColor(c):
return "%.3f %.3f %.3f" % (c[0], c[1], c[2])
# the following get color functions are a best guess
# as to how to convert blender's color model to X3D's
def getEmissive(mat):
eCol = mat.rgbCol
for c in range(3):
eCol[c] *= mat.emit
return eCol
def getDiffuse(mat):
dCol = mat.rgbCol
for c in range(3):
dCol[c] *= mat.ref
return dCol
def getSpecular(mat):
sCol = mat.specCol
for c in range(3):
sCol[c] = min(sCol[c]*mat.spec,1.0)
return sCol
##########################################################
# Functions for exporting texture data
##########################################################
def createTextures(mat):
texList = createTextureList(mat)
nTex = len(texList)
if( nTex == 1 ):
return texList[0]
elif( nTex > 1 ):
print "Warning: Exporting with more than one texture not tested."
return createMuliTexture(texList)
return None
def createTextureList(mat):
tElements = []
for tex in mat.getTextures():
if( tex != None ):
name = getTexturePath(tex)
if( name ):
e = _doc.createElement("ImageTexture")
e.setAttribute("url",name)
tElements.append(e)
return tElements
def getTexturePath(mtex):
image = mtex.tex.getImage()
if( image ):
return Blender.sys.basename(image.getFilename())
return ""
def createMultiTexture(texList):
mTex = _doc.createElement("MultiTexture")
for e in texList:
mTex.appendChild(e)
return mTex
#########################################################
# Functions for exporting matrix data
#########################################################
def createTransform(obj):
e = _doc.createElement("Transform")
e.setAttribute("translation","%.3f %.3f %.3f" % (obj.loc))
e.setAttribute("scale","%.3f %.3f %.3f" % (obj.size))
e.setAttribute("rotation","%.3f %.3f %.3f %.3f" % (euler2AxisAngle(obj.rot)))
return e
#########################################################
# Functions for exporting camera data
#########################################################
def createViewpoint(obj):
print "Exporting Camera : \"%s\"" % (obj.name)
e = _doc.createElement("Viewpoint")
e.setAttribute("orientation","%.3f %.3f %.3f %.3f" %\
(euler2AxisAngle(obj.rot)))
e.setAttribute("position","%.3f %.3f %.3f" % (obj.loc))
return e
#########################################################
# Functions for exporting light data
#########################################################
def createLight(obj):
light = obj.getData()
print "Exporting Light : \"%s\"" % (obj.name)
if(light.type == Lamp.Types['Lamp']):
return createPointLight(obj)
if(light.type == Lamp.Types['Spot']):
return createSpotLight(obj)
if(light.type == Lamp.Types['Sun']):
return createDirectionalLight(obj)
print "Error: Light type not recognized"
return None
def createPointLight(obj):
light = obj.getData()
l = _doc.createElement("PointLight")
l.setAttribute("location","%.3f %.3f %.3f" % (obj.loc))
l.setAttribute("color",getColor(light.col))
l.setAttribute("radius","%.3f" % (light.dist))
if(light.mode & Lamp.Modes['Quad']):
l.setAttribute("attenuation","0 %.3f %.3f" % (light.quad1,light.quad2))
return l
def createDirectionalLight(obj):
light = obj.getData()
l = _doc.createElement("DirectionalLight")
l.setAttribute("color",getColor(light.col))
l.setAttribute("direction","%.3f %.3f %.3f" % (euler2Vector(obj.rot)))
return l
def createSpotLight(obj):
light = obj.getData()
l = _doc.createElement("SpotLight")
l.setAttribute("location","%.3f %.3f %.3f" % (obj.loc))
l.setAttribute("direction","%.3f %.3f %.3f" % (euler2Vector(obj.rot)))
l.setAttribute("color",getColor(light.col))
l.setAttribute("radius","%.3f" % (light.dist))
if(light.mode & Lamp.Modes['Quad']):
l.setAttribute("attenuation","0 %.3f %.3f" % (light.quad1,light.quad2))
l.setAttribute("beamWidth","%.3f" % radians(light.spotSize/2))
l.setAttribute("cutOffAngle","%.3f" % radians(light.spotSize/2))
return l
###############################################
# Basic utility functions
###############################################
def radians(deg):
return deg * pi/180.0
def degs(rad):
return rad * 180.0/pi
def euler2AxisAngle(rot):
c = ( cos(rot[0]/2.0), cos(rot[1]/2.0), cos(rot[2]/2.0) )
s = ( sin(rot[0]/2.0), sin(rot[1]/2.0), sin(rot[2]/2.0) )
a = 2*acos( c[0]*c[1]*c[2] + s[0]*s[1]*s[2] )
z = c[0]*c[1]*s[2] - s[0]*s[1]*c[2]
y = c[0]*s[1]*c[2] + s[0]*c[1]*s[2]
x = s[0]*c[1]*c[2] - c[0]*s[1]*s[2]
if( abs(a) < 0.0001 ): a = 0
if( abs(x) < 0.0001 ): x = 0
if( abs(y) < 0.0001 ): y = 0
if( abs(z) < 0.0001 ): z = 0
len = sqrt( x*x + y*y + z*z )
if( abs(len) > 0.0001 ):
x /= len
y /= len
z /= len
return ( x, y, z, a )
def euler2Vector(rot):
vec = Vector([0,0,-1])
x,y,z = rot
mat = Euler([degs(x),degs(y),degs(z)]).toMatrix()
res = VecMultMat(vec,mat.rotationPart())
return res[0],res[1],res[2]
########################################
# Python 2.2 writexml patches
# Function borrowed from Python 2.3 code
########################################
def patch2_2_Document_writexml(self, writer, indent="", addindent="",\
newl="",encoding = None):
if encoding is None:
writer.write('<?xml version="1.0" ?>\n')
else:
writer.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
for node in self.childNodes:
node.writexml(writer, indent, addindent, newl)
def patch2_2_DocumentType_writexml(self, writer, indent="", addindent="",\
newl=""):
writer.write("<!DOCTYPE ")
writer.write(self.name)
if self.publicId:
writer.write("\n PUBLIC '%s' '%s'" % (self.publicId, self.systemId))
elif self.systemId:
writer.write("\n SYSTEM '%s'" % self.systemId)
if self.internalSubset is not None:
writer.write(" [")
writer.write(self.internalSubset)
writer.write("]")
writer.write(">\n")
|