The Source for Java Technology Collaboration


Home | Changes | Index | Search | Go

AdrianCheaterX3DExporter

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")

Topic AdrianCheaterX3DExporter . { Edit | Ref-By | Printable | Diffs r2 < r1 | More }
 XML java.net RSS

Revision r2 - 24 Nov 2008 - 16:21:22 - Main.rkd
Parents: WebHome > ProjectWonderland > ProjectWonderlandArtImportTutorial