Quake 1 (idtech2) model format (MDL version 6): Python parsing library

Application

Quake 1 (idtech2)

File extension

mdl

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

This page hosts a formal specification of Quake 1 (idtech2) model format (MDL version 6) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Parse a local file and get structure in memory:

data = QuakeMdl.from_file("path/to/local/file.mdl")

Or parse structure from a bytes:

from kaitaistruct import KaitaiStream, BytesIO

raw = b"\x00\x01\x02..."
data = QuakeMdl(KaitaiStream(BytesIO(raw)))

After that, one can get various attributes from the structure by invoking getter methods like:

data.header # => get header

Python source code to parse Quake 1 (idtech2) model format (MDL version 6)

quake_mdl.py

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO


if parse_version(ks_version) < parse_version('0.7'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version))

class QuakeMdl(KaitaiStruct):
    def __init__(self, _io, _parent=None, _root=None):
        self._io = _io
        self._parent = _parent
        self._root = _root if _root else self
        self._read()

    def _read(self):
        self.header = self._root.MdlHeader(self._io, self, self._root)
        self.skins = [None] * (self.header.num_skins)
        for i in range(self.header.num_skins):
            self.skins[i] = self._root.MdlSkin(self._io, self, self._root)

        self.texture_coordinates = [None] * (self.header.num_verts)
        for i in range(self.header.num_verts):
            self.texture_coordinates[i] = self._root.MdlTexcoord(self._io, self, self._root)

        self.triangles = [None] * (self.header.num_tris)
        for i in range(self.header.num_tris):
            self.triangles[i] = self._root.MdlTriangle(self._io, self, self._root)

        self.frames = [None] * (self.header.num_frames)
        for i in range(self.header.num_frames):
            self.frames[i] = self._root.MdlFrame(self._io, self, self._root)


    class MdlVertex(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.values = [None] * (3)
            for i in range(3):
                self.values[i] = self._io.read_u1()

            self.normal_index = self._io.read_u1()


    class MdlTexcoord(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.on_seam = self._io.read_s4le()
            self.s = self._io.read_s4le()
            self.t = self._io.read_s4le()


    class MdlHeader(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.ident = self._io.ensure_fixed_contents(b"\x49\x44\x50\x4F")
            self.version_must_be_6 = self._io.ensure_fixed_contents(b"\x06\x00\x00\x00")
            self.scale = self._root.Vec3(self._io, self, self._root)
            self.origin = self._root.Vec3(self._io, self, self._root)
            self.radius = self._io.read_f4le()
            self.eye_position = self._root.Vec3(self._io, self, self._root)
            self.num_skins = self._io.read_s4le()
            self.skin_width = self._io.read_s4le()
            self.skin_height = self._io.read_s4le()
            self.num_verts = self._io.read_s4le()
            self.num_tris = self._io.read_s4le()
            self.num_frames = self._io.read_s4le()
            self.synctype = self._io.read_s4le()
            self.flags = self._io.read_s4le()
            self.size = self._io.read_f4le()

        @property
        def version(self):
            if hasattr(self, '_m_version'):
                return self._m_version if hasattr(self, '_m_version') else None

            self._m_version = 6
            return self._m_version if hasattr(self, '_m_version') else None

        @property
        def skin_size(self):
            if hasattr(self, '_m_skin_size'):
                return self._m_skin_size if hasattr(self, '_m_skin_size') else None

            self._m_skin_size = (self.skin_width * self.skin_height)
            return self._m_skin_size if hasattr(self, '_m_skin_size') else None


    class MdlSkin(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.group = self._io.read_s4le()
            if self.group == 0:
                self.single_texture_data = self._io.read_bytes(self._root.header.skin_size)

            if self.group != 0:
                self.num_frames = self._io.read_u4le()

            if self.group != 0:
                self.frame_times = [None] * (self.num_frames)
                for i in range(self.num_frames):
                    self.frame_times[i] = self._io.read_f4le()


            if self.group != 0:
                self.group_texture_data = [None] * (self.num_frames)
                for i in range(self.num_frames):
                    self.group_texture_data[i] = self._io.read_bytes(self._root.header.skin_size)




    class MdlFrame(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.type = self._io.read_s4le()
            if self.type != 0:
                self.min = self._root.MdlVertex(self._io, self, self._root)

            if self.type != 0:
                self.max = self._root.MdlVertex(self._io, self, self._root)

            if self.type != 0:
                self.time = [None] * (self.type)
                for i in range(self.type):
                    self.time[i] = self._io.read_f4le()


            self.frames = [None] * (self.num_simple_frames)
            for i in range(self.num_simple_frames):
                self.frames[i] = self._root.MdlSimpleFrame(self._io, self, self._root)


        @property
        def num_simple_frames(self):
            if hasattr(self, '_m_num_simple_frames'):
                return self._m_num_simple_frames if hasattr(self, '_m_num_simple_frames') else None

            self._m_num_simple_frames = (1 if self.type == 0 else self.type)
            return self._m_num_simple_frames if hasattr(self, '_m_num_simple_frames') else None


    class MdlSimpleFrame(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.bbox_min = self._root.MdlVertex(self._io, self, self._root)
            self.bbox_max = self._root.MdlVertex(self._io, self, self._root)
            self.name = (KaitaiStream.bytes_terminate(KaitaiStream.bytes_strip_right(self._io.read_bytes(16), 0), 0, False)).decode(u"ASCII")
            self.vertices = [None] * (self._root.header.num_verts)
            for i in range(self._root.header.num_verts):
                self.vertices[i] = self._root.MdlVertex(self._io, self, self._root)



    class MdlTriangle(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.faces_front = self._io.read_s4le()
            self.vertices = [None] * (3)
            for i in range(3):
                self.vertices[i] = self._io.read_s4le()



    class Vec3(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.x = self._io.read_f4le()
            self.y = self._io.read_f4le()
            self.z = self._io.read_f4le()