Go to QuArK Web Site
How to Write Model Importers & Exporters
Updated 28 Dec 2021
Upper levels:
QuArK Information Base
1. Introduction to QuArK
1.5. Model-editor in QuArK

 1.5.3. How to Write Model Importers & Exporters

 [ Prev - Up - Next ] 

The QuArK's Model Structure section covers more of the technical part, in detail, of QuArK's Model Structure for programming purposes and is very important to understand if you plan to do any code writing for the Model Editor.

This section is dedicated to the structure of a QuArK Internal Object that makes up a model component and how to create it when writing a Model Importer and Exporter plugins file. Occasionally references will be made to the Model Editor's Python code located in the quarkpy and plugins folders as well as areas in the 'QuarkX' section of these Infobase docs.

It is highly recommended that you at least read the Components of a Model section before going on with this section to get a good understanding and visualization of a model component's structure since that is what an importer will be creating and an exporter will be writing the model file from.

Although highly technical in understanding a components structure and Python coding, writing a QuArK Model Importer and Exporter is truly a rewarding learning experience. These sections are designed to help you gain that experience with a little effort, time and perseverance. We look forward to seeing your results.


 Index


 What Makes Up a Model Component

cdunde - 05 Apr 2018   [ Top ] 

A model is a single QuArK Internal Object that can consist of a single or multiple components. The elements that make up a model are stored as "key : value" pairs of the QuArK Internal Object, where there is a key name and a value that goes along with that key name.

Also, each of the "key names" CAN BE given a specific type to distinguish what kind of element it is.
So an example of a key, its type and its related value would look like this:

{'teeth:mc': <QuArK Internal object at 0x00CC1E4C>}

The key name is teeth, its type mc (model component) and its value is another QuArK Internal Object containing its subitems, dictspec items and dictitems.
This method of using "key:value" pairs runs all the way through as a model's component(s) elements are broken down.

subitems : This is a list of QuArK Internal Objects that make up a component and controls the order that they are displayed in the editor's tree-view.
They consist of the Skins group (its texture images), Frames group (its animation\vertexes frames) and, SDO group (SystemDataObject) which is not really used at this time but still needs to be created.

A Skeleton group (its bones if any) is created outside of,and shaired by, the components. This group is covered in detail further below.

The creation of the subitems list is not something you need to be concerned about when writing an importer or exporter file.
QuArK takes care of that internally using the component's dictitems below.

dictitems : These are other dictionary items within the component that make it up, they are the same items as its subitems and each one is a key : value pair that an importer file will use to create a QuArK editor component, for example:

{
 'SDO:sdo': , # stands for SystemDataObject
 'Skins:sg': ,
 'Frames:fg': 
}
Notice how these items are not in any particular order. Because they are dictionary list they do not need to be. We use their key name to get what goes along with them, not the order that they are in the dictitems list. Each of these dictitems brake down into their own dictspecs items, but more on that later.

dictspec items : These are key : value item pairs that pertain to the component and its subitems as a whole and are only QuArK editor related.
They are individual dictionary items that contain settings for that component. Some of these settings are used and reset by simply moving things around in the editor, such as vertexes, while others are used to fill in the values on the editor's Specifics/Args page where they can be viewed and\or reset there. The importer file will create these items (covered in detail later), for example:
dictspec items of a model component :

'skinsize' is the size of a component's first skin image or a default size of (256, 256) can be used if there are no skins.
'show' Components can be Hidden in the editor (not seen) when this is set to '\x00' and visible when set to '\x01', so it should always be set to the latter.
'Tris' This is the primary one we will be concerned with when writing a model importer or exporter file.
            Its value is in binary code and needs to remain so to work in QuArK. Think of this code as one big list of smaller, individual list.
            Each of which contain three items, a vert_index (vertex index number), a U texture position value and a V texture position value.
            The U, V values give the flat 2D x , y position of that vertex on the model's skin (in pixels) and the vert_index points to that vertex.
            Each vertex can have more then one U, V value and this is a key point to remember. But more will be covered on that later.

dictspec items of a frame :

{'Vertices': (-1.859375, 31.234375, ...(a bunch more), 54.078125)}
'Vertices' This is a continuous list of float values that give the x,y,z position for each vertex of a model's component mesh. QuArK's internal code breaks these into groups of three to accomplish this, and of course it takes three vertexes, most of which are shared, to make a single triangle of a models mesh.

So there you have it, the breakdown of a component...which can be a bit bewildering at first, but just remember,
you have a dictionary inside a dictionary inside a dictionary.... and just about every dictionary has dictspec items that pertain directly to it.

Now for their relationship between one another in making up a QuArK model component in a model importer file and used for the exporter file as well.
By the way...don't make an importer without an exporter to go along with it...that can be extremely frustrating to others.

All of these files are in the QuArK\plugins folder and are written in Python, look at ie_md2_import.py as a guide. Also, all of them MUST start with ie_ for their name.
That is used to identify them for file loading purposes when QuArK's Model Editor is started up. Otherwise they will not show up on the Importer \ Exporter menu.

As we go through each item of a component, its title is shown the same way it would be written in Python code to gain access to it in an importer or exporter file.

Component.dictitems :

Component.dictitems['Frames:fg'].dictspec :
The only item here is {'type': '\x01'} which must be written to the frames group as shown.
Component.dictitems['Frames:fg'].dictspec['type'] = '\x01'
This is its group type which is used by QuArK's internal source code to identify and handle it.

Component.dictitems['Frames:fg'].dictitems : These are individual dictionary items, one for each frame in the frame group, for example:
'Frame 1:mf': <QuArK Internal object at 0x016827FC>
Each frame has its own dictspec items which are 'index' and 'Vertices'.
index is its position in the frames group, ex:
(0.0,) the comma behind it means it is a tuple.
Vertices is a list of all its 3D vertex positions.
Each vertex position has an x, y and z float value, ex: -1.859375 (positive or negative)
and since it is the first item in this list its vert_index would be 0 shown as Vertex 0.
These items are indicated in red to the right.

Even though this is a continuous list, QuArK knows to brake them up into groups of three (x,y,z) for each vertex.

So all of a frames vertexes make up the points in 3D space that give the model mesh its shape. And it takes three of these vertexes to make a single face of the mesh, even though most vertexes are what we call common vertexes, meaning that they are shared with other faces of the mesh. That's what holds a mesh together as one complete unit. Other wise you could pull it apart into individual faces or pieces...not a good thing.

If a model has more then one frame, these vertex positions change from frame to frame, thus creating its animation movement, but that can also be handled using other files and methods from one model format to another.
(see added footnote 1 below for more info)

Even though the frame vertexes provide the mesh's 3D points in space, the lines between those points (or vertexes) still need to be drawn and a working relationship between them still needs to be connected from one to the other and the other... to create a completed and joined mesh. That is done by the Component.dictspec{'Tris'] item.

Component.dictspec :

Component.dictspec{'Tris']
As indicated by the blue line to the right,
(put your mouse over these for their results)
the 'Tris' is one big list of individual sub-list and each sub-list consist of three items, one for each vertex of a face (or triangle), and each vertex consist of three items also. 1) vert_index, 2) U texture position and 3) V texture position. Follow the blue line to see how each vert_index links to its actual x,y,z 3D point position in a frame['Vertices'] list (covered above)
This is how the two lists link to one another. When refering to the 'Tris' a tri_index (triangle index) number is used that relates to its group's poistion in the 'Tris' list.

So the first triangle (tri_index) in the list would be: Component.dictspec['Tris'][0] its first vertex: Component.dictspec['Tris'][0][0]
and that vertex's vert_index: Component.dictspec['Tris'][0][0][0] its U value: Component.dictspec['Tris'][0][0][1] and its V value: Component.dictspec['Tris'][0][0][2] that's it.
The second vert_index would be: Component.dictspec['Tris'][0][1] and the third vert_index would be: Component.dictspec['Tris'][0][2].

Also notice how each vert_index (vertex) can have more then one U,V texture position (indicated by the green and fusha lines).
It is this vertex multiple uv capability system that allows the skinning of model sections on different tiles (repeated) images of a components skin texture. So even though a model's component mesh is one combined object, its texturing can be made up of many sections, either stacked on top of one another or spread out over many copies of its texture making it much easer to get to and work with those areas.

================================================ Footnotes ==================================================

footnote 1: Model Animation Methods
As mentioned above, model formats can use different methods of animating a model. For example, Quake 2 .md2 models use frames, like we have been covering, for their movement, animation, while Quake 4 models use Lightwave model formats which are .lwo, .md5mesh and .md5anim files. The .lwo is a static model, meaning it only has one frame and it does not have any animation movement to it. While the .md5mesh and .md5anim files (which are text files) are used together to create a moving model. The .md5mesh is a static model that gives the model its base shape and Bones System hierarchy. The .md5anim file gives the vertex movement for its animation using that Bones System. It gets a bit more complicated then that as to how they work exactly, but that is the basic method used for that format.


 Writing QuArK Elements into the Code

cdunde - 28 Dec 2021   [ Top ] 

First off, a little bit of encouragement. When I first started developing for QuArK I knew absolutely nothing about Python code writing, not to mention C or C++ as well. So if you know anything about these languages at all, you are far ahead from where I started. And Python is one of the easiest languages for you to learn.

To begin with, there a numbers of ways to get started. You can look for model importer\exporter files that someone else has already written to go by as a guide
(no I'm not suggesting that you steal code) or you can look for them written in other computer languages or you can start from scratch using a model's format doc.
No matter the choice, I highly recommend that you obtain a copy of the model's format documentation as a guide to go by as you create your own code because many times other files may have parts that have been left out, skipped over or completely incorrect to start with. The format doc will help you spot these kinds of situations and avoid a lot of wasted time. Conversely, having other code to go by as a guide, can help in understanding parts of the documentation when needed.

The example that I am going to be working from is the Quake 2 .md2 model importer file named ie_md2_import.py which is located in your QuArK\plugins folder, so you should open that file now with any text editor as well. We won't be covering everything in that file, just certain points as needed.

File Header  :
At the very top and bottom of you file you should put a comment about what model format this plugin handles. Next, put in a mention of the GPL license, followed by a variable called Info which will tell QuArK some information about the plugin.

At the very top the header would be as shown below:

"""   QuArK  -  Quake Army Knife

QuArK Model Editor importer for Quake 2 .md2 model files.
"""
#
# THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
# FOUND IN FILE "COPYING.TXT"
#

Info = {
   "plug-in":       "ie_md2_importer",
   "desc":          "This script imports a Quake 2 file (MD2), textures, and animations into QuArK for editing.
                     Original code from Blender, md2_import.py, author - Bob Holcomb.",
   "date":          "June 3 2008",
   "author":        "cdunde & DanielPharos",
   "author e-mail": "cdunde@sbcglobal.net",
   "quark":         "Version 6.6.0 Beta 2" }
What you enter for the Info is up to you but we would like to know who made it and any acknowledgements to others that might have helped or were authors of files you used as a guide, that's only fair.

The file name MUST start with ie_ or it will not show up on the QuArK Importer\Exporter menu, that is what QuArK uses to identify and load it by. Also, to be consistent with other files please use its file type, in this case md2, and whether it is an importer or exporter for the rest of its name and please make two separate files, one for each operation. That way users can tell at a glance that both operations exist and it's easier for any possible future additions and changes.

File Imports  :
The very next thing we need to do is to bring in (import) other files and Python Modules that we will be using through out this file. Below is the code which does that.

import struct, sys, os, time, operator
import quarkx
from types import *
import ie_utils
from ie_utils import tobj
from quarkpy.qdictionnary import Strings
You will see how these are used later on as we go through the code writing process from section to section and at which time they will be covered as needed.

Logging Implementation  :
QuArK has a built in model importer\exporter logging system that should be added to your file. By adding this code, a log can be created in various ways, or not at all, depending on the settings you select in the Configuration... > Model > Options > Importers \ Exporters section. One point about logging, the more that you add the slower the operation of importing or exporting can become, so try to keep it to valid and critical areas only without repeating data logging. When the logging option is turned off it has very little effect on the operations.

The working code for this system is in the QuArK\plugins\ie_utils.py file, where other useful functions and utilities should be added that strictly relate to the importers and exporters only. Now, for adding the code, this is also shown in the QuArK\plugins\ie_utils.py file. Be sure to change the names in step (1) for exportername and textlog to your file name and type.

1) To add logging to an importer or exporter put these lines near the top, under the file header, in this order:
import os, time, operator
import ie_utils
from ie_utils import tobj

# Globals
logging = 0
exportername = "ie_md2_export.py" (or importername = "ie_md2_import.py" depending on which one you're doing)
textlog = "md2_ie_log.txt"

2) Then add needed globals and calls to start and end the logging in your main file function like this:
def save_md2(filename):
    global tobj, logging, exportername, textlog ### Needed globals.
    ### Next line starts the logging.
    ### Use "EX" for exporter text, "IM" for importer text.
    logging, tobj, starttime = ie_utils.default_start_logging(exportername, textlog, filename, "EX")

    ### Line below here saves the model (just for this example---DO NOT COPY NEXT LINE).
    fill_md2(md2, component)

    ### Next line is optional, it adds additional text at the bottom of the default message,
    ### with a blank line between them. If none then just exclude it from the function arguments below.
    add_to_message = "Any used skin textures that are not a .pcx\nwill need to be created to go with the model"
    ### Next line ends the logging.
    ### Use "EX" for exporter text, "IM" for importer text.
    ie_utils.default_end_logging(filename, "EX", starttime, add_to_message)


3) Then in any function you want logging declair the global and call for tobj like this: (all items must be strings)
def fill_md2(md2, component):
    global tobj
    if logging == 1:
        tobj.logcon ("#####################################################################")
        tobj.logcon ("Skins group data: " + str(md2.num_skins) + " skins")
        tobj.logcon ("#####################################################################")
        tobj.logcon ("")
You can use tobj.logcon for a word search in any of the importer\exporter files to see how step (3) is applied.

Path Checking Implementation  :
Now to start the working part of your file. This is another function which is also located in the QuArK\plugins\ie_utils.py file. It's purpose is to test for a proper model path, meaning that games setup what you would call a folder path hierarchy for their files to keep things organized and working properly in the game. Some models store this information within the model file itself especially when it comes to the skin texture(s) they use. So it is always best to try and work under these same conditions when importing or exporting a model file.

For example, say you have the game setup for Quake 2, its full path to the actual game files folder might be C:\Program Files\Quake2\baseq2. This is where you would find the .pak files for Quake 2. If you open a couple of those you would see more folders inside them shuch as models/monsters/boss1 and finally the actual model file, tris.md2, inside that last folder. The valid path for that model would then be what is shown within the .pak file itself models/monsters/boss1 and that is all that this function is checking for. To import or export a modle these folders MUST be extracted, copied, from the .pak file to the game folder exactly the same way they are arranged in the .pak file or things may not work right later. Therefore, you can have, what I call a dummy folder, anywhere else on your drive and do the same extraction to that folder to work from, but again using their same exact folder path hierarchy. This helps to ensure that things should work fine later.

If you already did the Logging Implementation above you do not need to do step (1) here.

1) To add path checking to an importer or exporter put this line near the top:
import ie_utils

2) Call for the path check like this:
def loadmodel(root, filename, gamename, nomessage=0):
    ### First we test for a valid (proper) model path.
    basepath = ie_utils.validpath(filename)
    if basepath is None:
        return
If you look near the bottom of the ie_md2_import.py file you will see the above def loadmodel code along with some more code in that function. So now with the File Header code in, Logging Implementation and Path Checking Implementation from above completed, we can now go on to the actual importing code below.


 Writing the Start Function of the Code

cdunde - 05 Apr 2018   [ Top ] 

Now is a good time to bring up some general code writing points that apply to Python and probably some other programming languages as well. Basically speaking, you do not always start at the top of your file and work your way down, but the other way around, from the bottom to the top.

As far as Python goes, there are two general types of code writing, classes, which we will cover later, and standard functions. classes can pretty much be placed anywhere in your file that you wish. That is because they have their own functions written within them, which makes them more placement independent and a broader type of operation handling item.

While standard functions must be placed above any other functions that use them, or things just won't happen, except for some error messages. When you start writing your file you really don't know all the functions that you might need until you reach a point where you half to create some, at which case you'll want to always place them above the one(s) that will be using them so that they get defined, or loaded, first. So with that out of the way, lets go on from where we left off.

As mentioned just above and looking near the bottom of the ie_md2_import.py file you will see the def loadmodel code function. This is the starting, or entry, point into your file to perform its operation, that of importing a Quake 2 .md2 model file that can be edited in QuArK's Model Editor. The same would apply to an exporter.

This is where another function will be called to actually open the .md2 model file, read it and start processing it. But before we get to that part, there are a few other things that need to be done here. so lets breakdown the things that make up this function and what they are doing first.

Function Definition  :

def loadmodel(root, filename, gamename, nomessage=0):
This is where a function is defined, comes alive, and usually has arguments that go along with it. These arguments are things that are being sent to the function to work with and\or do special things within the function, like setting optional items within the function to perform or not perform. When you see something like the nomessage=0 argument, that means it is being set to a default value of zero so if you don't want to change that to another value then you don't need to send that argument when calling this function from another function.

Function Description  :
Next we have what this function is and does...sometimes. Unless it's very obvious, I recommend this be done. It saves people a lot of head scratching time.

Globals & Imports  :
Some functions need more then just what is being sent to them using their arguments. Unless a global's value is going to be changed within the function, it is not necessary to bring them in like this, they can still be called by their name to get whatever they represent. But if they are then they are the first thing that should be written to the function. Globals are things that are defined, set, elsewhere like at the beginning of a file or even another file. They are used to pass their settings from one function to another and\or to set their values, or what else they may be, within a function for passing to others. In this example we are bring in a progressbar, (which is covered in the footnotes at the end of this section) and all the logging globals because they are needed this way for the logging to work.

Next we bring in any imports that are needed, in this case the mdleditor file to define which editor we are working with. If an imported file is in another folder location then where this file will be kept, which it is, then the folder's name needs to proceed the files name, its file type .py is not required. This is so that Python can locate the file and import it, meaning its functions are available to use in this function. Most imports are done at the very top of a file, but if only one or two functions use the file then it might be imported within those functions. Other times things will not work right unless they are imported within the function, so experiment with this.

global progressbar, tobj, logging, importername, textlog, Strings
import quarkpy.mdleditor
editor = quarkpy.mdleditor.mdleditor

The Function's Body  :
This is where the function does its job. First we test to see if the basepath of the file we will be importing is valid or not using the ie_utils.validpath function which we covered earlier and should have already been written to the importer file we are making.

Next comes the call to start the logging, notice how that function is returning other items. Those will be used latter in this function to stop the logging.

Then comes the actual importing function import_md2_model which will be returning the Component that we will be using next to finish the importation of the model.

Once the model has been imported it still needs to be dropped into the editor, that is what the undo functionundo function part of our code does. The part dealing with compframe is assigning each frame a parent or upper level. Without this process, you would not be able to relocate the order of any frames within their component. So it is vital that this code be included in every import\export file made.

Next we close the progressbar since we are done with it at this point and we make the call to end any logging that might be going on.
Notice here how we are now using the arguments returned from above, when we called to start the logging function. This is a perfect example to demonstrate how you can pass things around (arguments) from one function to another and even have things returned to be used latter on. Like a Frisbee with a lot of players.

Finally we call the editor.ok of the undo function, set the current component skin (for proper selection) and our job is done....the model should now be imported. (SUCCESS ! ...well at least once we finish.)

One last point here, have you noticed how you have also been using some of the quarkx functions in your file to get the job done? You can do this if you have also written a import quarkx call at the top of your file....which you have. This demonstrates how you can use functions from all over the place to help you do the work and, as you will see soon, even Python functions and modules which are whole sections of added functions for each module.

(not all code lines are being shown here ..... represent lines left out, see the ie_md2_import.py file for those lines)

    basepath = ie_utils.validpath(filename)
.....
    ### Use "EX" for exporter text, "IM" for importer text.
    logging, tobj, starttime = ie_utils.default_start_logging(importername, textlog, filename, "IM")
.....
    ModelRoot, Component = import_md2_model(editor, filename)
.....
    undo = quarkx.action()
    undo.put(editor.Root, Component)
    editor.Root.currentcomponent = Component
    compframes = editor.Root.currentcomponent.findallsubitems("", ':mf') # get all frames
    for compframe in compframes:
        compframe.compparent = editor.Root.currentcomponent # To allow frame relocation after editing.
        progressbar.progress()

    progressbar.close()
.....
    ie_utils.default_end_logging(filename, "IM", starttime) ### Use "EX" for exporter text, "IM" for importer text.

    editor.ok(undo, Component.shortname + " created")

    comp = editor.Root.currentcomponent
    skins = comp.findallsubitems("", ':sg')      # Gets the skin group.
    if len(skins[0].subitems) != 0:
        comp.currentskin = skins[0].subitems[0]      # To try and set to the correct skin.
        quarkpy.mdlutils.Update_Skin_View(editor, 2) # Sends the Skin-view for updating and center the texture in the view.
    else:
        comp.currentskin = None

If you were also importing Bones or BBoxes (bounding boxes) from the model this is the second of two places where that code would be added now that the component has been created.
See the section below covering Bones Structure and QuArK's ModelComponentList for the needed code to copy and paste in this location of your import file.

Last, but not least, at the very bottom of our importer file we need to Register our starting function so that it will show up on the Files > Importer \ Exporter menus in the QuArK Model Editor. This is done outside of our function and just below it.

### To register this Python plugin and put it on the importers menu.
import quarkpy.qmdlbase
quarkpy.qmdlbase.RegisterMdlImporter(".md2 Quake2 Importer", ".md2 file", "*.md2", loadmodel)

After reading the footnote below about Progress Bars proceed on to the next section.

================================================ Footnotes ==================================================

footnote 1: Progress Bars
As mentioned above this file uses a Progress Bar as all of them should but it is optional. They will not always be seen if the model file is small, but for those that are fairly large, such as Lightwave Doom 3 .lwo outdoor scene files (I've loaded one with 13 Components) the progress bar lets people know that QuArK has not crashed but still hard at work getting the model into the editor. You might have already click on the Progress Bars link to the quarkx functions and read about it, but there is one nice little trick that you can add to it....have the name of the component that is currently being imported displayed as you import them. But first let's cover how to set them up, this is done as a global (Aw-Ha ! Now you see why we called it in our function above here.)

Originally the global for it could have been created near the very top of our file when we did the Logging Implementation and Path Checking Implementation in the previous section. But it might have been a bit confusing to do at that time, so I waited until now to spring this on you. (Aren't I a stinker !)
So now go back to the top and add this code with the rest of the globals to initialize (setup) your Progress Bar. Then come back for the next step.

progressbar = None
In the next section below we will cover a function called load_md2 and where you will once again see the global progressbar being called and soon afterwards you will see the next two lines of code. The first line is where that trick I was telling you about takes place by adding the component's name that is currently being processed to the default string of text that would normally be used to display on the progress bar, and the second line of code which activates the progressbar by giving its settings for the updated string and md2.num_faces + (md2.num_frames * 2), maximum faces that it should count to.
    Strings[2454] = name + "\n" + Strings[2454]
    progressbar = quarkx.progressbar(2454, md2.num_faces + (md2.num_frames * 2))
Further down in that same function you will see the next line of code that advances the progressbar as that particular component's models faces are processed.
        progressbar.progress()
Later on you will see these next two lines of code, the first to close the progressbar and the second to reset its default string back to what it was.
    progressbar.close()
    Strings[2454] = Strings[2454].replace(Component.shortname + "\n", "")
This is required or you will wind up with the previously processed component's name showing up above the component's name that is currently being processed. This same trick can be used in as many places as you desire, but the process must always be the same. It's best to use as few as possible or they won't show up.
1) Import the global progressbar, 2) add the name to the default string, 3) activate (set) the progressbar,
4) advance its count, 5) close the progrssbar and 6) reset its default string.

The Strings you see in the code, and also imported as a global, are all of the string (text) statements which are in the QuArK\quarkpy\qdictionnary.py file and using the number for the default setting above 2454 as a word search in that file you will find that default text string which is "Preparation data read...Exporting model".


 Writing the File Processing of the Code

cdunde - 05 Apr 2018   [ Top ] 

If you have completed the previous sections, you are now ready to start writing the part of the code that opens, reads and processes a model file.
We will continue to use the QuArK\plugins\ie_md2_import.py file as our working example, refer to it often since most of the code will be skipped here to keep it brief.

The next function we need to write is import_md2_model, which is called from our previous function loadmodel passing the editor and filename to our new function.

In the first part of its code you will see a section that deals with checking to see if we have already imported any other models into the editor so that it can get a proper numbering sequence to add to the name of the component that is currently being imported.
All names need to be distinct to avoid problems with other functions of the editor's code later on when working on (editing) the model.

The line of code following the above section calls another function, load_md2, which will return the various parts of data we will use in the following section of this function to create the component itself in a format that QuArK can understand and use, The Component's Format.

    Tris, skinsize, skingroup, framesgroup = load_md2(md2_filename, name) # Loads the model.

And finally, at the very end, this function will return the ModelRoot and the Component back to the loadmodel function to finish the whole process by putting the model's component into the editor, using its undo section of code.

Because we are working with the .md2 code, a Quake 2 model only has one component per model. If we were importing a different kind of model format it might have more then one. In which case, this whole process would continue in one big loop, in this function, until all of the model's components have been loaded, creating a list of components that would then be returned to the previous function loadmodel where each component in that list would be handled by its undo section to put them into the editor. You can find this kind of coding in the QuArK\plugins\ie_lwo_import.py file to use as a guide.

Now let's go on to our next function load_md2

Code to Open & Read a Model File  :
Near the very beginning of the load_md2 function we see this line of code which actually opens the .md2 model file for reading.

    file=open(md2_filename,"rb")
Notice the "rb", that stands for read binary because that is how most model files store their data, in binary format which you can not read like a regular text file. This saves space and speeds up the use of that file in the game. So yes, we will be getting into a little bit of binary code now. But not to worry, Python has a module called struct that handles the conversion of that type of code into regular text so we can work with it. We just need to cover on how to use that module because we already imported the module at the beginning.

It reads the entire file at one time, stores all the data into memory and then closes the file with the code file.close() a few lines down. Now we're ready to Process.

But before we do that we first need to deal with our progressbar by using that trick to add a component's name to its text and give its settings to start it.
And just below that, after another function call, we start with some of our logging text calls (see the file). Ok, let's go Process the Model's Data.

Code to Process the Model's Data  :
Remember, we are still in our load_md2 function that is going to be calling two other functions to do certain parts of the data processing and return what we need, right back to here, to pass on and create the component with later. These other two functions are:

    skinsize, skingroup = load_textures(md2) # Calls here to make the Skins Group.

    (the Tris creation code is done here)

    framesgroup = animate_md2(md2) # Calls here to make the Frames Group.
And finally it returns all of this reconstructed data right back to our import_md2_model function to finish creating the comonent with.
    return Tris, skinsize, skingroup, framesgroup
But lets not get ahead of ourselves, first we need to cover two more things, the 'Tris' and using the struct module in one of the above functions for the binary code.

As we had covered earlier in the What Makes Up a Model Component section the Tris is a list of smaller lists, each containing a (vert_index, U, V) but in binary code. The section above creates that Tris. The lines of code are long, so I will need to split them up to demonstrate here, looking at the file may also help you.

When ever you set something like this up you first need to define it (make a blank one) and since the Tris is nothing more then a long text string of data, that's what we'll make it with the first line of code.

Then we write a Python loop which just runs through each face (triangle) of the component's mesh adding that data to the Tris. That's what the three lines of code are doing inside our loop, one line for each of the three vertexes of that face (triangle).

Now you are seeing one of the functions (pack) of the struct module being put to work here and that is what the first part ("Hhh") is about.
This is how it tells the pack function that the next three peices of data that follow are 1 unsigned short integer and 2 short integers and
that we want to pack (or put) them into the Tris as binary code, because that is the way QuArK needs them in order to work.

Notice how each piece of data is split up by a comma at the end, except for the last one, giving four data items per line.
The final three are the vert_index, U, and V values that are read in from the model file else ware in the code (see our work file).
They are other lists of data that have been attached (that's what the dot betweeNotice how each piece of data is split up by a comma at the end, except for the last one, giving four data items per line. The final three are the vert_index, U, and V values that are read in from the model file else ware in the code (see our work file). They are other lists of data that have been attached (that's what the dot betwean them does) to the md2 object which is created in another function in the file. Search and study the file to see where and how that is done. You shouldn't have a problem doing that now.

Also take a look at the load_textures and animate_md2 functions while you are there to see how those are written and handled as well.

n them does) to the md2 object which is created in another function in the file.
Search and study the file to see where and how that is done. You shouldn't have a problem doing that now.

Tris = ''
for i in xrange(0, md2.num_faces):
    Tris = Tris + struct.pack("Hhh", md2.faces[i].vertex_index[0], md2.tex_coords[md2.faces[i].texture_index[0]].u,
                                     md2.tex_coords[md2.faces[i].texture_index[0]].v)
    Tris = Tris + struct.pack("Hhh", md2.faces[i].vertex_index[1], md2.tex_coords[md2.faces[i].texture_index[1]].u,
                                     md2.tex_coords[md2.faces[i].texture_index[1]].v)
    Tris = Tris + struct.pack("Hhh", md2.faces[i].vertex_index[2], md2.tex_coords[md2.faces[i].texture_index[2]].u,
                                     md2.tex_coords[md2.faces[i].texture_index[2]].v)
Also take a look at the load_textures and animate_md2 functions while you are there to see how those are written and handled as well.
Once you have done that, continue on to our final step below. To write the exporter, basically it's just the reverse process.


 Creating a Component in the Code

cdunde - 05 Apr 2018   [ Top ] 

In one of our previous functions, import_md2_model, we had the data to create a component returned to that function. Here we will look at its code, which is shown below, to see exactly how that is written.

    # Now we can name our component that will be imported.
    Component = quarkx.newobj(name + ':mc')
    Component['skinsize'] = skinsize
    Component['Tris'] = Tris
    Component['show'] = chr(1)
    sdogroup = quarkx.newobj('SDO:sdo')
    Component.appenditem(sdogroup)
    Component.appenditem(skingroup)
    Component.appenditem(framesgroup)
A special note here :
If you were also importing Bones or BBoxes (bounding boxes) from the model this is the first of two places where that code COULD be added if there is only ONE component used for the model. If there is more then one then they would need to be added at the second location as a mater of code consistency. The additional line of code should be placed just before the Component lines above and can be copied from below just as it is shown.
    editor.Root.dictitems['Skeleton:bg'].appenditem(skeletongroup)
See the section below covering Bones Structure, Data within the Bone for the needed code to create individual bones for the skeletongroup before the line above is called.

Just under the above code in the QuArK\plugins\ie_md2_import.py file you will see this code.

    ### Use the 'ModelRoot' below to test opening the QuArK's Model Editor with,
    ### needs to be qualified with main menu item.
    ModelRoot = quarkx.newobj('Model:mr')


 About the Python Struct Module

cdunde - 05 Apr 2018   [ Top ] 

We'll only cover a very small portion of it here, but basically the Struct Module is made up of functions to handle binary code data, converting it into standard text format and back again. A link to the entire module is here and another good part to review is the chart at the top, here, which is also shown below.

pack( fmt, v1, v2,  ...)
       Return a string containing the values v1, v2, ... packed according to the given format.
       The arguments must match the values required by the format exactly.

unpack( fmt, string )
         Unpack the string (presumably packed by pack(fmt, ...)) according to the given format.
         The result is a tuple even if it contains exactly one item.
         The string must contain exactly the amount of data required by the format
         (len(string) must equal calcsize(fmt))

A format character may be preceded by an integral repeat count.
    For example, the format string '4h' means exactly the same as 'hhhh'.
Whitespace characters between formats are ignored; a count and its format must not contain whitespace though.

calcsize( fmt )
           Return the size of the struct (and hence of the string) corresponding to the given format.

For the "s" format character, the count is interpreted as the size of the string,
    not a repeat count like for the other format characters;
    for example, '10s' means a single 10-byte string, while '10c' means 10 characters.
Format characters have the following meaning; the conversion between C and Python values should be obvious given their types:
                      ("signed" means they can be positive + or negative - values, "unsigned" means they are only positive +)


 Adding Bones & Their Structure

cdunde - 05 Apr 2018   [ Top ] 
Some models have their own bone structure built into them for animation purposes. So those need to be imported\exported along with the other data.
This section covers how QuArK needs the data to work with its bone system. It would be a good idea to read about the Bones System
to get an understanding of how it works and its capabilities.
Creating a Bone :
Beside the description below, a good summery of the code to make a bone can be seen in the quarkpy\mdlutils.py file's def addbone function.
Basically the bone data is split up into two areas, that which is kept within the bone object itself when it is created
and that which is created by QuArK and the import file when the model is read into the editor.
This data is kept in a Python Dictionary List named the editor.ModelComponentList.
The data that is kept within the bone primarily is what is displayed on a Bones Specifics/ Args page, shown to the right here,
although it is also used throughout QuArK's internal code.
This data must be read in from the imported model file and added to the editor.Root.dictitems['Skeleton:bg'] when the component is created in the Python import file shown futher above.

If a model consist of multiple parts, or components, then the bones need to be created and added in a fashion that addresses this issue.
This is because the editor only has one set of bones, the Skeleton:bg which can be used, and\or added to, by more then one component.
In addition, each bone must have its own individual name to avoid errors from occurring in the editor by over writing other bones.
Data within the Bone   :
Data that must be added for a models imported bones consist of two groups, Base Items and Other Items.
     Base Items  : The minimum items to construct a bone, known as its dictspec items, are shown below and are stored in a Python Dictionary List.
With this type of list, the order of their arrangement does not matter because their key names, such as scale, are used to retrieve the data as it is needed.
All dictspec items are not shown in the editor's tree-view, where as all its dictitems, such as bones, components, frames and the like do show up.
{
'flags': (0,0,0,0,0,0),
'show': (1.0,),
'parent_name': 'NewBone1:bone',
'position': (-23.702332855224609, -4.4704371452331543, 2.0622565078735352),
'scale': (1.0,),
'bone_length': (0.0, 0.0, 0.0),
'component': 'h_head:mc',
'draw_offset': (0.0, 0.0, 0.0),
'_color': '\x00\xff\xff'
}

     Other Items  : If a bone has vertexes assigned to it already, these items must be added to the bone itself as an attribute.

new_bone.vtxlist = { 'mesh1:mc' : [0, 18, 34, 36, 7, 8] , 'mesh2:mc' : [0, 3, 45, 88] }
new_bone.vtx_pos = {} or { 'mesh1:mc' : [0, 18, 34] } # Sets the bones custom position by those vertexes.
One last attribute that must be created is the
new_bone.rotmatrix = quarkx.matrix((1, 0, 0), (0, 1, 0), (0, 0, 1))

The new_bone.vtx_pos are specific vertex indexes (of a single component) from their respective new_bone.vtxlist.
These are used to reposition the handle of any newly added bone from frame to frame by averaging those vertex positions and applying that to the bone handle.
Bones that are imported with a model have their positions stored for each frame in the ModelComponentList, covered a little further down.

     Sample Code  :

Because the values are read in from the model file being imported we can not give code here that you can just copy and paste But you should be able to get enough from the
following example to write your own code. Also you can see more by opening the QuArK\quarkpy\mdlutils.py file, use any text editor like WordPad and do a word search for
def addbone(. You will also find many other functions there in the # Skeleton & Bone functions area.

    skeletongroup = editor.Root.dictitems['Skeleton:bg']  # gets the bones group.
    bones = skeletongroup.findallsubitems("", ':bone')    # get all bones (because bones can have other bones inside them it MUST be called this way).
    name = "NewBone1" # Can be any string item.
    new_bone = quarkx.newobj(name + ":bone") # Creates the bone as a QuArK Internal Object.
    new_bone['show'] = (1.0,) # Sets the bone to be drawn (displayed) in the editor, 0.0, would hide the bone.
    new_bone['component'] = component.name # The component name that this handle is assigned to. # Can be any component.
    new_bone['parent_name'] = "None" # Or "parent bonename:bone" if attached to another bone.
    new_bone['position'] = (-23.702332855224609, -4.4704371452331543, 2.0622565078735352) # As a tuple as shown for x,y,z.
    from math import sqrt
    new_bone.rotmatrix = quarkx.matrix((sqrt(2)/2, -sqrt(2)/2, 0), (sqrt(2)/2, sqrt(2)/2, 0), (0, 0, 1)) # Sets the Default or other rotation matrix.
    new_bone['draw_offset'] = (0.0,0.0,0.0) # Or a value can be read in here if one exist, but as a tuple as shown for x,y,z.
    new_bone['scale'] = (1.0,) # Or a value can be read in here if one exist, but as a tuple as shown.
    new_bone['_color'] = MapColor("BoneHandles", SS_MODEL) # Default QuArK function call.
         (or)
    new_bone['_color'] = str(\x80\x00\x80) # A Hex value of a color converted to a string.
    new_bone['bone_length'] = (0.0,0.0,0.0) # If no other bones are attached to this one...
         (or as in most cases, needs to be calculated using another bone's position)
    startpoint = quarkx.vect(new_bone['position']))
    endpoint = quarkx.vect(attached_bone['position']
    new_bone['bone_length'] = ((startpoint - endpoint)*-1).tuple # *-1 must be used or things will come out backwards.

    vtxlist = {}
    list = []
    for vtx in vertexes: # "list" being assigned vertex indexes you read in (by component) from the model file, each vertex index must be an integer.
        list = list + [vtx] # This will build a single "list of vertexes", which is needed to be that way.
    vtxlist[components name + ":mc"] = list # Puts each component in a dictionary with that component's name as the key (NOTE the ":mc" added as its type).
    new_bone.vtxlist = vtxlist # Needs to be done this way to work right.

    skeletongroup.appenditem(new_bone) # Each bone must be appended (added) to the skeletongroup here.

Once again, the bone creation code must be added to the import python file, further above, where each component is created
or some other fashion if more then one component (mesh) exist.
A reversal of the bones code and editor.ModelComponentList use must also be added to its export file.

The best thing for bones is to look at their sections in both the plugins\ie_md5_import.py and plugins\ie_md5_export.py
files to really see, as an example, how the code should be written.

QuArK's ModelComponentList   :

Because the QuArK Model Editor has been setup to handle a variety of different model formats, and more of them in the future,
a means of storing different types and structures of data for them was needed. That is what this dictionary list was created for.
It was made as a dictionary list, (primarily) using the component's full name and type, for three reasons:

1) To make it easy to have access to a particular component's data.
2) To allow different items and structures for each component, allowing different model formats to be imported at the same time.
3) To provide a means for additional specialized data lists to be added as the editor is developed further.

If you open the QuArK\quarkpy\mdleditor.py file, use any text editor like WordPad and do a word search for ModelComponentList
you will find a description of everything that is stored in this list and the structure as how the data is stored in detail.
Below is an example of those data structures.

What's in ModelComponentList   :

The first three items ['bboxlist'], ['bonelist'] and ['tristodraw'] do not use a component's name as their
key because they are used for all components to store special data that can not be stored with the individual
bones or bboxes for components them selves because of code restrictions. This data is created directly from the model import file when it is read in.

The ['bboxlist'] is a dictionary list, its keys are the bbox names. If a bbox is associated with a bone then its name
matches that bone's name, for example upperleg:bone -> upperleg:p and the same applies if they are associated with a component.

Bboxes are polyhedrons just like in the map editor.
Bbox stands for bounding box which can also be used for a collision box depending on the model format's needs.

For bones, each bbox in the ['bboxlist'] contains another list of two sets of values, minimum x, y, z and maximum x, y, z values.
Below is a sample of what it looks like in the ModelComponentList:

editor.ModelComponentList['bboxlist']
    {
    'upperleg:p':
        {'size':
                [(-34,-60,-14), (16,-30,16)]
        }
    }
Their positions get set by the bones they are assigned to for any animation frame that exist. They can be placed in the tree-view
individually or in a BboxGroupFolder:bbg, which is usually done to keep them identified with one particular imported model.

For bboxes assigned to a component the same method above can also be used
and in addition they have two other methods available using different key names.
The second method uses ['vtx_list'] to create a list of vertexes for each bbox that will be used to surround them from frame to frame
The third method uses ['frames'] to create a list of two distinct minimum and maximum vectors, as described above, for each individual frame.

The ['bonelist'] is also a dictionary list and its keys are the bone names.
It is used to store each bones exact imported position and rotation matrix for each animation frame.
This is done in another dictionary list, for each bone, called ['frames']. The keys are the frame names.
It can also have a ['type'] to specify what type of model format it was originally imported as (currently not really used).
Below is a sample of what it looks like in the ModelComponentList:

editor.ModelComponentList['bonelist']
    {
    'lostsoul_lost_flame1:bone':
        {'frames':
            {'meshframe:mf':
                {
                'position': (2.48, -0.04, -1.79),
                'rotmatrix': ((0.91, 0.41, -1.57), (-9.01, -1.81, -1.0), (-0.41, 0.91, -1.28))
                }
            }
        }
        {'type': 'md5'}
    }

The ['tristodraw'] also does not use a component's name as its key because it is used for all components to store data
for drawing the correct triangles (faces) when a drag operation takes place. As shown below, its data is created from the:

editor.ModelVertexSelList
    [
        [92, <vect 211.72 197.40 -14.58>],
        [90, <vect 210.12 203.39 -21.97>],
        [91, <vect 211.92 209.79 -14.98>]
    ]
And a sample of what it looks like in the list is this:
editor.ModelComponentList['tristodraw']
    {
    'h_head:mc':
        {
         0: [35, 34, 21, 2, 1],
         1: [21, 20, 2, 0],
         2: [39, 35, 1, 0],
         3: [27, 25, 17,13, 12, 5, 4],
         ....
         90: [91, 92, 5, 3],
         91: [28, 92, 90, 3],
         92: [28, 26, 25, 91, 90]
        }
    }
The component's full names and types are used as keys within the 'tristodraw' list for its own mesh which is made up of individual vertexes.
Then each vertex index (number) is, in turn, used as a key with its own list of other vertex indexes needed to draw its triangles or faces.

All the other items in the editor.ModelComponentList do use the component's full name and type (:mc) as its primary key, for example Component1:mc.

The first item we'll cover for components is the 'bonevtxlist' which stores data for the Bones System (using the full bone name and type) for a component.
Its sub-keys are vertex indexes (from that component, as intigers), that are assigned to that bone
and the value (what goes with that key) is that bone's handle color as a hex value converted to a string stored under the key of color.
This dictionary list is used to draw each assigned vertex (of that component) in the bone's handle color that the vertex is assigned to.
We do it this way because not everything can be stored with the bones as we would like, the Delphi code is not setup to allow it.

editor.ModelComponentList[Component1:mc]['bonevtxlist']
    {
    'NewBone2:bone':
        {
         18: {'color': '\xa4\x00\xa4'},
         34: {'color': '\xa4\x00\xa4'},
         ....
         90: {'color': '\x00\x00\xff'}
        }
    'NewBone3:bone':
        {
         92: {'color': '\x00\x00\xff'},
         90: {'color': '\x00\x00\xff'},
         ....
         91: {'color': '\x00\x00\xff'}
        }
    }

The next item we'll cover for components is the 'colorvtxlist' which stores a component's data for its Vertex Coloring System, if one exist.
Here again, its sub-keys are vertex indexes (from that component, as integers) but this time the value for each vertex key
is another dictionary list where the key is vtx_color and again a color as a hex value converted to a string.
But this color is used independently from the bone handle color described above and is for this particular system only.

editor.ModelComponentList[Component1:mc]['colorvtxlist']
    {
     39: {'vtx_color': '\xbf\xbf\xbf\x00'},
     38: {'vtx_color': '\xbf\xbf\xbf\x00'},
     49: {'vtx_color': '\xbf\xbf\xbf\x00'},
     ....
     51: {'vtx_color': '\xbf\xbf\xbf\x00'},
     57: {'vtx_color': '\xbf\xbf\xbf\x00'},
     ....
    }
The final item we'll cover for components is the 'weightvtxlist' which stores a component's data for its Vertex Weights System, if one exist.
Once more, its sub-keys are vertex indexes (from that component, as integers) yet this time the value for each vertex key
are a number of different items, all strictly pertaining to this particular system. You will notice that a single vertex
can have more then one full bone name as its sub-keys. That is because a vertex can be shared between more then one bone.
And then the bone sub-keys have sub-keys values of their own for each item needed in that system.
editor.ModelComponentList[Component1:mc]['weightvtxlist']
    {
     18: {'NewBone2:bone': {'weight_value': 0.8, 'color': 'x00\xb7'}}
         {'NewBone3:bone': {'weight_value': 0.2, 'color': 'x00\xa4'}}
    }
As stated above, more items can be added to these component dictionary lists as needed in the future without disrupting what is already there
as well as entirely new component dictionary list making the editor very flexible and expandable for future development.


 Adding Specifics Page & Menu Items

cdunde - 05 Apr 2018   [ Top ] 
Using existing icons  :
Button icons are .bmp files in the QuArK images folder which are loaded into QuArK when it starts up using a Python all in the quarkpy\qeditor.py file such as this one for example:
    ico_dict['ico_maped'] = LoadIconSet1("maped", 1.0)
The 1.0 tells it to use the file with the -1 in its name when a button is clicked.
This puts those images, which are maped-0.bmp and maped-1.bmp, into a dictionary list ico_dict so that entire list of objects will be removed from memory when QuArK is closed,
avoiding a memory leak. Do a word search using the above example line of code to see all of the image dictionary list that are available to use and trace those back to the actual
images in the images folder.
Each .bmp file is like a bar of icon images. The -0.bmp is used when a button is not active and the -1.bmp is used when a button is active.
Each icon image is counted from left to right starting with the digit zero to identify it.
An example on how to call one would be like this:(shortened)
    from quarkpy.qeditor import ico_dict # Get the dictionary list of all icon image files available.
    import quarkpy.qtoolbar              # Get the toolbar functions to make the button with.
    ico_maped=ico_dict['ico_maped']      # Just to shorten our call later.
    icon_btns = {}                       # Setup our button list, as a dictionary list, to return at the end.
                                         # Make our first button, below, to go into the above list.
    vtxcolorbtn = quarkpy.qtoolbar.button(colorclick, "Color UV Vertex mode||When active, puts the editor\nvertex selection into this mode.|intro.modeleditor.dataforms.html", ico_maped, 10)
    icon_btns['color'] = vtxcolorbtn     # Put our button in the above list to return.
You can see at the very end of the next to the last line of code above that we are using icon 10, the 11th one, from the maped-0.bmp and maped-1.bmp image files.
The first argument of the quarkpy.qtoolbar.button call is colorclick. This is the name of the function to call when the button is activated (or clicked) that you write, define, in your import file.
This name can be anything you wish and the actual function can do just about anything you want it to do in the editor,
such as storing vertex and\or triangle indexes and other needed data within the component that will be needed for exporting later.
The second argument does a couple of things:
  • 1) Gives a brief hint, of what the button does, followed by two bars || then
  • 2) a brief description of how to use the function and\or what it does in more detail.
         This shows up when someone presses their F1 key to get a help box window.
         Notice the \n in editor\nvertex which causes a new line. \n\n can be use for a space between lines and so on.
  • 3) Next we have a single bar | that allows us to provide a link to the InfoBase docs, with a button, to give more help.
  • 4) It gives our icon list item to use and which icon from that list.
For each button you want to add simply do it like above, with its own name like icon_btn2, and add it to the list as well.
Making your own icon  :
If you can not find an icon that gives a distinct appearance of what a button is for, then you can make your own.
To keep things simple and consistent, that new icon should be added at the end of the mdlskv-0.bmp, mdlskv-1.bmp and mdlskv-2.bmp files and submitted,
along with your import and export files, for others to draw from. The first five spaces, including blanks, are reserved for the editor's Skin-view and for future use.
Notice also that the image in mdled-1.bmp and mdlskv-2.bmp are slightly brighter or even a different set of colors. That is so when the button is active it will be more noticeable then the others.
Not all buttons will need to remain active but all three files still need to have the new icon to keep things straight.
The reason there are three related image files in this set is because the mdlskv-1.bmp is used as a pass over highlighted look when used with the proper button function type.
There are also other kinds of buttons that you can use. See the quarkpy\qtoolbar file or other mdl files for those.
Menu items  :
These types of items apply to the RMB menus. Using this type of method, complete functions can be written within the import file to perform special tasks required by that type of model format.
The results can then be stored within the model component and used for its exporting. The only requirement is that of using explicit function names to add items to the RMB menus.
One good example to look at is the ie_md5_import.py file. Some of these function names are given below along with a brief description of what they are for.
Others may be added later, so if you see something special used, it is a good idea to look at the code for that particular model import file for how it was done.
def dataformname(o):
    "Returns the data form for this type of object 'o' to use for the Specific/Args page."

def dataforminput(o):
    "Returns the default settings or input data for this type of object 'o' to use for the Specific/Args page."

def newmenuitems(editor, extra):
    "To add new menu items to other RMB menus. 'extra' is the current list of RMB menu items."

def bonemodeclick(btn_menu_item):
    "To add new menu mode items to this RMB menu, use editor.layout.buttons['bonemode'] to get the base menu."

def macro_opentexteditor(btn):
    "To open an object, such as a shader text object or skin texture object, in an outside editor."

 Bones & Their Matrixes

cdunde - 05 Apr 2018   [ Top ] 

The under lying engine of the bones are their Matrixes which are primarily used in their rotation movements but can also be used for their linier movements as well.

This is exactly why they are stored in the editor.ModelComponentList when they are read in from an imported model file that has bones. Without these, the model could not be properly exported. But simply having their matrixes is not enough, they also need to be inverted to give the opposite values that the importer brings in, because that is what happens when they are used to import them, the bone handle's positions are changed around by the matrixes for the editor's uses. So that needs to be reversed on export.

Unfortunately, I am not a Mathematician, but I was lucky enough to stumble across a page on Matrices and Determinants. After many hours of extreme hair pulling, I was able to decipher what it was trying to explain. And that is why I made up the Layman's Chart on Inverse Matrix below, to help others to understand how it works and how to use it.

There's not a lot to explain here, hopefully the colored steps will do the job much better.

Just one basic point. For a 3x3 matrix, each row has three values stored in it as a small list, and those are in position 0, 1 and 2 of their list. Three of this lists make up the matrix.

It also has three columns, which starts from the left hand side, of all the values in that position running from top to bottom.

One very important thing to remember in working to invert a matrix is that you do not want to change to original values as you invert the matrix. You need to make a copy of it and change the copy. Other wise this method will not work and you'll wind up with a mess on your hands.

To see this applied in Python code take a look at the plugins\ie_md5_export.py file's def inverse_matrix function near the top. Then use the name of that function for a word search to backtrack it in the file to see the preparation of self.bones for it to work with.

This function creates a new list of these inverted matrixes, one for each bone, called self.bone_matrix_list and that list is called on later in the file for their use. So use that list name in another word search to see how they are used to define ( = ) another variable and just keep following those names to the end results.

In the last step of the chart it shows 1/6.0 as though it is multiplying the matrix by that value. I have no idea if it is or not, or what that is used for. I did not use it in the md5 export file, but it works great without it.



Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/

 [ Prev - Top - Next ]