Skip to content
Snippets Groups Projects
io_export_md3.py 22.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### BEGIN GPL LICENSE BLOCK #####
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    # ##### END GPL LICENSE BLOCK #####
    
    bl_info = {
    
    Campbell Barton's avatar
    Campbell Barton committed
        "name": "Quake Model 3 (.md3)",
        "author": "Xembie",
        "version": (0, 7),
    
        "blender": (2, 53, 0),
    
    Campbell Barton's avatar
    Campbell Barton committed
        "location": "File > Export",
        "description": "Save a Quake Model 3 File)",
        "warning": "", # used for warning icon and text in addons panel
    
        "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
    
    Campbell Barton's avatar
    Campbell Barton committed
        "category": "Import-Export"}
    
    Brendon Murphy's avatar
    Brendon Murphy committed
    
    
    import bpy,struct,math,os
    
    MAX_QPATH = 64
    
    MD3_IDENT = "IDP3"
    MD3_VERSION = 15
    MD3_MAX_TAGS = 16
    MD3_MAX_SURFACES = 32
    MD3_MAX_FRAMES = 1024
    MD3_MAX_SHADERS = 256
    MD3_MAX_VERTICES = 4096
    MD3_MAX_TRIANGLES = 8192
    MD3_XYZ_SCALE = 64.0
    
    class md3Vert:
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        xyz = []
        normal = 0
        binaryFormat = "<3hH"
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.xyz = [0.0, 0.0, 0.0]
            self.normal = 0
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        # copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
        def Decode(self, latlng):
            lat = (latlng >> 8) & 0xFF;
            lng = (latlng) & 0xFF;
            lat *= math.pi / 128;
            lng *= math.pi / 128;
            x = math.cos(lat) * math.sin(lng)
            y = math.sin(lat) * math.sin(lng)
            z =                 math.cos(lng)
            retval = [ x, y, z ]
            return retval
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        # copied from PhaethonH <phaethon@linux.ucla.edu> md3.py
        def Encode(self, normal):
            x = normal[0]
            y = normal[1]
            z = normal[2]
            # normalize
            l = math.sqrt((x*x) + (y*y) + (z*z))
            if l == 0:
                return 0
            x = x/l
            y = y/l
            z = z/l
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            if (x == 0.0) & (y == 0.0) :
                if z > 0.0:
                    return 0
                else:
                    return (128 << 8)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            lng = math.acos(z) * 255 / (2 * math.pi)
            lat = math.atan2(y, x) * 255 / (2 * math.pi)
            retval = ((int(lat) & 0xFF) << 8) | (int(lng) & 0xFF)
            return retval
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            tmpData = [0] * 4
            tmpData[0] = int(self.xyz[0] * MD3_XYZ_SCALE)
            tmpData[1] = int(self.xyz[1] * MD3_XYZ_SCALE)
            tmpData[2] = int(self.xyz[2] * MD3_XYZ_SCALE)
            tmpData[3] = self.normal
            data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1], tmpData[2], tmpData[3])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        u = 0.0
        v = 0.0
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat = "<2f"
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.u = 0.0
            self.v = 0.0
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            tmpData = [0] * 2
            tmpData[0] = self.u
            tmpData[1] = 1.0 - self.v
            data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        indexes = []
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat = "<3i"
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.indexes = [ 0, 0, 0 ]
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            tmpData = [0] * 3
            tmpData[0] = self.indexes[0]
            tmpData[1] = self.indexes[2] # reverse
            tmpData[2] = self.indexes[1] # reverse
            data = struct.pack(self.binaryFormat,tmpData[0], tmpData[1], tmpData[2])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        name = ""
        index = 0
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat = "<%dsi" % MAX_QPATH
    
        def __init__(self):
            self.name = ""
            self.index = 0
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
        def Save(self, file):
            tmpData = [0] * 2
            tmpData[0] = self.name
            tmpData[1] = self.index
            data = struct.pack(self.binaryFormat, tmpData[0], tmpData[1])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        ident = ""
        name = ""
        flags = 0
        numFrames = 0
        numShaders = 0
        numVerts = 0
        numTriangles = 0
        ofsTriangles = 0
        ofsShaders = 0
        ofsUV = 0
        ofsVerts = 0
        ofsEnd = 0
        shaders = []
        triangles = []
        uv = []
        verts = []
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat = "<4s%ds10i" % MAX_QPATH  # 1 int, name, then 10 ints
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.ident = ""
            self.name = ""
            self.flags = 0
            self.numFrames = 0
            self.numShaders = 0
            self.numVerts = 0
            self.numTriangles = 0
            self.ofsTriangles = 0
            self.ofsShaders = 0
            self.ofsUV = 0
            self.ofsVerts = 0
            self.ofsEnd
            self.shaders = []
            self.triangles = []
            self.uv = []
            self.verts = []
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            sz = struct.calcsize(self.binaryFormat)
            self.ofsTriangles = sz
            for t in self.triangles:
                sz += t.GetSize()
            self.ofsShaders = sz
            for s in self.shaders:
                sz += s.GetSize()
            self.ofsUV = sz
            for u in self.uv:
                sz += u.GetSize()
            self.ofsVerts = sz
            for v in self.verts:
                sz += v.GetSize()
            self.ofsEnd = sz
            return self.ofsEnd
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            self.GetSize()
            tmpData = [0] * 12
            tmpData[0] = self.ident
            tmpData[1] = self.name
            tmpData[2] = self.flags
            tmpData[3] = self.numFrames
            tmpData[4] = self.numShaders
            tmpData[5] = self.numVerts
            tmpData[6] = self.numTriangles
            tmpData[7] = self.ofsTriangles
            tmpData[8] = self.ofsShaders
            tmpData[9] = self.ofsUV
            tmpData[10] = self.ofsVerts
            tmpData[11] = self.ofsEnd
            data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7],tmpData[8],tmpData[9],tmpData[10],tmpData[11])
            file.write(data)
    
            # write the tri data
            for t in self.triangles:
                t.Save(file)
    
            # save the shader coordinates
            for s in self.shaders:
                s.Save(file)
    
            # save the uv info
            for u in self.uv:
                u.Save(file)
    
            # save the verts
            for v in self.verts:
                v.Save(file)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        name = ""
        origin = []
        axis = []
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat="<%ds3f9f" % MAX_QPATH
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.name = ""
            self.origin = [0, 0, 0]
            self.axis = [0, 0, 0, 0, 0, 0, 0, 0, 0]
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            tmpData = [0] * 13
            tmpData[0] = self.name
            tmpData[1] = float(self.origin[0])
            tmpData[2] = float(self.origin[1])
            tmpData[3] = float(self.origin[2])
            tmpData[4] = float(self.axis[0])
            tmpData[5] = float(self.axis[1])
            tmpData[6] = float(self.axis[2])
            tmpData[7] = float(self.axis[3])
            tmpData[8] = float(self.axis[4])
            tmpData[9] = float(self.axis[5])
            tmpData[10] = float(self.axis[6])
            tmpData[11] = float(self.axis[7])
            tmpData[12] = float(self.axis[8])
            data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6], tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11], tmpData[12])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        mins = 0
        maxs = 0
        localOrigin = 0
        radius = 0.0
        name = ""
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        binaryFormat="<3f3f3ff16s"
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def __init__(self):
            self.mins = [0, 0, 0]
            self.maxs = [0, 0, 0]
            self.localOrigin = [0, 0, 0]
            self.radius = 0.0
            self.name = ""
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def GetSize(self):
            return struct.calcsize(self.binaryFormat)
    
        def Save(self, file):
            tmpData = [0] * 11
            tmpData[0] = self.mins[0]
            tmpData[1] = self.mins[1]
            tmpData[2] = self.mins[2]
            tmpData[3] = self.maxs[0]
            tmpData[4] = self.maxs[1]
            tmpData[5] = self.maxs[2]
            tmpData[6] = self.localOrigin[0]
            tmpData[7] = self.localOrigin[1]
            tmpData[8] = self.localOrigin[2]
            tmpData[9] = self.radius
            tmpData[10] = self.name
            data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10])
            file.write(data)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        # header structure
        ident = ""            # this is used to identify the file (must be IDP3)
        version = 0            # the version number of the file (Must be 15)
        name = ""
        flags = 0
        numFrames = 0
        numTags = 0
        numSurfaces = 0
        numSkins = 0
        ofsFrames = 0
        ofsTags = 0
        ofsSurfaces = 0
        ofsEnd = 0
        frames = []
        tags = []
        surfaces = []
    
        binaryFormat="<4si%ds9i" % MAX_QPATH  # little-endian (<), 17 integers (17i)
    
        def __init__(self):
            self.ident = 0
            self.version = 0
            self.name = ""
            self.flags = 0
            self.numFrames = 0
            self.numTags = 0
            self.numSurfaces = 0
            self.numSkins = 0
            self.ofsFrames = 0
            self.ofsTags = 0
            self.ofsSurfaces = 0
            self.ofsEnd = 0
            self.frames = []
            self.tags = []
            self.surfaces = []
    
        def GetSize(self):
            self.ofsFrames = struct.calcsize(self.binaryFormat)
            self.ofsTags = self.ofsFrames
            for f in self.frames:
                self.ofsTags += f.GetSize()
            self.ofsSurfaces += self.ofsTags
            for t in self.tags:
                self.ofsSurfaces += t.GetSize()
            self.ofsEnd = self.ofsSurfaces
            for s in self.surfaces:
                self.ofsEnd += s.GetSize()
            return self.ofsEnd
    
    Luca Bonavita's avatar
    Luca Bonavita committed
        def Save(self, file):
            self.GetSize()
            tmpData = [0] * 12
            tmpData[0] = self.ident
            tmpData[1] = self.version
            tmpData[2] = self.name
            tmpData[3] = self.flags
            tmpData[4] = self.numFrames
            tmpData[5] = self.numTags
            tmpData[6] = self.numSurfaces
            tmpData[7] = self.numSkins
            tmpData[8] = self.ofsFrames
            tmpData[9] = self.ofsTags
            tmpData[10] = self.ofsSurfaces
            tmpData[11] = self.ofsEnd
    
            data = struct.pack(self.binaryFormat, tmpData[0],tmpData[1],tmpData[2],tmpData[3],tmpData[4],tmpData[5],tmpData[6],tmpData[7], tmpData[8], tmpData[9], tmpData[10], tmpData[11])
            file.write(data)
    
            for f in self.frames:
                f.Save(file)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            for t in self.tags:
                t.Save(file)
    
    Luca Bonavita's avatar
    Luca Bonavita committed
            for s in self.surfaces:
                s.Save(file)
    
    
    
    def message(log,msg):
      if log:
        log.write(msg + "\n")
      else:
        print(msg)
    
    class md3Settings:
      def __init__(self,
                   savepath,
                   name,
                   logpath,
                   overwrite=True,
                   dumpall=False,
                   ignoreuvs=False,
                   scale=1.0,
                   offsetx=0.0,
                   offsety=0.0,
                   offsetz=0.0):
        self.savepath = savepath
        self.name = name
        self.logpath = logpath
        self.overwrite = overwrite
        self.dumpall = dumpall
        self.ignoreuvs = ignoreuvs
        self.scale = scale
        self.offsetx = offsetx
        self.offsety = offsety
        self.offsetz = offsetz
    
    def print_md3(log,md3,dumpall):
      message(log,"Header Information")
      message(log,"Ident: " + str(md3.ident))
      message(log,"Version: " + str(md3.version))
      message(log,"Name: " + md3.name)
      message(log,"Flags: " + str(md3.flags))
      message(log,"Number of Frames: " + str(md3.numFrames))
      message(log,"Number of Tags: " + str(md3.numTags))
      message(log,"Number of Surfaces: " + str(md3.numSurfaces))
      message(log,"Number of Skins: " + str(md3.numSkins))
      message(log,"Offset Frames: " + str(md3.ofsFrames))
      message(log,"Offset Tags: " + str(md3.ofsTags))
      message(log,"Offset Surfaces: " + str(md3.ofsSurfaces))
      message(log,"Offset end: " + str(md3.ofsEnd))
      if dumpall:
        message(log,"Frames:")
        for f in md3.frames:
          message(log," Mins: " + str(f.mins[0]) + " " + str(f.mins[1]) + " " + str(f.mins[2]))
          message(log," Maxs: " + str(f.maxs[0]) + " " + str(f.maxs[1]) + " " + str(f.maxs[2]))
          message(log," Origin(local): " + str(f.localOrigin[0]) + " " + str(f.localOrigin[1]) + " " + str(f.localOrigin[2]))
          message(log," Radius: " + str(f.radius))
          message(log," Name: " + f.name)
    
        message(log,"Tags:")
        for t in md3.tags:
          message(log," Name: " + t.name)
          message(log," Origin: " + str(t.origin[0]) + " " + str(t.origin[1]) + " " + str(t.origin[2]))
          message(log," Axis[0]: " + str(t.axis[0]) + " " + str(t.axis[1]) + " " + str(t.axis[2]))
          message(log," Axis[1]: " + str(t.axis[3]) + " " + str(t.axis[4]) + " " + str(t.axis[5]))
          message(log," Axis[2]: " + str(t.axis[6]) + " " + str(t.axis[7]) + " " + str(t.axis[8]))
    
        message(log,"Surfaces:")
        for s in md3.surfaces:
          message(log," Ident: " + s.ident)
          message(log," Name: " + s.name)
          message(log," Flags: " + str(s.flags))
          message(log," # of Frames: " + str(s.numFrames))
          message(log," # of Shaders: " + str(s.numShaders))
          message(log," # of Verts: " + str(s.numVerts))
          message(log," # of Triangles: " + str(s.numTriangles))
          message(log," Offset Triangles: " + str(s.ofsTriangles))
          message(log," Offset UVs: " + str(s.ofsUV))
          message(log," Offset Verts: " + str(s.ofsVerts))
          message(log," Offset End: " + str(s.ofsEnd))
          message(log," Shaders:")
          for shader in s.shaders:
            message(log,"  Name: " + shader.name)
            message(log,"  Index: " + str(shader.index))
          message(log," Triangles:")
          for tri in s.triangles:
            message(log,"  Indexes: " + str(tri.indexes[0]) + " " + str(tri.indexes[1]) + " " + str(tri.indexes[2]))
          message(log," UVs:")
          for uv in s.uv:
            message(log,"  U: " + str(uv.u))
    
          message(log," Verts:")
          for vert in s.verts:
            message(log,"  XYZ: " + str(vert.xyz[0]) + " " + str(vert.xyz[1]) + " " + str(vert.xyz[2]))
            message(log,"  Normal: " + str(vert.normal))
    
      shader_count = 0
      vert_count = 0
      tri_count = 0
      for surface in md3.surfaces:
        shader_count += surface.numShaders
        tri_count += surface.numTriangles
        vert_count += surface.numVerts
    
      if md3.numTags >= MD3_MAX_TAGS:
        message(log,"!Warning: Tag limit reached! " + str(md3.numTags))
      if md3.numSurfaces >= MD3_MAX_SURFACES:
        message(log,"!Warning: Surface limit reached! " + str(md3.numSurfaces))
      if md3.numFrames >= MD3_MAX_FRAMES:
        message(log,"!Warning: Frame limit reached! " + str(md3.numFrames))
      if shader_count >= MD3_MAX_SHADERS:
        message(log,"!Warning: Shader limit reached! " + str(shader_count))
      if vert_count >= MD3_MAX_VERTICES:
        message(log,"!Warning: Vertex limit reached! " + str(vert_count))
      if tri_count >= MD3_MAX_TRIANGLES:
        message(log,"!Warning: Triangle limit reached! " + str(tri_count))
    
    def save_md3(settings):
      if settings.logpath:
        if settings.overwrite:
          log = open(settings.logpath,"w")
        else:
          log = open(settings.logpath,"a")
      else:
        log = 0
      message(log,"##########Exporting MD3##########")
      bpy.ops.object.mode_set(mode='OBJECT')
      md3 = md3Object()
      md3.ident = MD3_IDENT
      md3.version = MD3_VERSION
      md3.name = settings.name
      md3.numFrames = (bpy.context.scene.frame_end + 1) - bpy.context.scene.frame_start
    
      for obj in bpy.context.selected_objects:
        if obj.type == 'MESH':
          nsurface = md3Surface()
          nsurface.name = obj.name
          nsurface.ident = MD3_IDENT
    
          vertlist = []
    
          for f,face in enumerate(obj.data.faces):
            ntri = md3Triangle()
            if len(face.verts) != 3:
              message(log,"Found a nontriangle face in object " + obj.name)
              continue
    
            for v,vert_index in enumerate(face.verts):
              uv_u = round(obj.data.active_uv_texture.data[f].uv[v][0],5)
              uv_v = round(obj.data.active_uv_texture.data[f].uv[v][1],5)
    
              match = 0
              match_index = 0
              for i,vi in enumerate(vertlist):
                if vi == vert_index:
                  if settings.ignoreuvs:
                    match = 1#there is a uv match for all
                    match_index = i
                  else:
                    if nsurface.uv[i].u == uv_u and nsurface.uv[i].v == uv_v:
                      match = 1
                      match_index = i
    
              if match == 0:
                vertlist.append(vert_index)
                ntri.indexes[v] = nsurface.numVerts
                ntex = md3TexCoord()
                ntex.u = uv_u
                ntex.v = uv_v
                nsurface.uv.append(ntex)
                nsurface.numVerts += 1
              else:
                ntri.indexes[v] = match_index
            nsurface.triangles.append(ntri)
            nsurface.numTriangles += 1
    
          if obj.data.active_uv_texture:
            nshader = md3Shader()
            nshader.name = obj.data.active_uv_texture.name
            nshader.index = nsurface.numShaders
            nsurface.shaders.append(nshader)
            nsurface.numShaders += 1
          if nsurface.numShaders < 1: #we should add a blank as a placeholder
            nshader = md3Shader()
            nshader.name = "NULL"
            nsurface.shaders.append(nshader)
            nsurface.numShaders += 1
    
          for frame in range(bpy.context.scene.frame_start,bpy.context.scene.frame_end + 1):
            bpy.context.scene.set_frame(frame)
            fobj = obj.create_mesh(bpy.context.scene,True,'PREVIEW')
            fobj.calc_normals()
            nframe = md3Frame()
            nframe.name = str(frame)
            for vi in vertlist:
                vert = fobj.verts[vi]
                nvert = md3Vert()
                nvert.xyz = vert.co * obj.matrix_world
                nvert.xyz[0] = (round(nvert.xyz[0] + obj.matrix_world[3][0],5) * settings.scale) + settings.offsetx
                nvert.xyz[1] = (round(nvert.xyz[1] + obj.matrix_world[3][1],5) * settings.scale) + settings.offsety
                nvert.xyz[2] = (round(nvert.xyz[2] + obj.matrix_world[3][2],5) * settings.scale) + settings.offsetz
                nvert.normal = nvert.Encode(vert.normal)
                for i in range(0,3):
                  nframe.mins[i] = min(nframe.mins[i],nvert.xyz[i])
                  nframe.maxs[i] = max(nframe.maxs[i],nvert.xyz[i])
                minlength = math.sqrt(math.pow(nframe.mins[0],2) + math.pow(nframe.mins[1],2) + math.pow(nframe.mins[2],2))
                maxlength = math.sqrt(math.pow(nframe.maxs[0],2) + math.pow(nframe.maxs[1],2) + math.pow(nframe.maxs[2],2))
                nframe.radius = round(max(minlength,maxlength),5)
    
            md3.frames.append(nframe)
            nsurface.numFrames += 1
            bpy.data.meshes.remove(fobj)
          md3.surfaces.append(nsurface)
          md3.numSurfaces += 1
    
        elif obj.type == 'EMPTY':
          md3.numTags += 1
          for frame in range(bpy.context.scene.frame_start,bpy.context.scene.frame_end + 1):
            bpy.context.scene.set_frame(frame)
            ntag = md3Tag()
            ntag.origin[0] = (round(obj.matrix_world[3][0] * settings.scale,5)) + settings.offsetx
            ntag.origin[1] = (round(obj.matrix_world[3][1] * settings.scale,5)) + settings.offsety
            ntag.origin[2] = (round(obj.matrix_world[3][2] * settings.scale,5)) + settings.offsetz
            ntag.axis[0] = obj.matrix_world[0][0]
            ntag.axis[1] = obj.matrix_world[0][1]
            ntag.axis[2] = obj.matrix_world[0][2]
            ntag.axis[3] = obj.matrix_world[1][0]
            ntag.axis[4] = obj.matrix_world[1][1]
            ntag.axis[5] = obj.matrix_world[1][2]
            ntag.axis[6] = obj.matrix_world[2][0]
            ntag.axis[7] = obj.matrix_world[2][1]
            ntag.axis[8] = obj.matrix_world[2][2]
            md3.tags.append(ntag)
    
      if md3.numSurfaces < 1:
        message(log,"Select a mesh to export!")
        if log:
          log.close()
        return
    
      file = open(settings.savepath, "wb")
      md3.Save(file)
      print_md3(log,md3,settings.dumpall)
      file.close()
    
      message(log,"MD3: " + settings.name + " saved to " + settings.savepath)
      if log:
        print("Logged to",settings.logpath)
        log.close()
    
    from bpy.props import *
    class ExportMD3(bpy.types.Operator):
    
      """Export to Quake Model 3 (.md3)"""
    
      bl_idname = "export.md3"
      bl_label = 'Export MD3'
    
    
      filepath: StringProperty(subtype = 'FILE_PATH',name="File Path", description="Filepath for exporting", maxlen= 1024, default= "")
      md3name: StringProperty(name="MD3 Name", description="MD3 header name / skin path (64 bytes)",maxlen=64,default="")
      md3log: StringProperty(name="MD3 Log", description="MD3 log file path",maxlen=1024,default="export_md3.log")
      md3overwritelog: BoolProperty(name="Overwrite log", description="Overwrite log (off == append)", default=True)
      md3dumpall: BoolProperty(name="Dump all", description="Dump all data for md3 to log",default=False)
      md3ignoreuvs: BoolProperty(name="Ignore UVs", description="Ignores uv influence on mesh generation. Use if uv map not made.",default=False)
      md3scale: FloatProperty(name="Scale", description="Scale all objects from world origin (0,0,0)",default=1.0,precision=5)
      md3offsetx: FloatProperty(name="Offset X", description="Transition scene along x axis",default=0.0,precision=5)
      md3offsety: FloatProperty(name="Offset Y", description="Transition scene along y axis",default=0.0,precision=5)
      md3offsetz: FloatProperty(name="Offset Z", description="Transition scene along z axis",default=0.0,precision=5)
    
    
      def execute(self, context):
       settings = md3Settings(savepath = self.properties.filepath,
                              name = self.properties.md3name,
                              logpath = self.properties.md3log,
                              overwrite = self.properties.md3overwritelog,
                              dumpall = self.properties.md3dumpall,
                              ignoreuvs = self.properties.md3ignoreuvs,
                              scale = self.properties.md3scale,
                              offsetx = self.properties.md3offsetx,
                              offsety = self.properties.md3offsety,
                              offsetz = self.properties.md3offsetz)
       save_md3(settings)
       return {'FINISHED'}
    
      def invoke(self, context, event):
    
        wm = context.window_manager
    
        return {'RUNNING_MODAL'}
    
    
      @classmethod
      def poll(cls, context):
        return context.active_object is not None
    
    
    def menu_func(self, context):
    
    Campbell Barton's avatar
    Campbell Barton committed
      newpath = os.path.splitext(bpy.context.blend_data.filepath)[0] + ".md3"
    
      self.layout.operator(ExportMD3.bl_idname, text="Quake Model 3 (.md3)").filepath = newpath
    
      bpy.types.TOPBAR_MT_file_export.append(menu_func)
    
      bpy.types.TOPBAR_MT_file_export.remove(menu_func)
    
    
    if __name__ == "__main__":
    
    Campbell Barton's avatar
    Campbell Barton committed
      register()