KatsBits Community

[MD5] EXPORT script for Blender 2.6x (OPEN)

keless · 228 · 350041

0 Members and 1 Guest are viewing this topic.

Offline keless

  • Newbie
    • Posts: 29

IMPORTANT: When posting problems, errors or support requests please reference the exact version of Blender used along with which script - not all versions, or combinations of either (Blender release candidates included) are supported so this information helps track issues.


To download the latest MD5 export script from KatsBits click here.
Generic MD5 installation and usage instructions are condensed here for convenience.

NOTES
Depending on the script used there may be issues with bone bounding-boxes. This does not have any negative bearing on the functionality of any exported models, unless, more sophisticated properties need to be used, i.e. using bone and their respective bound boxes for collision or collision-articulation (AF) which might be better suited to using external meshes or other system of mesh simplification and demarcation.

1) Re: 2.63. The MD5 export script for 2.63 has been found to work with the following versions of Blender; 2.67, 2.68a, 2.69, 2.70, 2.72. However, be sure to double-check support before use in any live projects (install Blender to a unique folder per version and test).

2) Re: 2.62. Script works correctly in Blender 2.62 ONLY.

3) Re: 2.59. Script works correctly in Blender 2.59 ONLY


MD5 Models, Multiple Textures/Materials

MD5 & Multiple Meshes (Blender)



Sadly not yet... I've been onto der_ton to see if he's had a chance to address this yet but so far he's not been able to do anything due to real life getting in the way. I think a number of these 'old' scripts are going to need the community to come up with the goods as the original authors of them have either lost interest and gone MIA or have other more pressing responsibilities - wife/baby/cat/goldfish [delete as appropriate].

I've recently added an MD5 loader and render support in my game engine, but I hate animating in old Blender 2.4x so it looks like I need to roll up my sleeves and write a 2.5x exporter. I'm somewhat familiar with the MD5 format now (plus its a human-readable format) but I'm not familiar with writing Blender exporters (or python for that matter, but no biggie I'm a professional).

Is anyone familiar with writing Blender 2.5x exporters that could give me an overview of that side of things before I delve in (someone did an Md3 exporter recently)? Alternatively, I'll bite the bullet and search for some tutorials.


Offline kat

  • Administrator
  • Hero Member
  • *
    • Posts: 3145
    • KatsBits
The MD3 was a bit of a 'hack' job from what the author told me. He did copy/paste some sections from the old ones to save time (why re-invent the wheel), but pretty much rewrote the thing. I'm having trouble getting Blenders website atm but there is supposed to be a knowledge base there for script authors looking for info on updating to 2.5.

http://www.blender.org/development/
The actual docs for Blender are here - http://wiki.blender.org/index.php/BlenderDev/Blender2.5


Offline keless

  • Newbie
    • Posts: 29
Thanks Kat.

With those, plus a python Quick Guide and Xembie's md3 exporter as reference, I've started down the yellow brick road. I have to diverge from the md3 script as it seems to be a binary format, whereas md5 is ascii (unless I'm reading the python code incorrectly)

Beginning to flesh out my data structs, can any python lovers remark if I'm messing things up?
Code: [Select]
__author__ = ["Paul Zirkle AKA Keless"]
__version__ = '0.1'
__url__ = ["www.blender.org","http://xreal.sourceforge.net","http://www.katsbits.com"]

# referenced export_md3_12.py by Xembie

"""
Name: 'Quake Model 5 (.md5mesh/.md5anim)'
Blender: 253
Group: 'Export'
Tooltip: 'Export a Md5 mesh/anim'
"""

import bpy,struct,math,os,time

MAX_QPATH = 64
MD5_VER_TAG = "MD5Version"
MD5_VERSION = 10

class Md5Joint_t:
name = "" #string
parent = -1 #int
pos = [] #float3 as vector3
orient = [] #float3 as quaternion3 (w is computed from xyz upon loading)

def __init__(self):
self.pos = [0.0, 0.0, 0.0]
self.orient = [0.0, 0.0, 0.0]

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, parentName):
strOut = '    "' + self.name + '" ' + parent \
+ ' ( ' + self.pos[0] + ' ' + self.pos[1] + ' ' + self.pos[2] + ' )' \
+ ' ( ' + self.orient[0] + ' ' + self.orient[1] + ' ' + self.orient[2] + ' ) ' \
+ '    // ' + parentName
file.write(strOut)

class Md5Vertex_t:
u
v
boneWeight = 0 #int
boneCount = 0 #int

def __init__(self):
self.u = 0.0
self.v = 0.0

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, index):
strOut = '    vert ' + index \
+ ' ( ' + self.u + ' ' + self.u + ' ) ' \
+ self.boneWeight + ' ' + self.boneCount
file.write(strOut)

class Md5Triangle_t:
indices = [] #int[3] vertex indicies

__init__(self):
self.indices = [ 0, 0, 0 ]

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, index):
strOut = '    tri ' + index + ' ' \
+ self.indicies[0] + ' ' \
+ self.indicies[1] + ' ' \
+ self.indicies[2]
file.write(strOut)



as reference, here are some of the relevant data structure outputs snipped from an md5 file:
Code: [Select]
"Body2" 1 ( 0 0.0000023712 39.7816314697 ) ( -0.7071067095 0 0 ) // Body
vert 4 ( 0.5554450154 0.2438279986 ) 13 4
tri 1 0 1 3

TIA


Offline kat

  • Administrator
  • Hero Member
  • *
    • Posts: 3145
    • KatsBits
Not sure if this will be of use here but might be a good idea to keep an eye on rich_is_bored's ASE topic, there's an interesting problem cropping up with that script to do with the latest builds of Blender.

And yes you're right, MD3 is compiled binary format. Although MD5 is 'ascii', a couple of games - Quake Wars and Wolfenstein - made use of a binary version of the format; iirc you just output a normal MD5's and then run them through the game editing tools to produce the binary versions.


Offline keless

  • Newbie
    • Posts: 29
Was on road trip this weekend, so didn't get a lot done, here is what I have:
Code: [Select]
__author__ = ["Paul Zirkle AKA Keless"]
__version__ = '0.1'
__url__ = ["www.blender.org","http://xreal.sourceforge.net","http://www.katsbits.com"]

# referenced export_md3_12.py by Xembie

"""
Name: 'Quake Model 5 (.md5mesh/.md5anim)'
Blender: 253
Group: 'Export'
Tooltip: 'Export a Md5 mesh/anim'
"""

import bpy,struct,math,os,time

MAX_QPATH = 64
MD5_COMMANDLINE = "created by Blender 2.53 with export_md5.py (v0.1) by Paul Zirkle"
MD5_VERSION = 10

class Md5Joint_t:
name = "" #string
parent = -1 #int
parentName = "" #string
pos = [] #float3 as vector3
orient = [] #float3 as quaternion3 (w is computed from xyz upon loading)

def __init__(self):
self.pos = [0.0, 0.0, 0.0]
self.orient = [0.0, 0.0, 0.0]

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file):
strOut = '    "%s" %d ( %f %f %f ) ( %f %f %f )    // %s ' % \
(self.name, parent, self.pos[0], self.pos[1], self.pos[2], \
self.orient[0], self.orient[1], self.orient[2], self.parentName )

#strOut = '    "' + self.name + '" ' + parent \
# + ' ( ' + self.pos[0] + ' ' + self.pos[1] + ' ' + self.pos[2] + ' )' \
# + ' ( ' + self.orient[0] + ' ' + self.orient[1] + ' ' + self.orient[2] + ' ) ' \
# + '    // ' + self.parentName
file.write(strOut)

#vertex texture and bone weight data
class Md5Vertex_t:
u = 0.0 #float
v = 0.0 #float
boneWeight = 0 #int
boneCount = 0 #int

def __init__(self):
self.u = 0.0
self.v = 0.0

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, index):
strOut = '    vert %d ( %f %f ) %d %d ' % \
( index, self.u, self.v, self.boneWeight, self.boneCount)
#strOut = '    vert ' + index \
# + ' ( ' + self.u + ' ' + self.v + ' ) ' \
# + self.boneWeight + ' ' + self.boneCount
file.write(strOut + '\n')

#triangle index data
class Md5Triangle_t:
indices = [] #int[3] vertex indicies

def __init__(self):
self.indices = [ 0, 0, 0 ]

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, index):
strOut = '    tri %d %d %d %d ' % \
( index, self.indices[0], self.indices[1], self.indices[2] )
#strOut = '    tri ' + index + ' ' \
# + self.indicies[0] + ' ' \
# + self.indicies[1] + ' ' \
# + self.indicies[2]
file.write(strOut + '\n')

#weighted vertex data
class Md5Weight_t:
jointIdx = 0 #int
bias = 0.0 #float
pos = [] #float[3] vertex

def __init__(self):
self.pos = [ 0.0, 0.0, 0.0 ]

def GetSize(self):
return struct.calcsize(self.binaryFormat)

#serialize
def Save(self, file, index):
strOut = '    weight %d %d %f ( %f %f %f ) ' % \
( index, self.jointIdx, self.bias, self.pos[0], self.pos[1], self.pos[2] )
#strOut = '    weight ' + index +  ' ' \
# + self.jointIdx + ' ' + self.bias  \
# + ' ( ' +  self.pos[0] + ' ' + self.pos[1] + ' ' + self.pos[2] + ' ) '
file.write(strOut + '\n')

#holds verts, tris, and weights
class Md5Mesh:
name = ""
shader = "" #string referencing skin file (dont appent ext)
verts = [] # list of Md5Vertex_t
tris = [] # list of Md5Triangle_t
weights = [] # list of Md5Weight_t

def __init__(self):
self.numVerts = 0
self.numTris = 0
self.numWeights = 0

#serialize
def Save(self, file):
strOut = 'mesh { \n'
strOut += '    // meshes: ' + self.name + '\n'
strOut += '    shader "' + self.shader + '" \n'
strOut += '\n'
strOut += '    numVerts ' + str( len(self.verts) ) + '\n'
file.write(strOut)
for vi in range(len(self.verts)):
self.verts[vi].Save(file, vi)

strOut = '\n'
strOut += '    numtris ' + str( len(self.tris) ) + '\n'
file.write(strOut)
for ti in range(len(self.tris)):
self.tris[ti].Save(file, ti)

strOut = '\n'
strOut += '    numweights ' + str( len(self.weights) ) + '\n'
file.write(strOut)
for wi in range(len(self.weights)):
self.weights[wi].Save(file, wi)

strOut = '\n'
strOut += '} \n\n'
file.write(strOut)


class md5meshFileObject:
ver = 0 # should be "MD5Version 10"
commandLine = "" # ignored, so insert blatant self-promotion
skeleton = [] # list of Md5Joint_t
meshes = [] # list of Md5Mesh

def __init__(self):
self.ver = MD5_VERSION
self.commandLine = MD5_COMMANDLINE

def Save(self, file):
strOut = 'MD5Version ' + str(self.ver) + '\n'
strOut += 'commandline "' + self.commandLine + '" \n'
strOut += '\n'
strOut += 'numJoints ' + str( len(self.skeleton) ) + '\n'
strOut += 'numMeshes ' + str( len(self.meshes) ) + '\n'
strOut += '\n'
strOut += 'joints { \n'
file.write(strOut)
for joint in self.skeleton:
  joint.Save(file)

strOut = '} \n'
strOut += '\n'
file.write(strOut)
for mesh in self.meshes:
  mesh.Save(file)


def message(log,msg):
  if log:
    log.write(msg + "\n")
  else:
    print(msg)

class md5Settings:
def __init__(self, savepath, name, logtype, scale=1.0, offsetx=0.0, offsety=0.0, offsetz=0.0):
self.savepath = savepath
self.name = name
self.logtype = logtype
self.scale = scale
self.offsetx = offsetx
self.offsety = offsety
self.offsetz = offsetz

#serialize
def save_md5(settings):
starttime = time.clock() #start timer
newlogpath = os.path.splitext(settings.savepath)[0] + ".log"
if settings.logtype == "append":
log = open(newlogpath,"a")
elif settings.logtype == "overwrite":
log = open(newlogpath,"w")
else:
log = 0

message(log,"######################BEGIN######################")
message(log,"Exporting selected objects...")
bpy.ops.object.mode_set(mode='OBJECT')
md5 = md5meshFileObject()

for obj in bpy.context.selected_objects:
  if obj.type == 'MESH':
    bpy.context.scene.set_frame(bpy.context.scene.frame_start) #set timeline to 0 so we get clean mesh
    nobj = obj.create_mesh(bpy.context.scene,True,'PREVIEW') #make a copy of the pure mesh

    md5mesh = Md5Mesh()
    md5mesh.name = obj.name

    md5.meshes.append( md5mesh )

    try:
      md5mesh.shader = obj["md5shader"] #pull from custom property
    except:
      md5mesh.shader = ""

    #we will store a list of unique vertex indicies here as we iterate the mesh's faces
    vertlist = []

    for f,face in enumerate(nobj.faces): #iterate each triangle face
      md5tri = Md5Triangle_t()
      if len(face.verts) != 3:
        message(log,"WARNING: Skipping a non-triangle face in object " + obj.name)
        continue
      for v,vert_index in enumerate(face.verts):
        uv_u = round(nobj.active_uv_texture.data[f].uv[v][0],5)
        uv_v = round(nobj.active_uv_texture.data[f].uv[v][1],5)
        match  = 0 #boolean, true if this is a non-unique vertex
        match_index = 0 #int, index of vertex if already listed
        for i,vi in enumerate(vertlist):
          if vi == vert_index:
            if md5mesh.verts[i].u == uv_u and md5mesh.verts[i].v == uv_v:
              match = 1
              match_index = i

        #ERROR: this isnt right, it assumes the same number of weights and verts, which is a lie!
        if match == 0: #found new unique vertex
          vertlist.append(vert_index)
          md5tri.indices[v] = md5mesh.numVerts
          md5vert = Md5Vertex_t()
          md5vert.u = uv_u
          md5vert.v = uv_v
          #TODO: md5vert.boneWeight
          #TODO: md5vert.boneCount
          md5weight = Md5Weight_t()
          #TODO: md5weight.jointIdx
          #TODO: md5weight.bias
          md5weight.pos = nobj.verts[vert_index].co * obj.matrix_world
          md5weight.pos[0] = round(((md5weight.pos[0] + obj.matrix_world[3][0]) * settings.scale) + settings.offsetx,5)
          md5weight.pos[1] = round(((md5weight.pos[0] + obj.matrix_world[3][1]) * settings.scale) + settings.offsety,5)
          md5weight.pos[2] = round(((md5weight.pos[0] + obj.matrix_world[3][2]) * settings.scale) + settings.offsetz,5)
          md5mesh.verts.append(md5vert)
          md5mesh.weights.append(md5weight)
          md5mesh.numVerts += 1
        else: #found matched vertex
          md5tri.indices[v] = match_index

      md5mesh.tris.append(md5tri)
      md5mesh.numTris += 1


    #end for f,face in enumerate(nobj.face)
    bpy.data.meshes.remove(nobj) #clean up temporary copy
    #TODO: now read in reference skeleton

  elif obj.type == 'EMPTY':
  message(log, "skip empty obj " + obj.name)

if bpy.context.selected_objects:
  file = open(settings.savepath + ".md5mesh", "w")  #removed "wb" -- ascii, so dont open in binary mode
  md5.Save(file)
  file.close()
  #TODO: save md5anim
  message(log, "MD5 saved to " + settings.savepath)
  elapsedtime = round(time.clock() - starttime,5)
  message(log, "Elapsed " + str(elapsedtime) + " seconds")
else:
  message(log, "Select an object to export!")

if log:
  print("Logged to", newlogpath)
  log.close()
 
#now create the UI registration interface
from bpy.props import *
class ExportMD5(bpy.types.Operator):
  '''Export to Quake Model 5 (.md5mesh)'''
  bl_idname = "export.md5"
  bl_label = 'Export MD5'
 
  logenum = [("console","Console","log to console"),
             ("append","Append","append to log file"),
             ("overwrite","Overwrite","overwrite log file")]
 
  filepath = StringProperty(subtype = 'FILE_PATH',name="File Path", description="Filepath for exporting", maxlen= 1024, default= "")
  md5name = StringProperty(name="MD5 Name", description="MD5 header name / skin path (64 bytes)",maxlen=64,default="")
  md5logtype = EnumProperty(name="Save log", items=logenum, description="File logging options",default = 'console')
  md5scale = FloatProperty(name="Scale", description="Scale all objects from world origin (0,0,0)",default=1.0,precision=5)
  md5offsetx = FloatProperty(name="Offset X", description="Transition scene along x axis",default=0.0,precision=5)
  md5offsety = FloatProperty(name="Offset Y", description="Transition scene along y axis",default=0.0,precision=5)
  md5offsetz = FloatProperty(name="Offset Z", description="Transition scene along z axis",default=0.0,precision=5)
 
  def execute(self, context):
   settings = md5Settings(savepath = self.properties.filepath,
                          name = self.properties.md5name,
                          logtype = self.properties.md5logtype,
                          scale = self.properties.md5scale,
                          offsetx = self.properties.md5offsetx,
                          offsety = self.properties.md5offsety,
                          offsetz = self.properties.md5offsetz)
   save_md5(settings)
   return {'FINISHED'}
   
  def invoke(self, context, event):
    wm = context.manager
    wm.add_fileselect(self)
    return {'RUNNING_MODAL'}

  def poll(self, context):
    return context.active_object != None
   
def menu_func(self, context):
  newpath = os.path.splitext(bpy.context.main.filepath)[0] + ".md5"
  self.layout.operator(ExportMD5.bl_idname, text="Quake Model 5 (.md5)", icon='BLENDER').filepath = newpath
     
def register():
  bpy.types.register(ExportMD5)
  bpy.types.INFO_MT_file_export.append(menu_func)
   
def unregister():
  bpy.types.unregister(ExportMD5)
  bpy.types.INFO_MT_file_export.remove(menu_func)
   
if __name__ == "__main__":
  register()
   

When used with the simple start up cube in blender (after CTRL-T to subdivide it into triangles), I get this result:
Code: [Select]
MD5Version 10
commandline "created by Blender 2.53 with export_md5.py (v0.1) by Paul Zirkle"

numJoints 0
numMeshes 1

joints {
}

mesh {
    // meshes: Cube
    shader ""

    numVerts 20
    vert 0 ( 0.000000 0.000000 ) 0 0
    vert 1 ( 1.000000 0.000000 ) 0 0
    vert 2 ( 1.000000 1.000000 ) 0 0
    vert 3 ( 0.000000 1.000000 ) 0 0
    vert 4 ( 0.000000 0.000000 ) 0 0
    vert 5 ( 1.000000 0.000000 ) 0 0
    vert 6 ( 1.000000 1.000000 ) 0 0
    vert 7 ( 0.000000 1.000000 ) 0 0
    vert 8 ( 0.000000 0.000000 ) 0 0
    vert 9 ( 1.000000 0.000000 ) 0 0
    vert 10 ( 0.000000 1.000000 ) 0 0
    vert 11 ( 1.000000 1.000000 ) 0 0
    vert 12 ( 0.000000 0.000000 ) 0 0
    vert 13 ( 1.000000 0.000000 ) 0 0
    vert 14 ( 0.000000 1.000000 ) 0 0
    vert 15 ( 1.000000 1.000000 ) 0 0
    vert 16 ( 1.000000 0.000000 ) 0 0
    vert 17 ( 0.000000 1.000000 ) 0 0
    vert 18 ( 1.000000 0.000000 ) 0 0
    vert 19 ( 1.000000 1.000000 ) 0 0

    numtris 12
    tri 0 0 1 2
    tri 1 0 2 3
    tri 2 4 5 6
    tri 3 4 6 7
    tri 4 8 9 10
    tri 5 9 11 10
    tri 6 12 13 14
    tri 7 13 15 14
    tri 8 0 16 17
    tri 9 16 11 17
    tri 10 12 18 19
    tri 11 12 19 7

    numweights 20
    weight 0 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 1 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 2 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 3 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 4 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 5 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 6 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 7 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 8 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 9 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 10 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 11 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 12 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 13 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 14 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 15 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 16 0 0.000000 ( -1.000000 -1.000000 -1.000000 )
    weight 17 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 18 0 0.000000 ( 1.000000 1.000000 1.000000 )
    weight 19 0 0.000000 ( -1.000000 -1.000000 -1.000000 )

}


I don't have any code to pull the skeleton yet (and that sample doesnt have bones anyway) but there is a problem besides that: the num weights and num verts are exactly the same. I tried to use analogies from the original md3 exporter when writing this, but they dont quite work out. I'm not really sure what I should be pulling the "weights" and "verts" from (though I think the "tris" section is correct).

Part of the problem being the MD5 naming scheme is terrible since "weights" has actual XYZ vertex data, and "verts" has UV texcoords data.



Offline kat

  • Administrator
  • Hero Member
  • *
    • Posts: 3145
    • KatsBits
I just looked at my old 'Bob' character model files I've got on site and the *.md5mesh data is not that far off what you've got there.

NumWeights looks like this;
Code: [Select]
weight 0 15 1.000000 0.872552 4.004826 1.665381NumVerts looks like this;
Code: [Select]
vert 0 0.162109 0.550781 0 1So at first glance it looks like the format is actually structured that way in terms of it's appearance without necessarily meaning that it *is* XYZ coordinate data?... if that makes sense?


Offline keless

  • Newbie
    • Posts: 29
I just looked at my old 'Bob' character model files I've got on site and the *.md5mesh data is not that far off what you've got there.

NumWeights looks like this;
Code: [Select]
weight 0 15 1.000000 0.872552 4.004826 1.665381NumVerts looks like this;
Code: [Select]
vert 0 0.162109 0.550781 0 1So at first glance it looks like the format is actually structured that way in terms of it's appearance without necessarily meaning that it *is* XYZ coordinate data?... if that makes sense?

Well, if the vector3f in Weight _isnt_ an XYZ coordinate of a vector, where IS that data? I basically assumed it was positional since there doesnt seem to be any other positional data other than the quat+pos for bones.

Tomorrow I'll take a look at the old 2.4x export script and see what I can figure out.


Offline ratty redemption

  • VIP
  • Hero Member
  • *
    • Posts: 1031
    • ratty's deviantart pages
@keless, although i can't help i 'applaud' you for working on this *thumbs up*


Offline keless

  • Newbie
    • Posts: 29
@keless, although i can't help i 'applaud' you for working on this *thumbs up*

np, I needed to practice some python anyway  ;)   Just wish it wasn't a weakly typed language, it makes it harder to read other people's scripts when they don't comment their variable declarations


Offline ratty redemption

  • VIP
  • Hero Member
  • *
    • Posts: 1031
    • ratty's deviantart pages
@keless, understood and i can imagine that would be frustrating, but well done for persevering, it's a shame there aren't more coders like you willing to work on these import/export scripts.


Offline der_ton

  • Defunct
  • Newbie
    • Posts: 1
Hi, I'm the guy who wrote the script for the 2.4x Blender version. I think you could save some time by reusing some of it and just updating the parts that interface with Blender to get the data out of Blender and into the python script's structures, and from there the already existing md5 output will work, independently of the changes in Blender2.5.

I can fully understand that you don't want to use the existing script though. Programming is fun, and programming your own stuff and going all the way on your own legs (or brains, hehe) is much more fun than reusing someone else's stuff (even though I like to think my old script is not too bad code, you're right, python makes it a bit more difficult if the script documentation isn't that good).

on topic: The file format apparently was laid out so there's a minimum of preprocessing that the game engine has to do on load-time. The data in the weights is indeed xyz positions, in bone-space of their respective bone. A vertex (in the md5 naming scheme) doesn't have a position because there is no fixed position in an animated mesh. A md5mesh vertex' position is determined by its position relative to its (possibly multiple) influencing bones, and these relative positions are those 'weights' positions. There are pages on doom3world.org and I also think on http://www.modwiki.net/ that describe the semantics of the md5 file contents. Sorry if that sounds rude, but how did you write md5 rendering code for your engine if a detail like that raises questions?


Offline keless

  • Newbie
    • Posts: 29
Hey der_ton--   yes, thats actually what I'm doing right now (using the module registration code from the md3 loader, but converting your 2.4 code into 2.5)

On that note, does anyone know what the Blender 2.5x translation of the 2.4x call to
Code: [Select]
data.getVertexInfluences(face.v[i].index) would be?

EDIT:
I'm reading some of the other 2.5x exporters (like export_obj.py) and I'm thinking the 'bone' names associated with the vertex are found as the vertex group names? So a vertex with 4 groups named 'g1, g2, g3, g4' would be associated with bones 'g1', 'g2', 'g3', and 'g4' and somewhere there will be a per-vertex list of weights.

So to get a list of bones associated with a vertex, it looks like syntax might be:
Code: [Select]
mesh = someMeshObj
for vert in mesh.verts:
  boneWeights = [0] * len( vert.groups )
  for vgIdx in range(len(vert.groups)):
    boneWeights[vgIdx].objVgIdx = vert.groups[vgIdx].group
    boneWeights[vgIdx].weight = vert.groups[vgIdx].weight
  
  for weight in boneWeights:
    weight.name = mesh.vertex_groups[ weight.objVgIdx ].name



Offline keless

  • Newbie
    • Posts: 29
Anyone know how to remove objects from a bpy_prop_collection in 2.5x?

specifically:
Code: [Select]
nobj = obj.create_mesh(bpy.context.scene,True,'PREVIEW')
faces = nopj.faces

for face in faces[:]:
  #some code here
  faces.remove(face.index)  #ERROR HERE

in the 2.4x md5 script, he iterates over the list of faces in a mesh, and removes each face as it's processed. However, with the 2.5x code the list of faces is such that the array of faces held by the "preview" object (or by bpy.data.meshes['meshname'].faces ) is 'protected' and remove() function exists but fails:

"TypeError: bpy_prop_collection.remove() not supported for this collection"


Offline kat

  • Administrator
  • Hero Member
  • *
    • Posts: 3145
    • KatsBits
Take a look on the ModWiki page for bone info because there are significant organisational differences between older and newer MD5's and their data structure. I just had a look at the file I posted above vs the files contained in the source zip on the same page, one's a v6 file, the other a v10 (something I need to correct as I missed that one)  :-\


Offline keless

  • Newbie
    • Posts: 29
Yeah, the MD5s I've been working with are all v10, and der_ton's export script is v10 as well.

I don't know of any versions greater than v10, but I'm only planning on getting 10 to work (ignoring older versions) at the moment.

I'm skipping animation for right now, but I've got a valid md5mesh output going. However, the texture coordinates seem wrong and some of my test files fail to export due to index OOB error.

The file is getting kind of long, is there a way to add attachments to forum posts, or should I set up a public host for sharing?