Exient XGS Engine *.XGM (MODELS)
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Exient XGS Engine *.XGM (MODELS)
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
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.
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Re: Angry Birds: Transformers/Go .XGM (MODELS)
Bump again? Its not just me looking for these...
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Re: Exient XGS Engine: Angry Birds: Transformers/Go .XGM (MODELS)
They are in the "models" directory of the game data in case you can't find them.
-
- Posts: 706
- Joined: Fri Aug 08, 2014 1:06 am
Re: Exient XGS Engine *.XGM (MODELS)
maybe moving this to "3D/2D models" will gain the attention it needs?
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Re: Exient XGS Engine *.XGM (MODELS)
I already sent a report to do so but thanks anyways.
-
- Posts: 647
- Joined: Tue Jul 24, 2018 8:52 am
Re: Exient XGS Engine *.XGM (MODELS)
This is your layout.
This is your UV's
Faces start right after last UV
Faces
Render
Here is the file
https://drive.google.com/file/d/1KpWe2o ... sp=sharing
This is your UV's
Faces start right after last UV
Faces
Render
Here is the file
https://drive.google.com/file/d/1KpWe2o ... sp=sharing
-
- Posts: 647
- Joined: Tue Jul 24, 2018 8:52 am
Re: Exient XGS Engine *.XGM (MODELS)
Sorry. I would of posted sooner but don't really look in here. He's right this should be in 3D Models
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Re: Exient XGS Engine *.XGM (MODELS)
DUDE I LOVE YOU! But how would I open them? I don't have that program...
-
- Posts: 647
- Joined: Tue Jul 24, 2018 8:52 am
-
- Posts: 865
- Joined: Fri Apr 20, 2018 12:41 am
Re: Exient XGS Engine *.XGM (MODELS)
Hell yeah!
-
- Posts: 18
- Joined: Sun Oct 13, 2019 1:00 am
Re: Exient XGS Engine *.XGM (MODELS)
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
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.
-
- Posts: 7
- Joined: Sat May 09, 2020 11:33 pm
Re: Exient XGS Engine *.XGM (MODELS)
What about these files xgm?
https://www.mediafire.com/folder/lmyr80 ... es_Example
https://www.mediafire.com/folder/lmyr80 ... es_Example
-
- Posts: 7
- Joined: Sat May 09, 2020 11:33 pm
Re: Exient XGS Engine *.XGM (MODELS)
JCBurgos01 wrote:What about these files xgm?
https://www.mediafire.com/folder/lmyr80 ... es_Example
Nothing? Please
-
- Posts: 12
- Joined: Fri Aug 08, 2014 12:59 am
Re: Exient XGS Engine *.XGM (MODELS)
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
-
- Posts: 7
- Joined: Sat May 09, 2020 11:33 pm
Re: Exient XGS Engine *.XGM (MODELS)
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?
-
- Posts: 647
- Joined: Tue Jul 24, 2018 8:52 am
-
- Posts: 2
- Joined: Tue Feb 01, 2022 11:36 pm
Re: Exient XGS Engine *.XGM (MODELS)
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.
-
- Posts: 647
- Joined: Tue Jul 24, 2018 8:52 am
-
- Posts: 2
- Joined: Tue Feb 01, 2022 11:36 pm
Re: Exient XGS Engine *.XGM (MODELS)
Wow, thank you so much!