All pastes #1674224 Raw Edit

Updated Python script

public python v1 · immutable
#1674224 ·published 2009-11-17 00:39 UTC
rendered paste body
################################################################################ Dragon Age End User Toolset#### Lightmap processing script#### Author: James Goldman## Version: 1.0.1## Date: March 18, 2009#### Copyright (c) 2009 BioWare Corp.############################################################################### Version: 1.0.1 November 16, 2009# - Fixed bug in models with straight float vector vertex formatsimport win32comimport structimport pywintypesimport os.pathimport ctypesimport aergiaimport xml.dom.minidomfrom win32com.client import constantsimport sysimport getoptimport subprocessimport globimport shutilGAMMA = 1.5def MakeFourCC(str):    if len(str) != 4:        return 0    return ord(str[3]) << 24 | ord(str[2]) << 16 | ord(str[1]) << 8 | ord(str[0])def MatrixMultiply(lhs,rhs):    """Multiply two 4x4 matrices in the form of lists of 16 floats"""    if len(lhs) != len(rhs) or len(lhs) != 16:        print "Need 4x4 matrices"        return None    res = [0 for x in range(0, 16)]    for i in range(0,4):        for k in range(0,4):            for j in range(0,4):                res[i*4+k]+=lhs[i*4+j]*rhs[j*4+k]    return resclass GFFID:    GFF_NAME                                = 2    GFF_MMH_NAME                            = 6000    GFF_MODEL_HIERARCHY_MODEL_DATA_NAME     = 6005    GFF_MMH_MESH_GROUP_NAME                 = 6006    GFF_MMH_TRANSLATION                     = 6047    GFF_MMH_ROTATION                        = 6048    GFF_MMH_MESH_CAST_BAKED_SHADOW          = 6177    GFF_MMH_MESH_RECEIVE_BAKED_SHADOW       = 6301    GFF_MMH_CHILDREN                        = 6999        GFF_MESH_CHUNK_VERTEXSIZE               = 8000    GFF_MESH_CHUNK_VERTEXCOUNT              = 8001    GFF_MESH_CHUNK_INDEXCOUNT               = 8002    GFF_MESH_CHUNK_PRIMITIVETYPE            = 8003    GFF_MESH_CHUNK_INDEXFORMAT              = 8004    GFF_MESH_CHUNK_VERTEXOFFSET             = 8006    GFF_MESH_CHUNK_STARTINDEX               = 8009    GFF_MESH_BOUNDS_SPHERE                  = 8019    GFF_MESH_CHUNK_BOUNDS                   = 8020    GFF_MESH_CHUNKS                         = 8021    GFF_MESH_VERTEXDATA                     = 8022    GFF_MESH_INDEXDATA                      = 8023    GFF_MESH_CHUNK_VERTEXDECLARATOR         = 8025    GFF_MESH_VERTEXDECLARATOR_OFFSET        = 8027    GFF_MESH_VERTEXDECLARATOR_DATATYPE      = 8028    GFF_MESH_VERTEXDECLARATOR_USAGE         = 8029    GFF_MESH_VERTEXDECLARATOR_USAGEINDEX    = 8030class MSHChunk:    # Vertex data types    TYPE_FLOAT1     = 0    TYPE_FLOAT2     = 1    TYPE_FLOAT3     = 2    TYPE_FLOAT4     = 3    TYPE_COLOR      = 4    TYPE_UBYTE4     = 5    TYPE_SHORT2     = 6    TYPE_SHORT4     = 7    TYPE_UBYTE4N    = 8    TYPE_SHORT2N    = 9    TYPE_SHORT4N    = 10    TYPE_USHORT2N   = 11    TYPE_USHORT4N   = 12    TYPE_UDEC3      = 13    TYPE_DEC3N      = 14    TYPE_FLOAT16_2  = 15    TYPE_FLOAT16_4  = 16    # These are the only vertex usages we care about for lightmapping    USAGE_POSITION  = 0    USAGE_NORMAL    = 3    USAGE_TEXCOORD  = 5    # Index formats    IF_16  = 0    IF_32 = 1    # Primitive types    PT_TRIANGLE = 0    def Float16To32(self, num):        """Convert half-precision float to full float"""        z = ctypes.c_long((num & 0x8000) << 16) # Sign bit        z.value = z.value | (num & 0x03ff) << 13 # Position mantissa        z.value = z.value | (((num & 0x7C00) >> 10) + 0x70) << 23 # Rebias and position exponent        return struct.unpack_from("f", z)[0]    def ParseVertex(self, VertexData, offset, format):        """Parse the data at the given offset using the given format into a list of floats"""        if format == MSHChunk.TYPE_FLOAT1:            return map(float, struct.unpack_from("f", VertexData, offset))        elif format == MSHChunk.TYPE_FLOAT2:            return map(float, struct.unpack_from("ff", VertexData, offset))        elif format == MSHChunk.TYPE_FLOAT3:            return map(float, struct.unpack_from("fff", VertexData, offset))        elif format == MSHChunk.TYPE_FLOAT4:            return map(float, struct.unpack_from("ffff", VertexData, offset))        elif format == MSHChunk.TYPE_UBYTE4:            return float(ord(VertexData[offset]))        elif format == MSHChunk.TYPE_UBYTE4N:            return float(ord(VertexData[offset])) / 255.0        elif format == MSHChunk.TYPE_SHORT2:            return map(float, struct.unpack_from("hh", VertexData, offset))        elif format == MSHChunk.TYPE_SHORT4:            return map(float, struct.unpack_from("hhhh", VertexData, offset))        elif format == MSHChunk.TYPE_SHORT2N:            return map(lambda x: float(x) / 65535.0, struct.unpack_from("hh", VertexData, offset))        elif format == MSHChunk.TYPE_SHORT4N:            return map(lambda x: float(x) / 65535.0, struct.unpack_from("hhhh", VertexData, offset))        elif format == MSHChunk.TYPE_UDEC3:            x = struct.unpack_from("I", VertexData, offset)[0]            return [float(x & 0x3ff), float(x >> 10 & 0x3ff), float(x >> 20 & 0x3ff)]        elif format == MSHChunk.TYPE_DEC3N:            x = struct.unpack_from("I", VertexData, offset)[0]            return [float(x & 0x3ff)/511.0, float(x >> 10 & 0x3ff)/511.0, float(x >> 20 & 0x3ff)/511.0]        elif format == MSHChunk.TYPE_FLOAT16_2:            return map(self.Float16To32, struct.unpack_from("HH", VertexData, offset))        elif format == MSHChunk.TYPE_FLOAT16_4:            return map(self.Float16To32, struct.unpack_from("HHHH", VertexData, offset))    def __init__(self, reader, VertexData, IndexData):        if reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_PRIMITIVETYPE) != MSHChunk.PT_TRIANGLE:            print "Primitive types other than triangle not currently supported."            return        self.name = reader.GetValueByLabel(GFFID.GFF_NAME)        vertexSize = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_VERTEXSIZE)        vertexCount = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_VERTEXCOUNT)        chunkOffset = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_VERTEXOFFSET)        # Bounding sphere for light culling        self.bsphere = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_BOUNDS).GetValueByLabel(GFFID.GFF_MESH_BOUNDS_SPHERE)        declarators = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_VERTEXDECLARATOR)        # Position declaration        self.positions = []        rdr = [x for x in declarators if x.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGE) == MSHChunk.USAGE_POSITION][0]        posOffset = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_OFFSET)        posType = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_DATATYPE)        # Normal declaration        # TODO: Normals are not currently getting to Yafray. Lightmaps would look much better with them, though        self.normals = []        normalType = None        try:            rdr = [x for x in declarators if x.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGE) == MSHChunk.USAGE_NORMAL][0]            normalOffset = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_OFFSET)            normalType = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_DATATYPE)        except IndexError:            pass # No normal data found - occluding geometry?        # Texture coordinate declaration        self.texcoords = []        texcoordType = None        try:            # Try to get a texture coordinate declarator in channel one, where most models have their UV            # data. If that doesn't exist, it might be terrain so try channel zero. After that, give up.            tcdecl = [x for x in declarators if x.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGE) == MSHChunk.USAGE_TEXCOORD]            try:                rdr = [x for x in tcdecl if x.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGEINDEX) == 1][0]            except IndexError:                rdr = [x for x in tcdecl if x.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGEINDEX) == 0][0]            texcoordOffset = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_OFFSET)            texcoordType = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_DATATYPE)            texcoordUsage = rdr.GetValueByLabel(GFFID.GFF_MESH_VERTEXDECLARATOR_USAGEINDEX)        except (IndexError, pywintypes.com_error):            pass # No texture coordinate data found - occluding geometry?        # Now we can actually parse the BLOB into vertices        for i in range(0, vertexCount):            baseOffset = chunkOffset + i * vertexSize            self.positions += self.ParseVertex(VertexData, baseOffset + posOffset, posType)[0:3] # Drop W element, if it exists            if texcoordType != None:                vert = self.ParseVertex(VertexData, baseOffset + texcoordOffset, texcoordType)                vert[1] = 1.0 - vert[1] # Yafray seems to use a different coordinate space than our tools                self.texcoords += vert            if normalType != None:                self.normals += self.ParseVertex(VertexData, baseOffset + normalOffset, normalType)        # Read in the indices.        indexFormat = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_INDEXFORMAT)        indexCount = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_INDEXCOUNT)        indexStart = reader.GetValueByLabel(GFFID.GFF_MESH_CHUNK_STARTINDEX)        fmt = "h" if indexFormat == MSHChunk.IF_16 else "i"        self.indices = map(int, struct.unpack_from(fmt*indexCount, IndexData, indexStart*struct.calcsize(fmt)))    def CreateAergiaMesh(self, transform, material, scene):        vertexbuffer = aergia.buffer(aergia.BufferUsage_Position, len(self.positions))        vertexbuffer.setData(self.positions)        vertexcount = len(self.positions)/3        UVcount = len(self.texcoords)/2        texcoordbuffer = aergia.buffer(aergia.BufferUsage_Texcoord, len(self.texcoords))        if UVcount > 0:            texcoordbuffer.setData(self.texcoords)        indexbuffer = aergia.buffer(aergia.BufferUsage_Index, len(self.indices))        indexbuffer.setData(self.indices)        tricount = len(self.indices)/3        return aergia.mesh(self.name, vertexbuffer, 0, vertexcount,            texcoordbuffer, 0, UVcount,            indexbuffer, 0, tricount,            transform, self.bsphere, scene, material)class GFFMSHReader:    def __init__(self, filename):        self.chunks = {}        try:            self.reader = win32com.client.gencache.EnsureDispatch("Bioware.Eclipse.EngGFFReader")            self.reader.LoadGFFFromFile(filename)        except pywintypes.com_error:            print "Could not load form %s" % (filename)            return        if self.reader.MagicNumber != constants.EngGFFMagicNumber or self.reader.GFFVersion != constants.EngGFFVersion40:            print "Error: %s is not a GFF 4.0 file" % (filename)            return        try:            # Get the shared raw data            VertexData = self.reader.TopLevelStruct.GetValueByLabel(GFFID.GFF_MESH_VERTEXDATA)            IndexData = self.reader.TopLevelStruct.GetValueByLabel(GFFID.GFF_MESH_INDEXDATA)            # Create the chunks            for reader in self.reader.TopLevelStruct.GetValueByLabel(GFFID.GFF_MESH_CHUNKS):                chunk = MSHChunk(reader, VertexData, IndexData)                self.chunks[chunk.name] = chunk        except pywintypes.com_error:            print "Error parsing %s" % (filename)def GetMMHTransform(reader):    """Read the transform out of the child nodes of the given GFF structure."""    t = (0, 0, 0, 1)    r = (0, 0, 0, 1)    for child in [ref.GetTargetValue() for ref in reader.GetValueByLabel(GFFID.GFF_MMH_CHILDREN)]:        if child.StructType == MakeFourCC("trsl"):            t = child.GetValueByLabel(GFFID.GFF_MMH_TRANSLATION)        elif child.StructType == MakeFourCC("rota"):            r = child.GetValueByLabel(GFFID.GFF_MMH_ROTATION)    return [1-2*(r[1]*r[1]+r[2]*r[2]), 2*(r[0]*r[1]-r[3]*r[2]), 2*(r[0]*r[2]+r[3]*r[1]), t[0],        2*(r[0]*r[1]+r[3]*r[2]), 1-2*(r[0]*r[0]+r[2]*r[2]), 2*(r[1]*r[2]-r[3]*r[0]), t[1],        2*(r[0]*r[2]-r[3]*r[1]), 2*(r[1]*r[2]+r[3]*r[0]), 1-2*(r[0]*r[0]+r[1]*r[1]), t[2],        0, 0, 0, t[3] ]class MMHPart:    def __init__(self, structReader, transform):        # Note: It appears that our binarizer will not create sub-parts, so no need to propagate the transform any further        self.receive_baked_shadow = False        self.cast_baked_shadow = False        self.transform = MatrixMultiply(transform, GetMMHTransform(structReader))        self.name = ""        try:            self.name = structReader.GetValueByLabel(GFFID.GFF_MMH_NAME)            self.meshgroup = structReader.GetValueByLabel(GFFID.GFF_MMH_MESH_GROUP_NAME)            self.receive_baked_shadow = structReader.GetValueByLabel(GFFID.GFF_MMH_MESH_RECEIVE_BAKED_SHADOW) != 0            self.cast_baked_shadow = structReader.GetValueByLabel(GFFID.GFF_MMH_MESH_CAST_BAKED_SHADOW) != 0        except pywintypes.com_error:            passclass GFFMMHReader:    def __init__(self, filename):        self.meshreader = None        self.parts = []        """Read in and parse an MMH and its associated meshes."""        try:            # EnsureDispatch generates the wrapper class if necessary            reader = win32com.client.gencache.EnsureDispatch("Bioware.Eclipse.EngGFFReader")        except pywintypes.com_error:            print "Could not get a GFF reader object. Make sure Engine.dll is registered."            return        try:            reader.LoadGFFFromFile(filename)        except pywintypes.com_error:            print "Could not load from %s" % (filename)            return        if reader.MagicNumber != constants.EngGFFMagicNumber or reader.GFFVersion != constants.EngGFFVersion40:            print "Error: %s is not a GFF 4.0 file" % (filename)            return        try:            # Load the mesh file            meshpath = os.path.join(os.path.dirname(filename), reader.TopLevelStruct.GetValueByLabel(GFFID.GFF_MODEL_HIERARCHY_MODEL_DATA_NAME))            if not os.path.exists(meshpath):                print "Error: mesh file referenced in %s does not exist in the directory." % (filename)                return            self.meshreader = GFFMSHReader(meshpath)            # Get the root node. Make sure there's only one            roots = [ref.GetTargetValue() for ref in reader.TopLevelStruct.GetValueByLabel(GFFID.GFF_MMH_CHILDREN)]            if len(roots) != 1:                print "Error: model in %s does not have exactly one root node" % (filename)                return            # Get the transform for the whole works            transform = GetMMHTransform(roots[0])            # The root should have mesh nodes as its direct children            parts = [ ref.GetTargetValue() for ref in roots[0].GetValueByLabel(GFFID.GFF_MMH_CHILDREN) if ref.GetTargetValue().StructType == MakeFourCC("mshh")]            if len(parts) == 0:                print "Error: model in %s contains no parts." % (filename)                return            for part in parts:                self.parts.append(MMHPart(part, transform))        except pywintypes.com_error:            print "Error parsing %s" % (filename)    def CreateMeshes(self, transform, material, scene):        """Create and add the meshes to the scene, and return a map of chunk names --> mesh instances."""            chunks = {}        for part in self.parts:            chunks[part.name] = self.meshreader.chunks[part.meshgroup].CreateAergiaMesh(                aergia.geometry.matrix4x4(MatrixMultiply(transform, part.transform)), material, scene)        return chunksdef BuildScene(scenefile, instances):    """Parse the scene xml and build the scene in aergia"""    material = aergia.materials.shinydiffuse( 'whitemat',         aergia.geometry.vector3d(1.0, 1.0, 1.0), 0.0, 0.0, 1.0, 0.0, 0.0, 1.3, 0.0 )    scene = aergia.scene()    xmldoc = xml.dom.minidom.parse(scenefile)    for object in xmldoc.getElementsByTagName("Object"):        mmhreader = GFFMMHReader(object.getAttribute("Source"))        for instance in object.getElementsByTagName("Instance"):            instanceName = instance.getAttribute("Name")            transform = map(lambda x: float(instance.getElementsByTagName("Transform")[0].getAttribute(x)),                 ["M00","M10","M20","M30","M01","M11","M21","M31","M02","M12","M22","M32","M03","M13","M23","M33"])            instances[instanceName] = mmhreader.CreateMeshes(transform, material, scene)    return scenedef ParseLight(xmlnode, forLightmap):    type = xmlnode.getAttribute("Type").lower()    castShadows = True if xmlnode.getAttribute("CastShadows") == "true" else False        brightness = float(xmlnode.getElementsByTagName("Brightness")[0].childNodes[0].nodeValue)    colour = map(lambda x: float(xmlnode.getElementsByTagName("Colour")[0].getAttribute(x)), ("R", "G", "B"))    transform = aergia.geometry.matrix4x4(map(lambda x: float(xmlnode.getElementsByTagName("Transform")[0].getAttribute(x)),             ["M00","M10","M20","M30","M01","M11","M21","M31","M02","M12","M22","M32","M03","M13","M23","M33"]))    # If this is for the shadowmaps, check to see if it's marked as casting shadows    if not forLightmap and not castShadows:        brightness = 0.0        colour = [0.0, 0.0, 0.0]    GUIDNode = xmlnode.getElementsByTagName("GUID")[0]    GUID = GUIDNode.getAttribute("A") + GUIDNode.getAttribute("B") + GUIDNode.getAttribute("C") + GUIDNode.getAttribute("D")    radius = 0.0    innerAngle = 0.0    outerAngle = 30.0    try:        radius = float(xmlnode.getElementsByTagName("Radius")[0].childNodes[0].nodeValue)        innerAngle = float(xmlnode.getElementsByTagName("InnerRadius")[0].childNodes[0].nodeValue) * 180.0 / 3.141592        outerAngle = float(xmlnode.getElementsByTagName("OuterRadius")[0].childNodes[0].nodeValue) * 180.0 / 3.141592    except IndexError:        pass    col = aergia.geometry.vector3d(colour[0], colour[1], colour[2])    position = aergia.geometry.vector3d(0.0, 0.0, 0.0)    position.transform(transform)    if type == "point":        return GUID, aergia.lights.point(GUID, position, col, brightness, radius)    elif type == "ambient":        # TODO: brightness for ambients        return GUID, aergia.lights.ambient(GUID, col)        pass    elif type == "directional":        return GUID, aergia.lights.directional(GUID, col, brightness)    elif type == "spot":        # TODO: radius for spotlights not supported by Yafray?        falloff = 1.0        if outerAngle > 0:            falloff = 1.0 - (innerAngle/outerAngle)        to = aergia.geometry.vector3d(0.0,-1.0,0.0)        to.transform(transform)        return GUID, aergia.lights.spot(GUID, col, outerAngle, position, to, brightness, falloff, radius)def ProcessLightmapJob(scene, instances, jobfile):    """Process a single lightmap job file."""    print "Processing lightmap job %s ..." % (jobfile)    sys.stdout.flush()    MAXRAYDEPTH = 6    scene.addObject(aergia.integrators.directlight('integator', MAXRAYDEPTH, 0, 0, 0))    # Get the lights    xmldoc = xml.dom.minidom.parse(jobfile)    for lightnode in xmldoc.getElementsByTagName("Light"):        light = ParseLight(lightnode, True)        if light != None:            scene.addObject(light[1])    # Get the render targets (i.e. the objects to be lightmapped)    for target in xmldoc.getElementsByTagName("RenderTarget"):        modelkey = target.getAttribute("Model")        modelpart = target.getAttribute("Part")        width = int(target.getAttribute("SizeX"))        height = int(target.getAttribute("SizeY"))        outfile = target.getAttribute("OutputFile")        try:            if not terse:                print "Processing %s" % (os.path.basename(outfile))                sys.stdout.flush()            instance = instances[modelkey]            mesh = instance[modelpart]            film = aergia.film(outfile, width, height, aergia.FilmFilterType_Gauss, 1, GAMMA, 1, 0)            camera = aergia.lightmapcam(film, mesh)            scene.render(film, camera)        except KeyError:            if not terse:                print "Failed to find part %s on %s" % (modelpart, modelkey)def ProcessShadowmapJob(scene, instances, jobfile):    """Process a single shadowmap job file."""    MAXRAYDEPTH = 6    scene.addObject(aergia.integrators.directlight('integator', MAXRAYDEPTH, 0, 0, 1))    print "Processing shadow map job %s ..." % (jobfile)    sys.stdout.flush()    # Get the lights    xmldoc = xml.dom.minidom.parse(jobfile)    for lightnode in xmldoc.getElementsByTagName("Light"):        light = ParseLight(lightnode, False)        if light != None:            scene.addObject(light[1])    # Get the render targets (i.e. the objects to be lightmapped)    for target in xmldoc.getElementsByTagName("RenderTarget"):        modelkey = target.getAttribute("Model")        modelpart = target.getAttribute("Part")        width = int(target.getAttribute("SizeX"))        height = int(target.getAttribute("SizeY"))        outfile = target.getAttribute("OutputFile")        try:            if not terse:                print "Processing %s" % (os.path.basename(outfile))                sys.stdout.flush()            instance = instances[modelkey]            mesh = instance[modelpart]            film = aergia.film(outfile, width, height, aergia.FilmFilterType_Gauss, 1, 1, 1, 0)            camera = aergia.lightmapcam(film, mesh)            scene.render(film, camera)        except KeyError:            if not terse:                print "Failed to find part %s on %s" % (modelpart, modelkey)def ProcessAmbientOcclusionJob(scene, instances, jobfile):    """Process a single ambient occlusion job file."""    # TODO: read the ray length and number of samples from the ao job file    raylength = 5    aorays = 5    scene.addObject(aergia.integrators.ambientocclusion(        'AOIntegrator', aorays, raylength, aergia.geometry.vector3d(1.0, 1.0, 1.0)))    print "Processing ambient occlusion job %s ..." % (jobfile)    sys.stdout.flush()    xmldoc = xml.dom.minidom.parse(jobfile)    # Get the render targets (i.e. the objects to be lightmapped)    for target in xmldoc.getElementsByTagName("RenderTarget"):        modelkey = target.getAttribute("Model")        modelpart = target.getAttribute("Part")        width = int(target.getAttribute("SizeX"))        height = int(target.getAttribute("SizeY"))        outfile = target.getAttribute("OutputFile")        try:            if not terse:                print "Processing %s" % (os.path.basename(outfile))                sys.stdout.flush()            instance = instances[modelkey]            mesh = instance[modelpart]            film = aergia.film(outfile, width, height, aergia.FilmFilterType_Gauss, 1, GAMMA, 1, 0)            camera = aergia.lightmapcam(film, mesh)            scene.render(film, camera)        except KeyError:            if not terse:                print "Failed to find part %s on %s" % (modelpart, modelkey)def ProcessJobs(folder, processor, basename):    """Process all numbered jobs found in the given folder which match the given template."""    i = 0    while True:        jobfile = os.path.join(folder, basename % (i))        if not os.path.exists(jobfile): break        instances = {}        # Create the scene in the raytracer environment        scene = BuildScene(os.path.join(folder, "job_scene.xml"), instances)        processor(scene, instances, jobfile)        i = i + 1        aergia.ClearAll()def ProcessFolder(folder):    """Processes one folder containing lightmapping and/or shadowmapping and/or ambient occlusion jobs."""    ProcessJobs(folder, ProcessLightmapJob, "job_lm%d.xml")    ProcessJobs(folder, ProcessAmbientOcclusionJob, "job_ao%d.xml")    ProcessJobs(folder, ProcessShadowmapJob, "job_sm%d.xml")def DefaultDir(dir, default):    """Test to see if the directory name is specified. If not, create the given default directory."""    if len(dir) == 0:        dir = default        if not os.path.exists(dir):            os.mkdir(dir)    return os.path.normpath(dir)def Execute(command):    startupinfo = subprocess.STARTUPINFO()    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW    p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo = startupinfo)    outtext, errtext = p.communicate()    if len(outtext) != 0:        print outtext    if p.returncode == 0:        return True    return Falsedef main():    FAIL = 1    SUCCESS = 0    options = 'i:n:o:c:m:e:v'    longOptions = ['inputDir=',                   'outputTGADir=',                   'outputAoTGADir=',                   'combinedDDSDir=',                   'compressedDDSDir=',                   'atlasedDDSDir=',                   'numSubJobs=',       # Not actually used, but maybe should be                   'atlasFile=',                   'in_width=',                   'in_height=',                   'notify_complete',                   'terse',                   'cpus=']             # This is here to avoid the GetoptError. It's actually parsed by eclipseray :S    try:        opts, pargs = getopt.getopt(sys.argv, options, longOptions)    except getopt.GetoptError:        print "Lightmapper.py error: Invalid input parameter specified."        return FAIL    global terse    inputDir = ""    numSubJobs = 1    outputTGADir = ""    outputAoTGADir = ""    combinedDDSDir = ""    atlasedDDSDir = ""    compressedDDSDir = ""    atlasFile = ""    in_width = "512"    in_height = "512"    notify_complete = False    terse = False    for opt in opts:        if opt[0] == "--inputDir":            inputDir = opt[1]        elif opt[0] == "--numSubJobs":            numSubJobs = int(opt[1])        elif opt[0] == "--outputTGADir":            outputTGADir = opt[1]        elif opt[0] == "--outputAoTGADir":            outputAoTGADir = opt[1]        elif opt[0] == "--combinedDDSDir":            combinedDDSDir = opt[1]        elif opt[0] == "--atlasedDDSDir":            atlasedDDSDir = opt[1]        elif opt[0] == "--compressedDDSDir":            compressedDDSDir = opt[1]        elif opt[0] == "--atlasFile":            atlasFile = opt[1]        elif opt[0] == "--in_width":            in_width = opt[1]        elif opt[0] == "--in_height":            in_height = opt[1]        elif opt[0] == "--notify_complete":            notify_complete == True        elif opt[0] == "--terse":            terse = True;    if not os.path.exists(inputDir):        print "Invalid input directory"        return FAIL    outputTGADir = DefaultDir(outputTGADir, os.path.join(inputDir, "TGA"))    if not os.path.exists(outputTGADir):        print "Invalid TGA directory"        return FAIL    combinedDDSDir = DefaultDir (combinedDDSDir, os.path.join(inputDir, "PreAtlas"))    if not os.path.exists(combinedDDSDir):        print "Invalid combined DDS directory"        return FAIL    outputAoTGADir = DefaultDir (outputAoTGADir, os.path.join(inputDir, "AO"))        if not os.path.exists(outputAoTGADir):        print "Invalid AO output TGA directory"        return FAIL    compressedDDSDir = DefaultDir (compressedDDSDir, os.path.join(inputDir, "CombinedMaps"))    if not os.path.exists(compressedDDSDir):        print "Invalid compressed DDS directory"        return FAIL    atlasedDDSDir = DefaultDir (atlasedDDSDir, os.path.join(inputDir, "Atlased"))    if not os.path.exists(atlasedDDSDir):        print "Invalid atlased DDS directory"        return FAIL    if len(atlasFile) == 0:        atlasFile = os.path.join(inputDir, "job_atlas.xml")    if not os.path.exists(atlasFile):        print "Invalid atlas job file"        return FAIL    # Create the input textures    ProcessFolder(inputDir)    # Test the raytracing environment for errors    if aergia.errors.hasErrors():        print '(%d) errors were detected:' % (aergia.errors.getCount())        for error in aergia.errors.getErrors():            print error        return FAIL    # Combine the textures    if not Execute(["BakedMapProcessor.exe", "-in_lm", outputTGADir,         "-in_sm", outputTGADir, "-in_ao", outputAoTGADir, "-out", combinedDDSDir]):        print "FAILED to combine the textures"        return FAIL    # Atlas the textures    if not Execute(["CreateAtlas.exe", "-in", combinedDDSDir, "-out", atlasedDDSDir,         "-width", "1024", "-height", "1024", "-gutter", "0", "-halftexel",        "-in_width", in_width, "-in_height", in_height, "-file", atlasFile]):        print "FAILED to atlas output textures"        return FAIL    for fname in glob.glob(os.path.join(atlasedDDSDir, "*dat.xml")):        shutil.move(fname, compressedDDSDir)    # Compress    if not Execute(["TextureProcessor.exe", "-dxt5", "-nomipmap",         "-input_directory", atlasedDDSDir, "-output_directory", compressedDDSDir]):        print "FAILED to compress atlased textures"        return FAIL    for fname in glob.glob(os.path.join(atlasedDDSDir, "*.meta")):        shutil.copy(fname, compressedDDSDir)    #Done    return SUCCESSif __name__ == '__main__':    print "Running lightmapper script."    try:        retval = main()        if retval != 0:            sys.exit(retval)    except:        print "Lightmapper script failed."        print sys.argv        sys.exit(1)    print "Lightmapper script completed successfully."