minimalistic exporter for blender 2.5

before it get’s lost in my panda snipplets archive directories i’d like to share what i’ve archived while working on a minimalistic exporter for blender 2.5.

you need to change the paths where the files are exported to in the script itself (eggFilename, logFilename and rootFolder). othervise load it into the scripts editor of blender 2.5 and run it. all meshes in the scene should then be exported.

it’s has no to few testing, is missing 90% of the features the egg-exporter for 2.49 had (read the comments). but it can do some stuff already. and i hope the code-archidecture isnt too bad to extend it further, i’m just not sure if i have the time to work on it in the future (depends on my needs).

i’ve only tested under osx, with some simple texturing and materials.

if you have an idea how to extend it, or you’d like to continue working on it, write it here. if you have the request for another feature, read the previous sentence :wink:

-> edit: see later posts for updated code

… your post have no comments. Maybe it should go in “Pipeline”?

I will try your script :slight_smile:

Any chance for armature animations? From your post I don’t think so :confused: but …

there is little chance that i can do animated stuff anytime soon. digging into that would probably require a lot of time, which i havent got spare right now. anyways, the script has a lot of untested features already, so getting the current state to alpha will require some work already (multitexturing, different types of mappings) and lower level features not implemented like uv-wrapping-modes.

changes:

  • fixed the normals for rotated objects

usage:

  • add this code as “io_export_panda3d_egg.py” into the “scripts/addons” folder of blender
    • for example under osx this is:
      “blender-2.54.app/Contents/MacOS/2.54/scripts/addons”
  • modify the pviewBinary path if you want to use previewing
  • activate the “Import/Export: Export Panda3d Egg Format (.egg)” under the “User Preferences” in Blender
  • add objects to the scene
  • “File -> Export -> Panda3d Egg (.egg)”
# -*- coding: utf-8 -*-

#------------------------------------------------------------------------------
# panda3d-egg exporter for blender 2.5
#   written by Reto Spoerri (Dez.2010) (rspoerri at nouser dot org)
#------------------------------------------------------------------------------
# whats done:
# - basic material settings (diffuse color and specular and shininess)
# - smooth and flat shaded faces
# - multi-uv maps
# - basic texturing stuff
#------------------------------------------------------------------------------
# missing functionality:
# - face vertex colors
# - bone animations (any animations)
# - uv-wrapping-modes
# - testing
# - double sided vertices
# - interface, currently the output path needs to be defined manually
#------------------------------------------------------------------------------
# known or possible bugs:
# - lots of 
#------------------------------------------------------------------------------
# functionality:
#  exports blender 2.5 data as egg file
# how it works:
# (1) the blender node-tree is walked trough (class __init__ functions)
#   - Root
#     - World (not used)
#     - Scene
#       - Object
#         - Object (recursive)
#         - MeshData
#           - Face
#           - Vertex
#           - UvTexture
#             - UvTextureData
#         - Curve (not implemented)
#     - Material
#       - TextureSlot
#         - Texture
# (2) the write funtion of each class goes trough
#   - Materials
#   - Textures
#   - Scene
#     - Object
#       - Object (recursive)
#       - MeshData
#         -> the MeshData.write function converts blender faces and vertices
#            into egg face and vertice data which is then written
#------------------------------------------------------------------------------

bl_addon_info = {
    "name": "Export Panda3d Egg Format (.egg)",
    "author": "Reto Spoerri",
    "version": (0, 1),
    "blender": (2, 5, 4),
    "api": 31847,
    "location": "File > Export",
    "description": "Export to the Panda3d Model Format (.x)",
    "warning": "",
    "category": "Import/Export"}

pviewBinary = '/Developer/Tools/Panda3D/pview'

#------------------------------------------------------------------------------
# dont need to change
#------------------------------------------------------------------------------
import bpy
import subprocess
import os
import time
#from bpy.props import *
from io_utils import ExportHelper

eggFilename = 'out-004.egg'
rootFolder = '/Users/rspoerri/Desktop/eggExporter25/'

logFilename = 'eggExporter.log'
# number of spaces to indent on each level
indentAdd = 2

DEBUG = 2 # 0 disabled, 1 file, 2 print

#------------------------------------------------------------------------------
# DEBUG WRITER
#------------------------------------------------------------------------------
class Debug(object):
  def __init__(self):
    if DEBUG == 0:
      pass
    if DEBUG == 1:
      self.debugfile = open(os.path.join(rootFolder, logFilename), 'wt')
      self.write(time.asctime()+":\n")
    if DEBUG == 2:
      pass
  
  def write(self, *text):
    if DEBUG == 1:
      if len(text) == 1 and isinstance(text, str):
        self.debugfile.write(text[0].rstrip()+"\n")
      else:
        self.debugfile.write(str(text)+"\n")
      self.debugfile.flush()
    if DEBUG == 2:
      if len(text) == 1 and isinstance(text, str):
        print(text[0].rstrip()+"\n")
      else:
        print(str(text)+"\n")
  
  

debug = Debug()
#------------------------------------------------------------------------------
# SOME FUNCTIONS, taken from the 2.49 exporter
#------------------------------------------------------------------------------
def eggSafeName(s):
  """Function that converts names into something suitable for the egg file format - simply puts " around names that contain spaces and prunes bad characters, replacing them with an underscore."""
  s = s.replace('"','_') # Sure there are more bad characters, but this will do for now.
  if ' ' in s:
    return '"' + s + '"'
  else:
    return s

def convertFileNameToPanda(filename):
  """Converts Blender filenames to Panda 3D filenames."""
  path =  filename.replace('//', './').replace('\\', '/')
  if os.name == 'nt' and path.find(':') != -1:
    path = '/'+ path[0].lower() + path[2:]
  return path

#------------------------------------------------------------------------------
# A SIMPLE RECURSIVE HIERARCHY
#------------------------------------------------------------------------------
class Node(object):
  def __init__(self, parent):
    self.parent = parent
    self.children = list()
  
  def write(self, recursion):
    eggContent = ''
    for child in self.children:
      eggContent += child.write(recursion+1)
    return eggContent

#------------------------------------------------------------------------------
# indent function, prints (argument) number of spaces
#------------------------------------------------------------------------------
def indent(level):
  return level*indentAdd*" "

#------------------------------------------------------------------------------
# A BLENDER CURVE
#------------------------------------------------------------------------------
class CurveData(Node):
  def __init__(self, parent, node):
    super(Curve, self).__init__(parent)

#------------------------------------------------------------------------------
# A BLENDER VERTEX
#------------------------------------------------------------------------------
class Vertex(Node):
  def __init__(self, parent, vertexNode):
    super(Vertex, self).__init__(parent)
    
    self.index      = vertexNode.index
    # the coordinate system must be in world coordinates
    self.coordinate = (self.parent.parent.worldTransform * vertexNode.co)
    self.normal     = vertexNode.normal[:]
    if vertexNode.normal.length != 0:
      # convert into world coordinates
      self.normal     = (self.parent.parent.worldTransform.rotation_part() * vertexNode.normal).normalize()[:]
  
  def __repr__(self):
    out = 'vertex: %i : %s (%s)\n' % (self.index, " ".join(map(str, self.coordinate)), " ".join(map(str, self.normal)))
    for [uvName, uvData] in self.uvTextures:
      out += "  - uv: %s : %s\n" % (uvName, " ".join(map(str, uvData)))
    
    return out
  

#------------------------------------------------------------------------------
# A BLENDER FACE
#------------------------------------------------------------------------------
class Face(Node):
  def __init__(self, parent, faceNode):
    super(Face, self).__init__(parent)
    self.index          = faceNode.index
    self.vertices       = faceNode.vertices[:]
    self.normal         = faceNode.normal[:]
    if faceNode.normal.length != 0:
      # convert into world coordinates
      self.normal      = (self.parent.parent.worldTransform.rotation_part() * faceNode.normal).normalize()[:]
    self.flat_faces     = not faceNode.use_smooth
    self.material_index = faceNode.material_index
  
  def __repr__(self):
    out = 'face: %i : %s (%s)\n' % (self.index, " ".join(map(str, self.vertices)), " ".join(map(str, self.normal)))
    return out
  

#------------------------------------------------------------------------------
# A BLENDER MESH
#------------------------------------------------------------------------------
class MeshData(Node):
  def __init__(self, parent, meshDataNode):
    super(MeshData, self).__init__(parent)
    
    self.name = eggSafeName(meshDataNode.name)
    self.materials = list()
    self.textures = list()
    for material in meshDataNode.materials:
      if material:
        self.materials.append(eggSafeName(material.name))
        for texture_slots in material.texture_slots:
          if texture_slots:
            self.textures.append(eggSafeName(texture_slots.name))
    
    # add all faces and remember smooth ones, by default it's set to hard
    self.faces = list()
    for faceNode in meshDataNode.faces:
      self.faces.append(Face(self, faceNode))
    
    # add all vertices
    self.vertices = list()
    for vertexNode in meshDataNode.vertices:
      self.vertices.append(Vertex(self, vertexNode))
    
    self.uvTextures = list()
    for uvTexturesNode in meshDataNode.uv_textures:
      self.uvTextures.append(UvTextures(self, uvTexturesNode))
  
  def write(self, recursion):
    
    eggVertexList = list()
    class EggVertex():
      def __init__(self, index, coordinate, smooth, normal, uvData):
        eggVertexList.append(self)
        self.index = index
        self.coordinate = coordinate
        self.smooth = smooth
        self.normal = normal
        self.uvData = uvData
      def write(self, recursion):
        # hard version
        eggContent  = ""
        eggContent += indent(recursion+1)+"<Vertex> %i {\n" % (self.index)
        eggContent += indent(recursion+2)+"%s\n" % " ".join(map(str, self.coordinate))
        # uv & vertex_color & normal untested
        for uvName, uvCoord in self.uvData:
          eggContent += indent(recursion+2)+"<UV> %s { %s }\n" % (uvName, " ".join(map(str, uvCoord)))
        # without normals these are smooth edges
        if self.smooth:
          eggContent += indent(recursion+2)+"<Normal> { %s }" % " ".join(map(str, self.normal))+"\n"
        # this is not implemented yet
        #if self.vertex_color:
        #  eggContent += indent(recursion+2)+"<RGBA> { %s }" % " ".join(map(str, self.vertex_color))+"\n"
        eggContent += indent(recursion+1)+"}\n"
        return eggContent
    
    eggFaceList = list()
    class EggFace():
      def __init__(self, index, name, vertices, normal, flat, material, textures):
        eggFaceList.append(self)
        self.index = index
        self.name = name
        self.vertices = vertices
        self.normal = normal
        self.flat = flat
        self.material = material
        self.textures = textures
      def write(self, recursion):
        eggContent  = ""
        eggContent += indent(recursion  )+"<Polygon> {\n"
        eggContent += indent(recursion+1)+"<VertexRef> {\n"
        eggContent += indent(recursion+2)+" ".join(map(str, self.vertices))+"\n"
        eggContent += indent(recursion+2)+"<Ref> { %s }\n" % self.name
        eggContent += indent(recursion+1)+"}\n"
        # this makes hard edges, but is overridden by smooth ones (which dont know if it shall be smooth or hard)
        if self.flat:
          eggContent += indent(recursion+1)+"<Normal> { %s }\n" % " ".join(map(str, self.normal))
        if self.material:
          eggContent += indent(recursion+1)+"<MRef> { %s }\n" % self.material
        for texture in self.textures:
          if texture:
            eggContent += indent(recursion+1)+"<TRef> { %s }\n" % texture
        eggContent += indent(recursion)+"}\n"
        return eggContent
    
    
    eggVertexIndex = 0
    for faceId, face in enumerate(self.faces):
      faceIndex    = face.index
      faceVertices = face.vertices
      faceNormal   = face.normal
      faceFlat     = face.flat_faces
      faceMaterial = None
      if face.material_index < len(self.materials):
        faceMaterial = self.materials[face.material_index]
      faceTextures = self.textures
      # list of the vertex id's for the egg file
      eggFaceVertexList = list()
      for faceVerticesId, vertexIndex in enumerate(faceVertices):
        vertex = self.vertices[vertexIndex]
        vertexCoordinate = vertex.coordinate
        vertexNormal = vertex.normal
        vertexSmooth = not faceFlat
        #vertexUv = faceUvs[faceVerticesId]
        uvData   = []
        for uvTexture in self.uvTextures:
          uvData.append([uvTexture.name, uvTexture.uvTextureData[faceIndex].uv[faceVerticesId]])
        if len(uvData) > 0:
          uvData[0][0] = ""
        # create the vertices for the egg file
        EggVertex(eggVertexIndex, vertexCoordinate, vertexSmooth, vertexNormal, uvData)
        eggFaceVertexList.append(eggVertexIndex)
        eggVertexIndex += 1
      # create the faces for the egg file
      EggFace(faceIndex, self.name, eggFaceVertexList, faceNormal, faceFlat, faceMaterial, faceTextures)
    
    # handle vertices first
    eggContent  = ""
    eggContent += indent(recursion)+"<VertexPool> %s {\n" % self.name
    for vertex in eggVertexList:
      eggContent += vertex.write(recursion)
    eggContent += indent(recursion)+"}\n"
    
    # handle faces afterwards
    for face in eggFaceList:
      #if isinstance(childNode, Face):
      eggContent += face.write(recursion)
    
    return eggContent
  
  def __repr__(self):
    ''' for debugging purposes '''
    out = ''
    for c in self.materials:
      out += c.__repr__()+"\n"
    for c in self.faces:
      out += c.__repr__()+"\n"
    for c in self.vertices:
      out += c.__repr__()+"\n"
    for c in self.uvTextures:
      out += c.__repr__()+"\n"
    return out
  

#------------------------------------------------------------------------------
# BLENDER OBJECT NODE
#------------------------------------------------------------------------------
class Object(Node):
  def __init__(self, parent, objectNode):
    super(Object, self).__init__(parent)
    
    self.name = eggSafeName(objectNode.name)
    self.worldTransform = objectNode.matrix_world
    self.localTransform = objectNode.matrix_local
    
    # different type of objects
    if objectNode.type == 'MESH':
      self.children.append(MeshData(self, objectNode.data))
    if objectNode.type == 'CURVE':
      self.children.append(CurveData(self, objectNode.data))
    
    # childrens
    for childNode in objectNode.children:
      self.children.append(Object(self, childNode))
  
  def write(self, recursion):
    eggContent = ""
    eggContent += indent(recursion)+"<Group> %s {\n" % (self.name)
    if self.worldTransform:
      eggContent += indent(recursion+1)+"<Transform> {\n"
      eggContent += indent(recursion+2)+"<Matrix4> {\n"
      eggContent += indent(recursion+3)+" ".join(map(str, self.worldTransform[0]))+"\n"
      eggContent += indent(recursion+3)+" ".join(map(str, self.worldTransform[1]))+"\n"
      eggContent += indent(recursion+3)+" ".join(map(str, self.worldTransform[2]))+"\n"
      eggContent += indent(recursion+3)+" ".join(map(str, self.worldTransform[3]))+"\n"
      eggContent += indent(recursion+2)+"}\n"
      eggContent += indent(recursion+1)+"}\n"
    eggContent += super(Object, self).write(recursion)
    eggContent += indent(recursion)+"}\n"
    return eggContent
  

#------------------------------------------------------------------------------
# BLENDER SCENE NODE
#------------------------------------------------------------------------------
class Scene(Node):
  def __init__(self, parent, sceneNode):
    super(Scene, self).__init__(parent)
    self.name = eggSafeName(sceneNode.name)
    
    for object in sceneNode.objects:
      if not object.parent: # only handle objects without parents
        self.children.append(Object(self, object))
  
  def write(self, recursion):
    eggContent = ""
    for childNode in self.children:
      eggContent += childNode.write(recursion)
    return eggContent
  

#------------------------------------------------------------------------------
# BLENDER WORLD NODE
#------------------------------------------------------------------------------
class World(Node):
  # dont know what worlds are used for...
  def __init__(self, parent, worldNode):
    super(World, self).__init__(parent)
  
  def write(self, recursion):
    eggContent = ""
    for childNode in self.children:
      eggContent += childNode.write(recursion)
    return eggContent
  

#------------------------------------------------------------------------------
# THE BLENDER UVTEXTURE (a single entry of a uv)
#------------------------------------------------------------------------------
class UvTextureData(Node):
  def __init__(self, parent, uvTextureDataNode):
    super(UvTextureData, self).__init__(parent)
    self.uv = list()
    for uvCoordinate in uvTextureDataNode.uv:
      self.uv.append(uvCoordinate[:])
  
  def __repr__(self):
    out = ''
    for c in self.uv:
      out += str(c)
    return out
  

#------------------------------------------------------------------------------
# THE BLENDER UVTEXTURES
#------------------------------------------------------------------------------
class UvTextures(Node):
  def __init__(self, parent, uvTexturesNode):
    super(UvTextures, self).__init__(parent)
    self.name = uvTexturesNode.name
    
    self.uvTextureData = list()
    for uvTextureDataNode in uvTexturesNode.data:
      self.uvTextureData.append(UvTextureData(self, uvTextureDataNode))
  
  def __repr__(self):
    out = self.name+"\n"
    for c in self.uvTextureData:
      out += c.__repr__()+"\n"
    return out
  

#------------------------------------------------------------------------------
# MATERIALS
#------------------------------------------------------------------------------
all_materials = dict()
class Material(Node):
  def __init__(self, parent, materialNode):
    super(Material, self).__init__(parent)
    debug.write("MATERIAL")
    self.name           = eggSafeName(materialNode.name)
    self.diffuse_color  = materialNode.diffuse_color[:]
    specular_intensity  = materialNode.specular_intensity/2.0
    self.specular_color = [
        materialNode.specular_color[0]*specular_intensity,
        materialNode.specular_color[1]*specular_intensity,
        materialNode.specular_color[2]*specular_intensity,
      ]
    self.shininess      = materialNode.specular_hardness/4.0
    self.texture_slot_names = list()
    self.texture_slots = list()
    for texture_slot in materialNode.texture_slots:
      if texture_slot:
        self.texture_slot_names.append(texture_slot.name)
        self.texture_slots.append(TextureSlot(self, texture_slot))
    
    # store in global all_materials
    global all_materials
    all_materials[self.name] = self
    
  def write(self, recursion):
    eggContent = ""
    if self.name:
      eggContent += indent(recursion)+"<Material> %s {\n" % self.name
      if self.diffuse_color:
        eggContent += indent(recursion+1)+"<Scalar> diffr { %f }\n" % self.diffuse_color[0]
        eggContent += indent(recursion+1)+"<Scalar> diffg { %f }\n" % self.diffuse_color[1]
        eggContent += indent(recursion+1)+"<Scalar> diffb { %f }\n" % self.diffuse_color[2]
      if self.specular_color:
        eggContent += indent(recursion+1)+"<Scalar> specr { %f }\n" % self.specular_color[0]
        eggContent += indent(recursion+1)+"<Scalar> specg { %f }\n" % self.specular_color[1]
        eggContent += indent(recursion+1)+"<Scalar> specb { %f }\n" % self.specular_color[2]
      if self.shininess:
        eggContent += indent(recursion+1)+"<Scalar> shininess { %f }\n" % self.shininess
      eggContent += indent(recursion)+"}\n"
    eggContent += super(Material, self).write(recursion+1)
    return eggContent

#------------------------------------------------------------------------------
# TEXTURE
# a texture is a subpart of a material[x]->texture_slot[x]->texture
# or directly accessable on the root
#------------------------------------------------------------------------------
all_textures = dict()
class Texture(Node):
  def __init__(self, parent, textureNode):
    super(Texture, self).__init__(parent)
    
    if textureNode.type != 'IMAGE':
      debug.write('texture with invalid type')
    
    all_textures[self.name] = self
    
  def write(self, recursion):
    eggContent = ""
    return eggContent

#------------------------------------------------------------------------------
# TEXTURE_SLOT
# it has access to more informations then the texture itself
# it knows about the modes, that the texture is defined to
#------------------------------------------------------------------------------
all_texture_slots = dict()
class TextureSlot(Node):  
  def __init__(self, parent, textureSlotNode):
    super(TextureSlot, self).__init__(parent)
    
    self.name = eggSafeName(textureSlotNode.name)
    #self.texture = Texture(self, textureSlotNode.texture)
    
    self.envType = 'MODULATE' # default
    # "normal" coloring modes
    if textureSlotNode.blend_type == 'MIX':
      self.envType = 'MIX'
      # or when there is a texture on the object already
      #self.envType = 'MODULATE' 
    if textureSlotNode.blend_type == 'MULTIPLY':
      self.envType = 'MODULATE'
    if textureSlotNode.blend_type == 'ADD':
      self.envType = 'ADD'
    if textureSlotNode.blend_type == 'SCREEN':
      self.envType = 'DECAL'
    # if it's a normal map
    debug.write("textureSlotNode.use_map_normal", textureSlotNode.use_map_normal)
    if textureSlotNode.use_map_normal:
      debug.write("textureSlotNode.texture.use_normal_map", textureSlotNode.texture.use_normal_map)
      if textureSlotNode.texture.use_normal_map:
        self.envType = 'NORMAL'
      else:
        self.envType = 'HEIGHT'
    # if it's a glossmap
    if textureSlotNode.use_map_specular:
      self.envType = 'GLOSS'
    # if it's a glowmap
    if textureSlotNode.use_map_emit:
      self.envType = 'GLOW'
    debug.write("envtype", self.envType)
    
    self.textureName = eggSafeName(textureSlotNode.texture.name)
    self.textureImageName = None
    self.textureImageFilepath = "INVALID"
    
    self.wrapMode = 'REPEAT'
    
    self.filter = 'LINEAR_MIPMAP_LINEAR'
    if textureSlotNode.texture.use_mipmap:
      if not textureSlotNode.texture.use_interpolation:
        self.filter = 'NEAREST'
    
    if textureSlotNode.texture.image:
      if textureSlotNode.texture.image.source != 'FILE':
        debug.write('texture.image with invalid source')
      if textureSlotNode.texture.image.mapping != 'UV':
        debug.write('texture.image with invalid mapping')
      if textureSlotNode.texture.image.type != 'IMAGE':
        debug.write('texture.image with invalid type')
      
      self.textureImageName = eggSafeName(textureSlotNode.texture.image.name)
      self.textureImageFilepath = textureSlotNode.texture.image.filepath
    
    global all_texture_slots
    debug.write("found texture slot", self.name, self)
    all_texture_slots[self.name] = self
    debug.write("all_texture_slots", all_texture_slots.keys())
    
  def write(self, recursion):
    eggContent = ""
    eggContent += indent(recursion)+"<Texture> %s {\n" % self.textureName
    if self.textureImageFilepath:
      eggContent += indent(recursion+1)+'"%s"\n' % self.textureImageFilepath
    eggContent += indent(recursion+1)+"<Scalar> saved-result { 1 }\n"
    eggContent += indent(recursion+1)+"<Scalar> envtype { %s }\n" % self.envType
    eggContent += indent(recursion+1)+"<Scalar> minfilter { %s }\n" % self.filter
    eggContent += indent(recursion+1)+"<Scalar> magfilter { %s }\n" % self.filter
    eggContent += indent(recursion+1)+"<Scalar> wrap { %s }\n" % self.wrapMode
    eggContent += indent(recursion)+"}\n"
    return eggContent

#------------------------------------------------------------------------------
# BLENDER SCENE ROOT
#------------------------------------------------------------------------------
class Root(Node):
  def __init__(self, rootNode):
    super(Root, self).__init__(None)
    for worldNode in rootNode.worlds:
      self.children.append(World(self, worldNode))
    for sceneNode in rootNode.scenes:
      self.children.append(Scene(self, sceneNode))
    for materialNode in rootNode.materials:
      self.children.append(Material(self, materialNode))
    #for textureNode in rootNode.textures:
    #  self.children.append(Texture(self, textureNode))
  
  def write(self, filename):
    outfile = open(filename, 'wt')
    eggContent = ""
    # handle materials first
    for child in self.children:
      if isinstance(child, Material):
        eggContent += child.write(0)
    # handle textures
    global all_texture_slots
    debug.write("final all_texture_slots", all_texture_slots.keys())
    for childName, child in all_texture_slots.items():
      if isinstance(child, TextureSlot):
        debug.write(childName, child)
        eggContent += child.write(0)
    # handle scenes second
    for child in self.children:
      if isinstance(child, Scene):
        eggContent += child.write(0)
    # handle worlds last
    for child in self.children:
      if isinstance(child, World):
        eggContent += child.write(0)
    outfile.write(eggContent)
    outfile.close()


#------------------------------------------------------------------------------
# BLENDER GUI STUFF
#------------------------------------------------------------------------------
class ExportEgg(bpy.types.Operator):
  '''Export a Panda3d Egg file (.egg)'''
  bl_idname = "export.panda3d_egg"
  bl_label = "Export Panda3d EGG"
  filename_ext = ".egg"
  
  filepath = bpy.props.StringProperty()
  filename = bpy.props.StringProperty()
  directory = bpy.props.StringProperty()

  verbose = bpy.props.BoolProperty(
      name="Verbose", 
      description="Run the exporter in debug mode.  Check the console for output.", 
      default=False
    )
  preview = bpy.props.BoolProperty(
      name="Preview", 
      description="View the exported Egg in PView",
      default=True,
    )

  def execute(self, context):
    #Append .x if needed
    filepath = self.filepath
    if not filepath.lower().endswith(".egg"):
      filepath += ".egg"

    Root(bpy.data).write(filepath)
    
    if self.preview:
      pid = subprocess.Popen([pviewBinary, eggFilename]).pid
    
    return {"FINISHED"}

  def invoke(self, context, event):
    if not self.filepath:
      self.filepath = os.path.splitext(context.blend_data.filepath)[0] + self.filename_ext
    context.window_manager.add_fileselect(self)
    return {"RUNNING_MODAL"}

def menu_func(self, context):
  default_path = os.path.splitext(bpy.data.filepath)[0] + ".egg"
  self.layout.operator(ExportEgg.bl_idname, text="Panda3d Egg (.egg)").filepath = default_path

def register():
  bpy.types.INFO_MT_file_export.append(menu_func)

def unregister():
  bpy.types.INFO_MT_file_export.remove(menu_func)

if __name__ == "__main__":
  register()

[/code]

Is there any way this could be ziped or a so we do not have to go thru the process of putting the correct indention in when using copy and paste?

pastebin.com/rfg8fAB7

ps: if you cant copy the

[code]
indented text, it’s probably the browser that does it incorrectly (it works for me).

Thank you Hypnos. I.E. windows does that with every copy and paste. I have not tried cnp in Firefox so I might be using the wrong browser.