Egg Postprocessing (Adding collisions, tags)

Here’s a class and a couple of convenience scripts for adding collision nodes and tags to eggs after exporting them out. It’s nothing you can’t do by hand with a text editor but this would fit content pipelines much nicer since it can be automated. Feel free to rip apart/extend the class and convenience functions.

Installation:

  1. Copy the code from below and save them in the same folder. I put them in my panda/bin folder so they can be used in the same way as maya2egg, egg-opt-char etc.

egg-tag usage:
egg-tag.py -i [-o ] --nodes|-n “nodeName[,nodeName1]” --tags|-t “tag[,tag1]” --values|-v “val1[,val2]”

egg-collide usage:
egg-collide.py -i [-o ] --nodes|-n “nodeName[,nodeName1]” [–keep|-k “True|False”]

EggPost.py

from pandac.PandaModules import EggData, EggGroup, Filename

class EggPost(EggData):
    def __init__(self,filename):
        super(EggPost,self).__init__()
        pandaPath = Filename.fromOsSpecific(filename)
        self.read(pandaPath)

    def __findInGroup(self, nodeList, groupName):
        """
        Searches a list of eggNodes for a named group,
        recursively searches, returns the first one it finds.
        If it doesn't find anything, returns False
        """
        returnNode = None
        for node in nodeList:
            if node.getClassType().getName() == "EggGroup":
                #print "Node name:",node.getName()
                if node.getName() == groupName:
                    return node
                testNode = self.__findInGroup(node.getChildren(),groupName)
                #print "Test Node:",testNode
                if testNode != False:
                    return testNode
        return False

    def findGroup(self,groupName):
        return self.__findInGroup(self.getChildren(),groupName)

    def tagGroup(self,groupName,tag,value):
        targetGroup = self.findGroup(groupName)
        #print targetGroup
        if targetGroup:
            print "tagging %s with %s:%s"%(groupName,tag,value)
            targetGroup.setTag(tag,value)

    def collideGroup(self,groupName,keep="False"):
        targetGroup = self.findGroup(groupName)
        if targetGroup:
            if keep == "True":
                cFlags = EggGroup.CFKeep+EggGroup.CFDescend
            else:
                cFlags = EggGroup.CFDescend
            targetGroup.setCsType(EggGroup.CSTPolyset)
            targetGroup.setCollideFlags(cFlags)

    def saveEgg(self,eggName=None):
        if eggName is None:
            saveFile = pandaPath
        else:
            saveFile = Filename.fromOsSpecific(eggName)
        self.writeEgg(saveFile)

egg-collide.py

import EggPost

if __name__ == "__main__":
    import sys, getopt
    try:
        opts, args = getopt.getopt(sys.argv[1:],"i:o:n:k:h",["in=","out=","nodes=","keep=","help"])
    except getopt.GetoptError:
        print "Error: unknown parameter"
        sys.exit(2)

    keepValues = []
    for opt, arg in opts:
        if opt in ("-h","--help"):
            print "Help! I need somebody... help!"
            sys.exit()
            # Put in usage here sometime later
        if opt in ("-i","--in"):
            targetFile = arg
        if opt in ("-o","--out"):
            outFile = arg
        if opt in ("-n","--nodes"):
            print "nodes"
            targetNodes = arg.split(",")
        if opt in ("-k","--keep"):
            print "yays"
            keepValues = arg.split(",")

    if len(keepValues) > len(targetNodes):
        print "\nError: more keep values than target nodes"
        sys.exit(2)

    targetEgg = EggPost.EggPost(targetFile)
    for i in xrange(len(targetNodes)):
        if i < len(keepValues):
        	targetEgg.collideGroup(targetNodes[i],keepValues[i])
       	else:
			targetEgg.collideGroup(targetNodes[i])
    if outFile:
        targetEgg.saveEgg(outFile)
    else:
        targetEgg.saveEgg()

egg-tag.py

import EggPost

if __name__ == "__main__":
    import sys, getopt
    try:
        opts, args = getopt.getopt(sys.argv[1:],"i:o:n:t:v:h",["in=","out=","nodes=","tags=","values=","help"])
    except getopt.GetoptError:
        print "Error: unknown parameter"
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-h","--help"):
            print "Help! I need somebody... help!"
            # Put in usage here sometime later
        if opt in ("-i","--in"):
            targetFile = arg
        if opt in ("-o","--out"):
            outFile = arg
        if opt in ("-n","--nodes"):
            targetNodes = arg.split(",")
            print "targetNodes:",targetNodes
        if opt in ("-t","--tags"):
            nodeTags = arg.split(",")
        if opt in ("-v","--values"):
            tagValues = arg.split(",")

    if (len(targetNodes)+len(nodeTags)+len(tagValues))/3 != len(targetNodes):
        print "\nError: The number of target nodes, tags and values mismatch"
        sys.exit(2)
    targetEgg = EggPost.EggPost(targetFile)
    for i in xrange(len(targetNodes)):
        print "attempting to tag %s with %s:%s"%(targetNodes[i],nodeTags[i],tagValues[i])
        targetEgg.tagGroup(targetNodes[i],nodeTags[i],tagValues[i])
    if outFile:
        targetEgg.saveEgg(outFile)
    else:
        targetEgg.saveEgg()

hm… this sorta cries for beeing merged into chicken exporter :slight_smile: anyone has some free time to spare?

I don’t really agree that it needs to be merged since Chicken can already do tagging and object types (which can be used for collision). Perhaps better documentation of those features would suffice. Besides, this tool is dependent on the EggData library, which I tried to do once with Chicken but it didn’t work so well because it required that your Blender and Panda versions were compiled with the same Python version which is an unnecessary requirement for an exporter.
It would be useful as a separate tool however, when combined with Chicken’s background export mode.

I was talking with Thomas on the IRC channel. He did bring up the Egg library dependency thing which is a good point.

These scripts are more beneficial for the formats with less extensive exporters. Right now it seems like Blender has the best exporter thanks to psiberpunk’s work. These scripts are more of a stopgap measure for the other oxporters.

It’ also good to keep it separated.

I use a kind of pipeline chain (.bat) so it fits very well as a post processing step for egg files, not related if they come from blender or MS3D or …

I also have a little egg scripts i can post. Will do that when i get home.

This should be very useful for me. I’m new, and so I’m sure of the format for the collide tags. If I wanted to add a collision sphere to a static object, or a collision mesh, what would I use for the node name part. An example would be very useful.

some of my egg scripts

egg-ls

#!/usr/bin/python
"""lists the contents of an egg file
-f    full contents"""
import sys, getopt
import math
from pandac.PandaModules import *

def getChildren(eggNode):
    try:
        child = eggNode.getFirstChild()
        while child:
            yield child
            child = eggNode.getNextChild()
    except:
        pass

def eggLs(eggNode,indent=0,full=False):
    if full or eggNode.__class__.__name__ != "EggPolygon":
        print " "*indent+eggNode.__class__.__name__+" "+eggNode.getName()
        for eggChildren in getChildren(eggNode):
            eggLs(eggChildren,indent+1,full)

def main():
    optlist, list = getopt.getopt(sys.argv[1:], 'hf')
    full = False
    for opt in optlist:
        if opt[0] == '-h':
            print __doc__
            sys.exit(0)
        if opt[0] == '-f':
            full = True
        
    for file in list:
        if '.egg' in file:
            egg = EggData()
            egg.read(Filename(file))
            eggLs(egg,full=full)
    
if __name__ == "__main__":
    main()

egg-extract

#!/usr/bin/python
"""extracts a node out of an egg file
egg-extract -g node filesname.egg [outfilename.egg]
-s   do not strip textures"""

import sys, getopt
import math
from pandac.PandaModules import *

def getChildren(eggNode):
    try:
        child = eggNode.getFirstChild()
        while child:
            yield child
            child = eggNode.getNextChild()
    except:
        pass

def eggExtract(eggNode,name):
    if name == eggNode.getName():
        return eggNode
    for eggChildren in getChildren(eggNode):
        ret = eggExtract(eggChildren,name)
        if ret: return ret

def eggStripTexture(eggNode):
    if eggNode.__class__ == EggPolygon:
        eggNode.clearTexture()        
    else:
        for eggChildren in getChildren(eggNode):
            eggStripTexture(eggChildren)

def main():
    optlist, list = getopt.getopt(sys.argv[1:], 'shg:')
    full = False
    stripTexture = True
    for opt in optlist:
        if opt[0] == '-h':
            print __doc__
            sys.exit(0)
        if opt[0] == '-s':
            stripTexture = False
        if opt[0] == '-g':
            group = opt[1]
    file = list[0]
    try:
        outfile = list[1]
    except:
        outfile = group+'.egg'
        
    egg = EggData()
    egg.read(Filename(file))
    if stripTexture: eggStripTexture(egg)
    eggNode = eggExtract(egg,group)
    egg2 = EggData()
    egg2.stealChildren(eggNode)
    egg2.writeEgg(Filename(outfile))
    
if __name__ == "__main__":
    main()

egg-octreefy

#!/usr/bin/python
"""
                              _                 
                             | |                
  ___  __ _  __ _   ___   ___| |_ _ __ ___  ___ 
 / _ \/ _` |/ _` | / _ \ / __| __| '__/ _ \/ _ \
|  __/ (_| | (_| || (_) | (__| |_| | |  __/  __/
 \___|\__, |\__, | \___/ \___|\__|_|  \___|\___|
       __/ | __/ |  by treeform                            
      |___/ |___/                                                              
                              
This is a replacement of raytaller wonderful 
Egg Octree script many people had problem using it
( i always guessed wrong about the size of cells )
and it generated many "empty" branches which this 
one does not.  
original see : ( https://discourse.panda3d.org/viewtopic.php?t=2502 )
This script like the original also released under the WTFPL license.
Usage: egg-octreefy [args] [-o outfile.egg] infile.egg [infile.egg...]  
-h     display this 
-v     verbose
-l     list resulting egg file
-n     number of triangles per leaf (default 3) 
if outfile is not specified "infile"-octree.egg assumed
"""
import sys, getopt
import math
from pandac.PandaModules import *
global verbose,listResultingEgg,maxNumber
listResultingEgg = False
verbose = False
maxNumber = 3
    
def getCenter(vertexList):
    """ get a list of Polywraps and figure out their center """
    # Loop on the vertices determine the bounding box
    center = Point3D(0,0,0)
    i = False
    for vtx in vertexList:
        center += vtx.center
        i+=1
    if i:
        center /= i
    return center

def flatten(thing):
    """ get nested tuple structure like quadrents and flatten it """ 
    if type(thing) == tuple:
        for element in thing: 
            for thing in flatten(element):
                yield thing
    else:
        yield thing

def splitIntoQuadrants(vertexList,center):
    """
        +---+---+    +---+---+
        | 1 | 2 |    | 5 | 6 |
        +---+---+    +---+---+
        | 3 | 4 |    | 7 | 8 |
        +---+---+    +---+---+
        put all poly wraps into quadrents
    """
    quadrants = ((([],[]),
                 ([],[])),
                 (([],[]),
                  ([],[])))
    for vtx in vertexList:
        vtxPos = vtx.center
        x =  vtxPos[0] > center[0]
        y =  vtxPos[1] > center[1]
        z =  vtxPos[2] > center[2]
        quadrants[x][y][z].append(vtx)
    quadrants = flatten(quadrants)
    return quadrants

class Polywrap:
    """ 
        its a class that defines polygons center
        so that it does not have to be recomputed
    """
    polygon = None
    center = None
    
    def __str__(self):
        """ some visualization to aid debugging """
        return str(self.polygon.getNumVertices())+":"+str(self.center)
    
def genPolyWraps(group):
    """ generate a list of polywraps form a group of polygons """
    for polygon in iterChildren(group):
        if type(polygon) == EggPolygon:
            center = Vec3D()
            i = False
            for vtx in iterVertexes(polygon):
                center += vtx.getPos3()
                i += 1 
            if i : center /= i
            pw = Polywrap()
            pw.polygon = polygon
            pw.center = center
            yield pw 
          
def buildOctree(group):
    """ build an octree form a egg group """
    global verbose
    group.triangulatePolygons(0xff)
    polywraps = [i for i in genPolyWraps(group)]
    if verbose: print len(polywraps),"triangles"
    center = getCenter(polywraps)
    quadrants = splitIntoQuadrants(polywraps,center)
    eg = EggGroup('octree-root')
    for node in recr(quadrants):
        eg.addChild(node)
    return eg

def recr(quadrants,indent=0):
    """ 
        visit each quadrent and create octree there
        all the end consolidate all octrees into egg groups 
    """ 
    global verbose,maxNumber
    qs = [i for i in quadrants]
    if verbose: print "    "*indent,"8 quadrents have ",[len(i) for i in qs]," triangles"
    for quadrent in qs:
        if len(quadrent) == 0:
            if verbose: print "    "*indent," no triangles at this quadrent"
            continue
        elif len(quadrent) <= maxNumber:
            center = getCenter(quadrent)
            if verbose: print "    "*indent," triangle center", center, len(quadrent)
            eg = EggGroup('leaf %i tri'%len(quadrent))
            eg.addObjectType('barrier')
            for pw in quadrent:
                eg.addChild(pw.polygon)
                if eg.getFirstChild : yield eg
        else:
            eg = EggGroup('branch-%i'%indent)
            center = getCenter(quadrent)
            for node in recr(splitIntoQuadrants(quadrent,center),indent+1):
                eg.addChild(node)
            if eg.getFirstChild : yield eg
      
def iterChildren(eggNode):
    """ iterate all children of a node """
    try:
        child = eggNode.getFirstChild()
        while child:
            yield child
            child = eggNode.getNextChild()
    except:
        pass
    
def iterVertexes(eggNode):
    """ iterate all vertexes of polygon or polylist """
    try:
        index = eggNode.getHighestIndex()
        for i in xrange(index+1):
            yield eggNode.getVertex(i)
    except:
        index = eggNode.getNumVertices()
        for i in xrange(index):
            yield eggNode.getVertex(i)
        pass

def eggLs(eggNode,indent=0):
    """ list whats in our egg """
    if eggNode.__class__.__name__ != "EggPolygon":
        print " "*indent+eggNode.__class__.__name__+" "+eggNode.getName()
        for eggChildren in iterChildren(eggNode):
            eggLs(eggChildren,indent+1)
        
def eggStripTexture(eggNode):
    """ strip textures and materials """
    if eggNode.__class__ == EggPolygon:
        eggNode.clearTexture() 
        eggNode.clearMaterial()       
    else:
        for eggChildren in iterChildren(eggNode):
            eggStripTexture(eggChildren)
            
            
def octreefy(infile,outfile):
    """ 
        octreefy infile and write to outfile 
        using the buildOctree functions 
    """
    egg = EggData()
    egg.read(Filename(infile))
    eggStripTexture(egg)
    group = egg
    vertexPool = False
    # find the fist group and fine the first vertexPool
    # you might have to mess with this if your egg files 
    # are in odd format
    for child in iterChildren(egg):
        if type(child) == EggVertexPool:
            vertexPool = child
        if type(child) == EggGroup:
            group = child
    # if we have not found the vertexPool it must be inside 
    if not vertexPool: 
        for child in iterChildren(group):
            if type(child) == EggVertexPool:
                vertexPool = child
    if vertexPool and group:
        ed = EggData()
        ed.setCoordinateSystem(egg.getCoordinateSystem())
        ed.addChild(vertexPool)
        ed.addChild(buildOctree(group))
        if listResultingEgg: eggLs(ed)
        ed.writeEgg(Filename(outfile))
        
def main():
    """ interface to our egg octreefier """
    try:
        optlist, list = getopt.getopt(sys.argv[1:], 'hlvo:n:')
    except Exception,e:
        print e
        sys.exit(0)
    global verbose,listResultingEgg,maxNumber
    outfile = False
    for opt in optlist:
        if opt[0] == '-h':
            print __doc__
            sys.exit(0)
        if opt[0] == '-l':
            listResultingEgg = True
        if opt[0] == '-v':
            verbose = True
        if opt[0] == '-n':
            maxNumber = int(opt[1])
        if opt[0] == '-o':
            outfile = opt[1]
    if outfile and len(list) > 1:
        print "error can have an outfile and more then one infile"
        sys.exit(0)
        
    for file in list:
        if '.egg' in file:
            if verbose: print "processing",file
            if outfile:
                octreefy(file,outfile)
            else:
                octreefy(file,file.replace(".egg","-octree.egg"))
                  
if __name__ == "__main__":
    import os
    main()