Exient XGS Engine *.XGM (MODELS)

Skeletons, animations, shaders, texturing, converting, fixing and anything else related to read game models
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Exient XGS Engine *.XGM (MODELS)

Post by LolHacksRule »

Any idea on how to open XGM files properly? It would help a lot. The 10 byte header is always : 15 00 00 00 18 00 00 00 58 47 53 4D 26 01 01 01. I heard hex2obj is the best for this but I'm not sure...

ABGO full game dump: https://drive.google.com/open?id=10Lp3D ... JdUTljgDzo
Last edited by LolHacksRule on Fri Dec 13, 2019 5:25 pm, edited 3 times in total.
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Angry Birds: Transformers/Go .XGM (MODELS)

Post by LolHacksRule »

Anything?
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Angry Birds: Transformers/Go .XGM (MODELS)

Post by LolHacksRule »

Bump again? Its not just me looking for these...
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Exient XGS Engine: Angry Birds: Transformers/Go .XGM (MODELS)

Post by LolHacksRule »

They are in the "models" directory of the game data in case you can't find them.
Acewell
Posts: 706
Joined: Fri Aug 08, 2014 1:06 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by Acewell »

maybe moving this to "3D/2D models" will gain the attention it needs? :)
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by LolHacksRule »

I already sent a report to do so but thanks anyways.
DJ Normality
Posts: 647
Joined: Tue Jul 24, 2018 8:52 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by DJ Normality »

This is your layout.
Image
This is your UV's
Image
Faces start right after last UV
Image
Faces
Image
Render
Image
Here is the file
https://drive.google.com/file/d/1KpWe2o ... sp=sharing
DJ Normality
Posts: 647
Joined: Tue Jul 24, 2018 8:52 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by DJ Normality »

Sorry. I would of posted sooner but don't really look in here. He's right this should be in 3D Models :D
LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by LolHacksRule »

DUDE I LOVE YOU! But how would I open them? I don't have that program...
DJ Normality
Posts: 647
Joined: Tue Jul 24, 2018 8:52 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by DJ Normality »

LolHacksRule
Posts: 865
Joined: Fri Apr 20, 2018 12:41 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by LolHacksRule »

Hell yeah!
Asdfguy86
Posts: 18
Joined: Sun Oct 13, 2019 1:00 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by Asdfguy86 »

DJ Normality wrote:Sorry. I would of posted sooner but don't really look in here. He's right this should be in 3D Models :D

even more progress made for this not well known engine. do you think you could explain your process on how you found out what is what? i would like to do the same with the XGM models in need for speed hot pursuit (the wii version).
i have all of the game's files on this google drive link.
JCBurgos01
Posts: 7
Joined: Sat May 09, 2020 11:33 pm

Re: Exient XGS Engine *.XGM (MODELS)

Post by JCBurgos01 »

JCBurgos01
Posts: 7
Joined: Sat May 09, 2020 11:33 pm

Re: Exient XGS Engine *.XGM (MODELS)

Post by JCBurgos01 »

JCBurgos01 wrote:What about these files xgm?
https://www.mediafire.com/folder/lmyr80 ... es_Example

Nothing? Please
mariokart64n
Posts: 12
Joined: Fri Aug 08, 2014 12:59 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by mariokart64n »

blender python script, goto the script tab press new and paste the script. press the play button (Alt + P) and you can open a xgm and import it into blender. From that point the script is registered and you can access the import function through the File > Import context until blender is restarted.

Code: Select all

""" ======================================================================

    PythonScript:   [Mobile] Angry Birds Transformers
    Author:         mariokart64n
    Date:           March 07, 2021
    Version:        0.1

    ======================================================================

    ChangeLog:

    2021-03-37
        Script Wrote
   

    ====================================================================== """

import bpy  # Needed to interface with blender
import struct  # Needed for Binary Reader
import math
from pathlib import Path  # Needed for os stuff

useOpenDialog = True

#
# ====================================================================================
# MAXCSRIPT FUNCTIONS
# ====================================================================================
# These function are written to mimic native functions in
# maxscript. This is to make porting my old maxscripts
# easier, so alot of these functions may be redundant..
# ====================================================================================
#

signed, unsigned = 0, 1  # Enums for read function
seek_set, seek_cur, seek_end = 0, 1, 2  # Enums for seek function

def messageBox(message="", title="Message Box", icon='INFO'):
    def draw(self, context): self.layout.label(text=message)

    bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
    return None

def getFileSize(filename):
    return Path(filename).stat().st_size

def clearListener(len=64):
    for i in range(0, len): print('')

def append(array, value):
    array.append(value)
    return None

class fopen:
    little_endian = True
    file = ""
    mode = 'rb'
    data = bytearray()
    size = 0
    pos = 0
    isGood = False

    def __init__(self, filename=None, mode='rb', isLittleEndian=True):
        if mode == 'rb':
            if filename != None and Path(filename).is_file():
                self.data = open(filename, mode).read()
                self.size = len(self.data)
                self.pos = 0
                self.mode = mode
                self.file = filename
                self.little_endian = isLittleEndian
                self.isGood = True
        else:
            self.file = filename
            self.mode = mode
            self.data = bytearray()
            self.pos = 0
            self.size = 0
            self.little_endian = isLittleEndian
            self.isGood = False

        return None

    # def __del__(self):
    #    self.flush()

    def resize(self, dataSize=0):
        if dataSize > 0:
            self.data = bytearray(dataSize)
        else:
            self.data = bytearray()
        self.pos = 0
        self.size = dataSize
        self.isGood = False
        return None

    def flush(self):
        print("flush")
        print("file:\t%s" % self.file)
        print("isGood:\t%s" % self.isGood)
        print("size:\t%s" % len(self.data))
        if self.file != "" and not self.isGood and len(self.data) > 0:
            self.isGood = True

            s = open(self.file, 'w+b')
            s.write(self.data)
            s.close()

    def read_and_unpack(self, unpack, size):
        '''
          Charactor, Byte-order
          @,         native, native
          =,         native, standard
          <,         little endian
          >,         big endian
          !,         network

          Format, C-type,         Python-type, Size[byte]
          c,      char,           byte,        1
          b,      signed char,    integer,     1
          B,      unsigned char,  integer,     1
          h,      short,          integer,     2
          H,      unsigned short, integer,     2
          i,      int,            integer,     4
          I,      unsigned int,   integer,     4
          f,      float,          float,       4
          d,      double,         float,       8
        '''
        value = 0
        if self.size > 0 and self.pos + size < self.size:
            value = struct.unpack_from(unpack, self.data, self.pos)[0]
            self.pos += size
        return value

    def pack_and_write(self, pack, size, value):
        if self.pos + size > self.size:
            self.data.extend(b'\x00' * ((self.pos + size) - self.size))
            self.size = self.pos + size
        try:
            struct.pack_into(pack, self.data, self.pos, value)
        except:
            print('Pos:\t%i / %i (buf:%i) [val:%i:%i:%s]' % (self.pos, self.size, len(self.data), value, size, pack))
            pass
        self.pos += size
        return None

    def set_pointer(self, offset):
        self.pos = offset
        return None


def fclose(bitStream):
    bitStream.flush()
    bitStream.isGood = False


def fseek(bitStream, offset, dir):
    if dir == 0:
        bitStream.set_pointer(offset)
    elif dir == 1:
        bitStream.set_pointer(bitStream.pos + offset)
    elif dir == 2:
        bitStream.set_pointer(bitStream.pos - offset)
    return None


def ftell(bitStream):
    return bitStream.pos


def readByte(bitStream, isSigned=0):
    fmt = 'b' if isSigned == 0 else 'B'
    return (bitStream.read_and_unpack(fmt, 1))


def readShort(bitStream, isSigned=0):
    fmt = '>' if not bitStream.little_endian else '<'
    fmt += 'h' if isSigned == 0 else 'H'
    return (bitStream.read_and_unpack(fmt, 2))


def readLong(bitStream, isSigned=0):
    fmt = '>' if not bitStream.little_endian else '<'
    fmt += 'i' if isSigned == 0 else 'I'
    return (bitStream.read_and_unpack(fmt, 4))


def readFloat(bitStream):
    fmt = '>f' if not bitStream.little_endian else '<f'
    return (bitStream.read_and_unpack(fmt, 4))


def readString(bitStream, length=0):
    string = ''
    pos = bitStream.pos
    lim = length if length != 0 else bitStream.size - bitStream.pos
    for i in range(0, lim):
        b = bitStream.read_and_unpack('B', 1)
        if b != 0:
            string += chr(b)
        else:
            if length > 0:
                bitStream.set_pointer(pos + length)
            break
    return string


def mesh(vertices=[], faces=[], materialIDs=[], tverts=[], normals=[], colours=[], materials=[], mscale=1.0, flipAxis=False, obj_name="Object", lay_name=''):
    #
    # This function is pretty, ugly
    # imports the mesh into blender
    #

    # Clear Any Object Selections
    # for o in bpy.context.selected_objects: o.select = False
    bpy.context.view_layer.objects.active = None

    # Get Collection (Layers)
    if lay_name != '':
        # make collection
        layer = bpy.data.collections.new(lay_name)
        bpy.context.scene.collection.children.link(layer)
    else:
        layer = bpy.data.collections[bpy.context.view_layer.active_layer_collection.name]

    # make mesh
    msh = bpy.data.meshes.new('Mesh')

    # msh.name = msh.name.replace(".", "_")

    # Apply vertex scaling
    # mscale *= bpy.context.scene.unit_settings.scale_length
    if len(vertices) > 0:
        vertArray = [[float] * 3] * len(vertices)
        if flipAxis:
            for v in range(0, len(vertices)):
                vertArray[v] = (
                    vertices[v][0] * mscale,
                    -vertices[v][2] * mscale,
                    vertices[v][1] * mscale
                )
        else:
            for v in range(0, len(vertices)):
                vertArray[v] = (
                    vertices[v][0] * mscale,
                    vertices[v][1] * mscale,
                    vertices[v][2] * mscale
                )

    # assign data from arrays
    msh.from_pydata(vertArray, [], faces)

    # set surface to smooth
    msh.polygons.foreach_set("use_smooth", [True] * len(msh.polygons))

    # Set Normals
    if len(faces) > 0:
        if len(normals) > 0:
            msh.use_auto_smooth = True
            if len(normals) == (len(faces) * 3):
                msh.normals_split_custom_set(normals)
            else:
                normArray = [[float] * 3] * (len(faces) * 3)
                if flipAxis:
                    for i in range(0, len(faces)):
                        for v in range(0, 3):
                            normArray[(i * 3) + v] = (
                                [normals[faces[i][v]][0],
                                 -normals[faces[i][v]][2],
                                 normals[faces[i][v]][1]]
                            )
                else:
                    for i in range(0, len(faces)):
                        for v in range(0, 3):
                            normArray[(i * 3) + v] = (
                                [normals[faces[i][v]][0],
                                 normals[faces[i][v]][1],
                                 normals[faces[i][v]][2]]
                            )
                msh.normals_split_custom_set(normArray)

        # create texture corrdinates
        print("tverts ", len(tverts))
        # this is just a hack, i just add all the UVs into the same space <<<
        if len(tverts) > 0:
            uvw = msh.uv_layers.new()
            # if len(tverts) == (len(faces) * 3):
            #    for v in range(0, len(faces) * 3):
            #        msh.uv_layers[uvw.name].data[v].uv = tverts[v]
            # else:
            uvwArray = [[float] * 2] * len(tverts[0])
            for i in range(0, len(tverts[0])):
                uvwArray[i] = [0.0, 0.0]

            for v in range(0, len(tverts[0])):
                for i in range(0, len(tverts)):
                    uvwArray[v][0] += tverts[i][v][0]
                    uvwArray[v][1] += 1.0 - tverts[i][v][1]

            for i in range(0, len(faces)):
                for v in range(0, 3):
                    msh.uv_layers[uvw.name].data[(i * 3) + v].uv = (
                        uvwArray[faces[i][v]][0],
                        uvwArray[faces[i][v]][1]
                    )

        # create vertex colours
        if len(colours) > 0:
            col = msh.vertex_colors.new()
            if len(colours) == (len(faces) * 3):
                for v in range(0, len(faces) * 3):
                    msh.vertex_colors[col.name].data[v].color = colours[v]
            else:
                colArray = [[float] * 4] * (len(faces) * 3)
                for i in range(0, len(faces)):
                    for v in range(0, 3):
                        msh.vertex_colors[col.name].data[(i * 3) + v].color = colours[faces[i][v]]


    # Create Face Maps?
    # msh.face_maps.new()

    # Update Mesh
    msh.update()

    # Check mesh is Valid
    if msh.validate():
        # Erase Mesh
        print("Mesh Invalid!")
        msh.user_clear()
        bpy.data.meshes.remove(msh)
        return None

    # Assign Mesh to Object
    obj = bpy.data.objects.new(obj_name, msh)
    # obj.name = obj.name.replace(".", "_")

    for i in range(0, len(materials)):

        if len(obj.material_slots) < (i + 1):
            # if there is no slot then we append to create the slot and assign
            obj.data.materials.append(materials[i])
        else:
            # we always want the material in slot[0]
            obj.material_slots[0].material = materials[i]
        # obj.active_material = obj.material_slots[i].material

    for i in range(0, len(materialIDs)):
        obj.data.polygons[i].material_index = materialIDs[i]

    # obj.data.materials.append(material)
    layer.objects.link(obj)

    # Generate a Material
    # img_name = "Test.jpg"  # dummy texture
    # mat_count = len(texmaps)

    # if mat_count == 0 and len(materialIDs) > 0:
    #    for i in range(0, len(materialIDs)):
    #        if (materialIDs[i] + 1) > mat_count: mat_count = materialIDs[i] + 1

    # Assign Material ID's
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    bpy.context.tool_settings.mesh_select_mode = [False, False, True]

    bpy.ops.object.mode_set(mode='OBJECT')
    # materialIDs

    # Redraw Entire Scene
    # bpy.context.scene.update()

    return obj


# END OF MAXSCRIPT FUNCTIONS #########################################################


#
# ====================================================================================
# BLENDER API FUNCTIONS
# ====================================================================================
# These are functions or wrappers specific with dealing with blender's API
# ====================================================================================
#


def deleteScene(include=[]):
    if len(include) > 0:
        # Exit and Interactions
        if bpy.context.view_layer.objects.active != None:
            bpy.ops.object.mode_set(mode='OBJECT')

        # Select All
        bpy.ops.object.select_all(action='SELECT')

        # Loop Through Each Selection
        for o in bpy.context.view_layer.objects.selected:
            for t in include:
                if o.type == t:
                    bpy.data.objects.remove(o, do_unlink=True)
                    break

        # De-Select All
        bpy.ops.object.select_all(action='DESELECT')
    return None



# Callback when file(s) are selected
def abt_xgm_imp_callback(fpath="", files=[], clearScene=True, mscale = 1.0):
    if len(files) > 0 and clearScene: deleteScene(['MESH', 'ARMATURE'])
    for file in files:
        read(fpath + file.name, mscale)
    if len(files) > 0:
        messageBox("Done!")
        return True
    else:
        return False


# Wrapper that Invokes FileSelector to open files from blender
def abt_xgm_imp(reload=False):
    # Un-Register Operator
    if reload and hasattr(bpy.types, "IMPORTHELPER_OT_abt_xgm_imp"):  # print(bpy.ops.importhelper.abt_xgm_imp.idname())

        try:
            bpy.types.TOPBAR_MT_file_import.remove(
                bpy.types.Operator.bl_rna_get_subclass_py('IMPORTHELPER_OT_abt_xgm_imp').menu_func_import)
        except:
            print("Failed to Unregister2")

        try:
            bpy.utils.unregister_class(bpy.types.Operator.bl_rna_get_subclass_py('IMPORTHELPER_OT_abt_xgm_imp'))
        except:
            print("Failed to Unregister1")

    # Define Operator
    class ImportHelper_abt_xgm_imp(bpy.types.Operator):

        # Operator Path
        bl_idname = "importhelper.abt_xgm_imp"
        bl_label = "Select File"

        # Operator Properties
        # filter_glob: bpy.props.StringProperty(default='*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp', options={'HIDDEN'})
        filter_glob: bpy.props.StringProperty(default='*.xgm', options={'HIDDEN'}, subtype='FILE_PATH')

        # Variables
        filepath: bpy.props.StringProperty(subtype="FILE_PATH")  # full path of selected item (path+filename)
        filename: bpy.props.StringProperty(subtype="FILE_NAME")  # name of selected item
        directory: bpy.props.StringProperty(subtype="FILE_PATH")  # directory of the selected item
        files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)  # a collection containing all the selected items as filenames

        # Controls
        my_float1: bpy.props.FloatProperty(name="Scale", default=1.0, description="Changes Scale of the imported Mesh")
        my_bool1: bpy.props.BoolProperty(name="Clear Scene", default=True, description="Deletes everything in the scene prior to importing")

        # Runs when this class OPENS
        def invoke(self, context, event):

            # Retrieve Settings
            try: self.filepath = bpy.types.Scene.abt_xgm_imp_filepath
            except: bpy.types.Scene.abt_xgm_imp_filepath = bpy.props.StringProperty(subtype="FILE_PATH")

            try: self.directory = bpy.types.Scene.abt_xgm_imp_directory
            except: bpy.types.Scene.abt_xgm_imp_directory = bpy.props.StringProperty(subtype="FILE_PATH")

            try: self.my_float1 = bpy.types.Scene.abt_xgm_imp_my_float1
            except: bpy.types.Scene.abt_xgm_imp_my_float1 = bpy.props.FloatProperty(default=1.0)

            try: self.my_bool1 = bpy.types.Scene.abt_xgm_imp_my_bool1
            except: bpy.types.Scene.abt_xgm_imp_my_bool1 = bpy.props.BoolProperty(default=False)


            # Open File Browser
            # Set Properties of the File Browser
            context.window_manager.fileselect_add(self)
            context.area.tag_redraw()

            return {'RUNNING_MODAL'}

        # Runs when this Window is CANCELLED
        def cancel(self, context): print("run *SPAM*")

        # Runs when the class EXITS
        def execute(self, context):

            # Save Settings
            bpy.types.Scene.abt_xgm_imp_filepath = self.filepath
            bpy.types.Scene.abt_xgm_imp_directory = self.directory
            bpy.types.Scene.abt_xgm_imp_my_float1 = self.my_float1
            bpy.types.Scene.abt_xgm_imp_my_bool1 = self.my_bool1

            # Run Callback
            abt_xgm_imp_callback(self.directory + "\\", self.files, self.my_bool1, self.my_float1)

            return {"FINISHED"}

            # Window Settings

        def draw(self, context):

            self.layout.row().label(text="Import Settings")

            self.layout.separator()
            self.layout.row().prop(self, "my_bool1")
            self.layout.row().prop(self, "my_float1")

            self.layout.separator()

            col = self.layout.row()
            col.alignment = 'RIGHT'
            col.label(text="  Author:", icon='QUESTION')
            col.alignment = 'LEFT'
            col.label(text="mariokart64n")

            col = self.layout.row()
            col.alignment = 'RIGHT'
            col.label(text="Release:", icon='GRIP')
            col.alignment = 'LEFT'
            col.label(text="March 07, 2021")

        def menu_func_import(self, context):
            self.layout.operator("importhelper.abt_xgm_imp", text="Angrybirds Transformers (*.xgm)")

    # Register Operator
    bpy.utils.register_class(ImportHelper_abt_xgm_imp)
    bpy.types.TOPBAR_MT_file_import.append(ImportHelper_abt_xgm_imp.menu_func_import)

    # Call ImportHelper
    bpy.ops.importhelper.abt_xgm_imp('INVOKE_DEFAULT')


# END OF BLENDER FUNCTIONS ###########################################################


#
# ====================================================================================
# MAIN CODE
# ====================================================================================
# And the actual program... honesty I should have split the code into modules <_<
# ====================================================================================
#


def read(file="", mscale = 1.0):
    f = fopen(file, "rb")
    if f.isGood:

        fsize = getFileSize(file)
        type = 0
        info = 0
        blen = 0
        pos = 0
        mshName = "Mesh"
        version = 0
        vertBufAddr = 0
        faceBufAddr = 0
        faceBufAddr2 = 0
        vertBufSize = 0
        faceBufSize = 0
        vertBufStride = 24
        faceBufStride = 2
        vertCount = 0
        faceCount = 0
        vertArray = []
        tvertArray = []
        faceArray = []
        msh = None
        face = [1, 1, 1]
        maxVert = 0
        addrs = []

        while ftell(f) < fsize:
            pos = ftell(f)
            type = readShort(f, unsigned)
            info = readShort(f, unsigned)
            blen = readLong(f, unsigned)
            if type == 0x15:
                fseek(f, 4, seek_cur)
                version = readByte(f, unsigned)

            elif type == 0x2D:
                fseek(f, 2, seek_cur)
                mshName = readString(f)
                print("mshName:\t%s" % mshName)

            elif type == 0x11:
                if version == 0x24:
                    fseek(f, 0x14, seek_cur)
                    addrs = []
                    for i in range(0, 10):
                        addrs.append(readLong(f, unsigned))
                    addrs.append(blen)
                    vertBufAddr = addrs[0]
                    normBuffAddr = addrs[1]
                    faceBufAddr = addrs[2]
                    tvertBufAddr = addrs[4]

                    print("vertBufAddr:\t%i" % vertBufAddr)
                    print("faceBufAddr:\t%i" % faceBufAddr)

                    addrs.sort()

                    vertCount = 0
                    for i in range(10, -1, -1):
                        if addrs[i] == vertBufAddr:
                            vertCount = int((addrs[i + 1] - vertBufAddr) / 12)
                            print("vertCount:\t%i" % vertCount)
                            break

                    if vertCount > 0:
                        fseek(f, pos + vertBufAddr, seek_set)
                        vertArray = [[float] * 3] * vertCount
                        tvertArray = [[float] * 3] * vertCount
                        for i in range(0, vertCount):
                            vertArray[i] = [readFloat(f), readFloat(f), readFloat(f)]

                        fseek(f, pos + tvertBufAddr, seek_set)
                        for i in range(0, vertCount):
                            tvertArray[i] = [readFloat(f), readFloat(f), 0.0]

                    faceCount = 0
                    for i in range(len(addrs) - 1, -1, -1):
                        if addrs[i] == faceBufAddr:
                            faceCount = int((addrs[i + 1] - faceBufAddr) / 2 / 3)
                            print("faceCount:\t%i" % faceCount)
                            break

                    if faceCount > 0:
                        maxVert = 0
                        fseek(f, pos + faceBufAddr, seek_set)
                        for i in range(0, faceCount):
                            face = [readShort(f, unsigned), readShort(f, unsigned), readShort(f, unsigned)]
                            if face[0] + 1 > maxVert: maxVert = face[0] + 1
                            if face[1] + 1 > maxVert: maxVert = face[1] + 1
                            if face[2] + 1 > maxVert: maxVert = face[2] + 1

                            if maxVert <= len(vertArray):
                                append(faceArray, face)

                            else:
                                print("index over")
                                break

                        print("maxVert:\t%i" % maxVert)

                    print(ftell(f))
                    if len(vertArray) > 0:
                        msh = mesh(vertices=vertArray, faces=faceArray, tverts=[tvertArray], mscale=mscale)

                    break


            elif type == 0x31:
                fseek(f, 0x78, seek_cur)
                vertBufAddr = readLong(f, unsigned)
                fseek(f, 0x04, seek_cur)
                faceBufAddr = readLong(f, unsigned)
                fseek(f, 0x34, seek_cur)
                vertBufSize = readLong(f, unsigned)
                faceBufSize = readLong(f, unsigned)

                vertCount = int(vertBufSize / vertBufStride)
                print("vertCount:\t%i" % vertCount)
                if vertCount > 0:
                    vertArray = [[float] * 3] * vertCount
                    tvertArray = [[float] * 3] * vertCount
                    fseek(f, pos + vertBufAddr, seek_set)
                    for i in range(0, vertCount):
                        vertArray[i] = [readFloat(f), readFloat(f), readFloat(f)]
                        fseek(f, 4, seek_cur)  # Normal
                        fseek(f, 4, seek_cur)  # Vertex Colour
                        tvertArray[i] = [readShort(f, signed) / 32767.0 + 0.5, readShort(f, signed) / 32767.0 + 0.5, 0.0]
                        tvertArray[i] = [1.0 - vertArray[i][1], vertArray[i][2], 0.0]

                faceCount = int(faceBufSize / faceBufStride / 3)
                if faceCount > 0:
                    maxVert = 0
                    fseek(f, pos + faceBufAddr, seek_set)

                    for i in range(0, faceCount):
                        face = [readShort(f, unsigned), readShort(f, unsigned), readShort(f, unsigned)]
                        if face[0] + 1 > maxVert: maxVert = face[0] + 1
                        if face[1] + 1 > maxVert: maxVert = face[1] + 1
                        if face[2] + 1 > maxVert: maxVert = face[2] + 1

                        if maxVert <= len(vertArray):
                            append(faceArray, [face[0], face[2], face[1]])

                        else:
                            print("index over")
                            break

                if len(vertArray) > 0 and len(faceArray) > 0:
                    msh = mesh(vertices=vertArray, faces=faceArray, tverts=[tvertArray], mscale=mscale)

                break

            else:
                print("Unsupported Chunk [%i] @ %i\n" % (type, pos))

            fseek(f, pos + blen, seek_set)

        fclose(f)

    else:
        print("Failed to Open File")
    return None



clearListener()  # clears out console
if not useOpenDialog:

    deleteScene(['MESH', 'ARMATURE'])  # Clear Scene
    read(
        "C:\\Users\\Corey\\Desktop\\grimlock\\models\\trophy_africancup.xgm"
        )
    messageBox("Done!")
else:
    abt_xgm_imp(True)
# bpy.context.scene.unit_settings.system = 'METRIC'

# bpy.context.scene.unit_settings.scale_length = 1.001
JCBurgos01
Posts: 7
Joined: Sat May 09, 2020 11:33 pm

Re: Exient XGS Engine *.XGM (MODELS)

Post by JCBurgos01 »

mariokart64n wrote:blender python script, goto the script tab press new and paste the script. press the play button (Alt + P) and you can open a xgm and import it into blender. From that point the script is registered and you can access the import function through the File > Import context until blender is restarted.

Code: Select all

""" ======================================================================

    PythonScript:   [Mobile] Angry Birds Transformers
    Author:         mariokart64n
    Date:           March 07, 2021
    Version:        0.1

    ======================================================================

    ChangeLog:

    2021-03-37
        Script Wrote
   

    ====================================================================== """

import bpy  # Needed to interface with blender
import struct  # Needed for Binary Reader
import math
from pathlib import Path  # Needed for os stuff

useOpenDialog = True

#
# ====================================================================================
# MAXCSRIPT FUNCTIONS
# ====================================================================================
# These function are written to mimic native functions in
# maxscript. This is to make porting my old maxscripts
# easier, so alot of these functions may be redundant..
# ====================================================================================
#

signed, unsigned = 0, 1  # Enums for read function
seek_set, seek_cur, seek_end = 0, 1, 2  # Enums for seek function

def messageBox(message="", title="Message Box", icon='INFO'):
    def draw(self, context): self.layout.label(text=message)

    bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
    return None

def getFileSize(filename):
    return Path(filename).stat().st_size

def clearListener(len=64):
    for i in range(0, len): print('')

def append(array, value):
    array.append(value)
    return None

class fopen:
    little_endian = True
    file = ""
    mode = 'rb'
    data = bytearray()
    size = 0
    pos = 0
    isGood = False

    def __init__(self, filename=None, mode='rb', isLittleEndian=True):
        if mode == 'rb':
            if filename != None and Path(filename).is_file():
                self.data = open(filename, mode).read()
                self.size = len(self.data)
                self.pos = 0
                self.mode = mode
                self.file = filename
                self.little_endian = isLittleEndian
                self.isGood = True
        else:
            self.file = filename
            self.mode = mode
            self.data = bytearray()
            self.pos = 0
            self.size = 0
            self.little_endian = isLittleEndian
            self.isGood = False

        return None

    # def __del__(self):
    #    self.flush()

    def resize(self, dataSize=0):
        if dataSize > 0:
            self.data = bytearray(dataSize)
        else:
            self.data = bytearray()
        self.pos = 0
        self.size = dataSize
        self.isGood = False
        return None

    def flush(self):
        print("flush")
        print("file:\t%s" % self.file)
        print("isGood:\t%s" % self.isGood)
        print("size:\t%s" % len(self.data))
        if self.file != "" and not self.isGood and len(self.data) > 0:
            self.isGood = True

            s = open(self.file, 'w+b')
            s.write(self.data)
            s.close()

    def read_and_unpack(self, unpack, size):
        '''
          Charactor, Byte-order
          @,         native, native
          =,         native, standard
          <,         little endian
          >,         big endian
          !,         network

          Format, C-type,         Python-type, Size[byte]
          c,      char,           byte,        1
          b,      signed char,    integer,     1
          B,      unsigned char,  integer,     1
          h,      short,          integer,     2
          H,      unsigned short, integer,     2
          i,      int,            integer,     4
          I,      unsigned int,   integer,     4
          f,      float,          float,       4
          d,      double,         float,       8
        '''
        value = 0
        if self.size > 0 and self.pos + size < self.size:
            value = struct.unpack_from(unpack, self.data, self.pos)[0]
            self.pos += size
        return value

    def pack_and_write(self, pack, size, value):
        if self.pos + size > self.size:
            self.data.extend(b'\x00' * ((self.pos + size) - self.size))
            self.size = self.pos + size
        try:
            struct.pack_into(pack, self.data, self.pos, value)
        except:
            print('Pos:\t%i / %i (buf:%i) [val:%i:%i:%s]' % (self.pos, self.size, len(self.data), value, size, pack))
            pass
        self.pos += size
        return None

    def set_pointer(self, offset):
        self.pos = offset
        return None


def fclose(bitStream):
    bitStream.flush()
    bitStream.isGood = False


def fseek(bitStream, offset, dir):
    if dir == 0:
        bitStream.set_pointer(offset)
    elif dir == 1:
        bitStream.set_pointer(bitStream.pos + offset)
    elif dir == 2:
        bitStream.set_pointer(bitStream.pos - offset)
    return None


def ftell(bitStream):
    return bitStream.pos


def readByte(bitStream, isSigned=0):
    fmt = 'b' if isSigned == 0 else 'B'
    return (bitStream.read_and_unpack(fmt, 1))


def readShort(bitStream, isSigned=0):
    fmt = '>' if not bitStream.little_endian else '<'
    fmt += 'h' if isSigned == 0 else 'H'
    return (bitStream.read_and_unpack(fmt, 2))


def readLong(bitStream, isSigned=0):
    fmt = '>' if not bitStream.little_endian else '<'
    fmt += 'i' if isSigned == 0 else 'I'
    return (bitStream.read_and_unpack(fmt, 4))


def readFloat(bitStream):
    fmt = '>f' if not bitStream.little_endian else '<f'
    return (bitStream.read_and_unpack(fmt, 4))


def readString(bitStream, length=0):
    string = ''
    pos = bitStream.pos
    lim = length if length != 0 else bitStream.size - bitStream.pos
    for i in range(0, lim):
        b = bitStream.read_and_unpack('B', 1)
        if b != 0:
            string += chr(b)
        else:
            if length > 0:
                bitStream.set_pointer(pos + length)
            break
    return string


def mesh(vertices=[], faces=[], materialIDs=[], tverts=[], normals=[], colours=[], materials=[], mscale=1.0, flipAxis=False, obj_name="Object", lay_name=''):
    #
    # This function is pretty, ugly
    # imports the mesh into blender
    #

    # Clear Any Object Selections
    # for o in bpy.context.selected_objects: o.select = False
    bpy.context.view_layer.objects.active = None

    # Get Collection (Layers)
    if lay_name != '':
        # make collection
        layer = bpy.data.collections.new(lay_name)
        bpy.context.scene.collection.children.link(layer)
    else:
        layer = bpy.data.collections[bpy.context.view_layer.active_layer_collection.name]

    # make mesh
    msh = bpy.data.meshes.new('Mesh')

    # msh.name = msh.name.replace(".", "_")

    # Apply vertex scaling
    # mscale *= bpy.context.scene.unit_settings.scale_length
    if len(vertices) > 0:
        vertArray = [[float] * 3] * len(vertices)
        if flipAxis:
            for v in range(0, len(vertices)):
                vertArray[v] = (
                    vertices[v][0] * mscale,
                    -vertices[v][2] * mscale,
                    vertices[v][1] * mscale
                )
        else:
            for v in range(0, len(vertices)):
                vertArray[v] = (
                    vertices[v][0] * mscale,
                    vertices[v][1] * mscale,
                    vertices[v][2] * mscale
                )

    # assign data from arrays
    msh.from_pydata(vertArray, [], faces)

    # set surface to smooth
    msh.polygons.foreach_set("use_smooth", [True] * len(msh.polygons))

    # Set Normals
    if len(faces) > 0:
        if len(normals) > 0:
            msh.use_auto_smooth = True
            if len(normals) == (len(faces) * 3):
                msh.normals_split_custom_set(normals)
            else:
                normArray = [[float] * 3] * (len(faces) * 3)
                if flipAxis:
                    for i in range(0, len(faces)):
                        for v in range(0, 3):
                            normArray[(i * 3) + v] = (
                                [normals[faces[i][v]][0],
                                 -normals[faces[i][v]][2],
                                 normals[faces[i][v]][1]]
                            )
                else:
                    for i in range(0, len(faces)):
                        for v in range(0, 3):
                            normArray[(i * 3) + v] = (
                                [normals[faces[i][v]][0],
                                 normals[faces[i][v]][1],
                                 normals[faces[i][v]][2]]
                            )
                msh.normals_split_custom_set(normArray)

        # create texture corrdinates
        print("tverts ", len(tverts))
        # this is just a hack, i just add all the UVs into the same space <<<
        if len(tverts) > 0:
            uvw = msh.uv_layers.new()
            # if len(tverts) == (len(faces) * 3):
            #    for v in range(0, len(faces) * 3):
            #        msh.uv_layers[uvw.name].data[v].uv = tverts[v]
            # else:
            uvwArray = [[float] * 2] * len(tverts[0])
            for i in range(0, len(tverts[0])):
                uvwArray[i] = [0.0, 0.0]

            for v in range(0, len(tverts[0])):
                for i in range(0, len(tverts)):
                    uvwArray[v][0] += tverts[i][v][0]
                    uvwArray[v][1] += 1.0 - tverts[i][v][1]

            for i in range(0, len(faces)):
                for v in range(0, 3):
                    msh.uv_layers[uvw.name].data[(i * 3) + v].uv = (
                        uvwArray[faces[i][v]][0],
                        uvwArray[faces[i][v]][1]
                    )

        # create vertex colours
        if len(colours) > 0:
            col = msh.vertex_colors.new()
            if len(colours) == (len(faces) * 3):
                for v in range(0, len(faces) * 3):
                    msh.vertex_colors[col.name].data[v].color = colours[v]
            else:
                colArray = [[float] * 4] * (len(faces) * 3)
                for i in range(0, len(faces)):
                    for v in range(0, 3):
                        msh.vertex_colors[col.name].data[(i * 3) + v].color = colours[faces[i][v]]


    # Create Face Maps?
    # msh.face_maps.new()

    # Update Mesh
    msh.update()

    # Check mesh is Valid
    if msh.validate():
        # Erase Mesh
        print("Mesh Invalid!")
        msh.user_clear()
        bpy.data.meshes.remove(msh)
        return None

    # Assign Mesh to Object
    obj = bpy.data.objects.new(obj_name, msh)
    # obj.name = obj.name.replace(".", "_")

    for i in range(0, len(materials)):

        if len(obj.material_slots) < (i + 1):
            # if there is no slot then we append to create the slot and assign
            obj.data.materials.append(materials[i])
        else:
            # we always want the material in slot[0]
            obj.material_slots[0].material = materials[i]
        # obj.active_material = obj.material_slots[i].material

    for i in range(0, len(materialIDs)):
        obj.data.polygons[i].material_index = materialIDs[i]

    # obj.data.materials.append(material)
    layer.objects.link(obj)

    # Generate a Material
    # img_name = "Test.jpg"  # dummy texture
    # mat_count = len(texmaps)

    # if mat_count == 0 and len(materialIDs) > 0:
    #    for i in range(0, len(materialIDs)):
    #        if (materialIDs[i] + 1) > mat_count: mat_count = materialIDs[i] + 1

    # Assign Material ID's
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    bpy.context.tool_settings.mesh_select_mode = [False, False, True]

    bpy.ops.object.mode_set(mode='OBJECT')
    # materialIDs

    # Redraw Entire Scene
    # bpy.context.scene.update()

    return obj


# END OF MAXSCRIPT FUNCTIONS #########################################################


#
# ====================================================================================
# BLENDER API FUNCTIONS
# ====================================================================================
# These are functions or wrappers specific with dealing with blender's API
# ====================================================================================
#


def deleteScene(include=[]):
    if len(include) > 0:
        # Exit and Interactions
        if bpy.context.view_layer.objects.active != None:
            bpy.ops.object.mode_set(mode='OBJECT')

        # Select All
        bpy.ops.object.select_all(action='SELECT')

        # Loop Through Each Selection
        for o in bpy.context.view_layer.objects.selected:
            for t in include:
                if o.type == t:
                    bpy.data.objects.remove(o, do_unlink=True)
                    break

        # De-Select All
        bpy.ops.object.select_all(action='DESELECT')
    return None



# Callback when file(s) are selected
def abt_xgm_imp_callback(fpath="", files=[], clearScene=True, mscale = 1.0):
    if len(files) > 0 and clearScene: deleteScene(['MESH', 'ARMATURE'])
    for file in files:
        read(fpath + file.name, mscale)
    if len(files) > 0:
        messageBox("Done!")
        return True
    else:
        return False


# Wrapper that Invokes FileSelector to open files from blender
def abt_xgm_imp(reload=False):
    # Un-Register Operator
    if reload and hasattr(bpy.types, "IMPORTHELPER_OT_abt_xgm_imp"):  # print(bpy.ops.importhelper.abt_xgm_imp.idname())

        try:
            bpy.types.TOPBAR_MT_file_import.remove(
                bpy.types.Operator.bl_rna_get_subclass_py('IMPORTHELPER_OT_abt_xgm_imp').menu_func_import)
        except:
            print("Failed to Unregister2")

        try:
            bpy.utils.unregister_class(bpy.types.Operator.bl_rna_get_subclass_py('IMPORTHELPER_OT_abt_xgm_imp'))
        except:
            print("Failed to Unregister1")

    # Define Operator
    class ImportHelper_abt_xgm_imp(bpy.types.Operator):

        # Operator Path
        bl_idname = "importhelper.abt_xgm_imp"
        bl_label = "Select File"

        # Operator Properties
        # filter_glob: bpy.props.StringProperty(default='*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp', options={'HIDDEN'})
        filter_glob: bpy.props.StringProperty(default='*.xgm', options={'HIDDEN'}, subtype='FILE_PATH')

        # Variables
        filepath: bpy.props.StringProperty(subtype="FILE_PATH")  # full path of selected item (path+filename)
        filename: bpy.props.StringProperty(subtype="FILE_NAME")  # name of selected item
        directory: bpy.props.StringProperty(subtype="FILE_PATH")  # directory of the selected item
        files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement)  # a collection containing all the selected items as filenames

        # Controls
        my_float1: bpy.props.FloatProperty(name="Scale", default=1.0, description="Changes Scale of the imported Mesh")
        my_bool1: bpy.props.BoolProperty(name="Clear Scene", default=True, description="Deletes everything in the scene prior to importing")

        # Runs when this class OPENS
        def invoke(self, context, event):

            # Retrieve Settings
            try: self.filepath = bpy.types.Scene.abt_xgm_imp_filepath
            except: bpy.types.Scene.abt_xgm_imp_filepath = bpy.props.StringProperty(subtype="FILE_PATH")

            try: self.directory = bpy.types.Scene.abt_xgm_imp_directory
            except: bpy.types.Scene.abt_xgm_imp_directory = bpy.props.StringProperty(subtype="FILE_PATH")

            try: self.my_float1 = bpy.types.Scene.abt_xgm_imp_my_float1
            except: bpy.types.Scene.abt_xgm_imp_my_float1 = bpy.props.FloatProperty(default=1.0)

            try: self.my_bool1 = bpy.types.Scene.abt_xgm_imp_my_bool1
            except: bpy.types.Scene.abt_xgm_imp_my_bool1 = bpy.props.BoolProperty(default=False)


            # Open File Browser
            # Set Properties of the File Browser
            context.window_manager.fileselect_add(self)
            context.area.tag_redraw()

            return {'RUNNING_MODAL'}

        # Runs when this Window is CANCELLED
        def cancel(self, context): print("run *SPAM*")

        # Runs when the class EXITS
        def execute(self, context):

            # Save Settings
            bpy.types.Scene.abt_xgm_imp_filepath = self.filepath
            bpy.types.Scene.abt_xgm_imp_directory = self.directory
            bpy.types.Scene.abt_xgm_imp_my_float1 = self.my_float1
            bpy.types.Scene.abt_xgm_imp_my_bool1 = self.my_bool1

            # Run Callback
            abt_xgm_imp_callback(self.directory + "\\", self.files, self.my_bool1, self.my_float1)

            return {"FINISHED"}

            # Window Settings

        def draw(self, context):

            self.layout.row().label(text="Import Settings")

            self.layout.separator()
            self.layout.row().prop(self, "my_bool1")
            self.layout.row().prop(self, "my_float1")

            self.layout.separator()

            col = self.layout.row()
            col.alignment = 'RIGHT'
            col.label(text="  Author:", icon='QUESTION')
            col.alignment = 'LEFT'
            col.label(text="mariokart64n")

            col = self.layout.row()
            col.alignment = 'RIGHT'
            col.label(text="Release:", icon='GRIP')
            col.alignment = 'LEFT'
            col.label(text="March 07, 2021")

        def menu_func_import(self, context):
            self.layout.operator("importhelper.abt_xgm_imp", text="Angrybirds Transformers (*.xgm)")

    # Register Operator
    bpy.utils.register_class(ImportHelper_abt_xgm_imp)
    bpy.types.TOPBAR_MT_file_import.append(ImportHelper_abt_xgm_imp.menu_func_import)

    # Call ImportHelper
    bpy.ops.importhelper.abt_xgm_imp('INVOKE_DEFAULT')


# END OF BLENDER FUNCTIONS ###########################################################


#
# ====================================================================================
# MAIN CODE
# ====================================================================================
# And the actual program... honesty I should have split the code into modules <_<
# ====================================================================================
#


def read(file="", mscale = 1.0):
    f = fopen(file, "rb")
    if f.isGood:

        fsize = getFileSize(file)
        type = 0
        info = 0
        blen = 0
        pos = 0
        mshName = "Mesh"
        version = 0
        vertBufAddr = 0
        faceBufAddr = 0
        faceBufAddr2 = 0
        vertBufSize = 0
        faceBufSize = 0
        vertBufStride = 24
        faceBufStride = 2
        vertCount = 0
        faceCount = 0
        vertArray = []
        tvertArray = []
        faceArray = []
        msh = None
        face = [1, 1, 1]
        maxVert = 0
        addrs = []

        while ftell(f) < fsize:
            pos = ftell(f)
            type = readShort(f, unsigned)
            info = readShort(f, unsigned)
            blen = readLong(f, unsigned)
            if type == 0x15:
                fseek(f, 4, seek_cur)
                version = readByte(f, unsigned)

            elif type == 0x2D:
                fseek(f, 2, seek_cur)
                mshName = readString(f)
                print("mshName:\t%s" % mshName)

            elif type == 0x11:
                if version == 0x24:
                    fseek(f, 0x14, seek_cur)
                    addrs = []
                    for i in range(0, 10):
                        addrs.append(readLong(f, unsigned))
                    addrs.append(blen)
                    vertBufAddr = addrs[0]
                    normBuffAddr = addrs[1]
                    faceBufAddr = addrs[2]
                    tvertBufAddr = addrs[4]

                    print("vertBufAddr:\t%i" % vertBufAddr)
                    print("faceBufAddr:\t%i" % faceBufAddr)

                    addrs.sort()

                    vertCount = 0
                    for i in range(10, -1, -1):
                        if addrs[i] == vertBufAddr:
                            vertCount = int((addrs[i + 1] - vertBufAddr) / 12)
                            print("vertCount:\t%i" % vertCount)
                            break

                    if vertCount > 0:
                        fseek(f, pos + vertBufAddr, seek_set)
                        vertArray = [[float] * 3] * vertCount
                        tvertArray = [[float] * 3] * vertCount
                        for i in range(0, vertCount):
                            vertArray[i] = [readFloat(f), readFloat(f), readFloat(f)]

                        fseek(f, pos + tvertBufAddr, seek_set)
                        for i in range(0, vertCount):
                            tvertArray[i] = [readFloat(f), readFloat(f), 0.0]

                    faceCount = 0
                    for i in range(len(addrs) - 1, -1, -1):
                        if addrs[i] == faceBufAddr:
                            faceCount = int((addrs[i + 1] - faceBufAddr) / 2 / 3)
                            print("faceCount:\t%i" % faceCount)
                            break

                    if faceCount > 0:
                        maxVert = 0
                        fseek(f, pos + faceBufAddr, seek_set)
                        for i in range(0, faceCount):
                            face = [readShort(f, unsigned), readShort(f, unsigned), readShort(f, unsigned)]
                            if face[0] + 1 > maxVert: maxVert = face[0] + 1
                            if face[1] + 1 > maxVert: maxVert = face[1] + 1
                            if face[2] + 1 > maxVert: maxVert = face[2] + 1

                            if maxVert <= len(vertArray):
                                append(faceArray, face)

                            else:
                                print("index over")
                                break

                        print("maxVert:\t%i" % maxVert)

                    print(ftell(f))
                    if len(vertArray) > 0:
                        msh = mesh(vertices=vertArray, faces=faceArray, tverts=[tvertArray], mscale=mscale)

                    break


            elif type == 0x31:
                fseek(f, 0x78, seek_cur)
                vertBufAddr = readLong(f, unsigned)
                fseek(f, 0x04, seek_cur)
                faceBufAddr = readLong(f, unsigned)
                fseek(f, 0x34, seek_cur)
                vertBufSize = readLong(f, unsigned)
                faceBufSize = readLong(f, unsigned)

                vertCount = int(vertBufSize / vertBufStride)
                print("vertCount:\t%i" % vertCount)
                if vertCount > 0:
                    vertArray = [[float] * 3] * vertCount
                    tvertArray = [[float] * 3] * vertCount
                    fseek(f, pos + vertBufAddr, seek_set)
                    for i in range(0, vertCount):
                        vertArray[i] = [readFloat(f), readFloat(f), readFloat(f)]
                        fseek(f, 4, seek_cur)  # Normal
                        fseek(f, 4, seek_cur)  # Vertex Colour
                        tvertArray[i] = [readShort(f, signed) / 32767.0 + 0.5, readShort(f, signed) / 32767.0 + 0.5, 0.0]
                        tvertArray[i] = [1.0 - vertArray[i][1], vertArray[i][2], 0.0]

                faceCount = int(faceBufSize / faceBufStride / 3)
                if faceCount > 0:
                    maxVert = 0
                    fseek(f, pos + faceBufAddr, seek_set)

                    for i in range(0, faceCount):
                        face = [readShort(f, unsigned), readShort(f, unsigned), readShort(f, unsigned)]
                        if face[0] + 1 > maxVert: maxVert = face[0] + 1
                        if face[1] + 1 > maxVert: maxVert = face[1] + 1
                        if face[2] + 1 > maxVert: maxVert = face[2] + 1

                        if maxVert <= len(vertArray):
                            append(faceArray, [face[0], face[2], face[1]])

                        else:
                            print("index over")
                            break

                if len(vertArray) > 0 and len(faceArray) > 0:
                    msh = mesh(vertices=vertArray, faces=faceArray, tverts=[tvertArray], mscale=mscale)

                break

            else:
                print("Unsupported Chunk [%i] @ %i\n" % (type, pos))

            fseek(f, pos + blen, seek_set)

        fclose(f)

    else:
        print("Failed to Open File")
    return None



clearListener()  # clears out console
if not useOpenDialog:

    deleteScene(['MESH', 'ARMATURE'])  # Clear Scene
    read(
        "C:\\Users\\Corey\\Desktop\\grimlock\\models\\trophy_africancup.xgm"
        )
    messageBox("Done!")
else:
    abt_xgm_imp(True)
# bpy.context.scene.unit_settings.system = 'METRIC'

# bpy.context.scene.unit_settings.scale_length = 1.001

Men, thanks for this but, How to Export?
DJ Normality
Posts: 647
Joined: Tue Jul 24, 2018 8:52 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by DJ Normality »

Warhammer_81
Posts: 2
Joined: Tue Feb 01, 2022 11:36 pm

Re: Exient XGS Engine *.XGM (MODELS)

Post by Warhammer_81 »

DJ Normality wrote:Noesis XGM importer
https://drive.google.com/file/d/1-YKRJW ... sp=sharing

Hey DJ Normality,
I guess I'm a bit late to the party here but do you happen to still have the Noesis XGM Importer script available anymore? The Google Drive link you shared is no longer valid. I'm trying to convert all the Angry Birds Transformers .XGM models with little success. I already have all the textures converted I just need the script to work on the models, thanks.
DJ Normality
Posts: 647
Joined: Tue Jul 24, 2018 8:52 am

Re: Exient XGS Engine *.XGM (MODELS)

Post by DJ Normality »

Sure do
Warhammer_81
Posts: 2
Joined: Tue Feb 01, 2022 11:36 pm

Re: Exient XGS Engine *.XGM (MODELS)

Post by Warhammer_81 »

Wow, thank you so much!