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]