Blender addon to import and export models in .m3
format, which is used in Blizzard's games: StarCraft II and Heroes of the Storm.
Originally made by: Florian Köberle (aka "println").
- Download:
- Most recent version
- Older versions, no longer maintained:
- Install addon using one of the methods:
- Option A (automatic):
- From Blender menu, navigate to
Edit -> Preferences
. ThenAdd-ons
tab. - Click on the
Install
button and point it to downloaded zipfile.
- From Blender menu, navigate to
- Option B (manual):
- Extract zipfile to
m3addon
directory which should be placed in following location: - Windows:
%APPDATA%\Blender Foundation\Blender\2.80\scripts\addons
- Linux:
~/.config/blender/2.80/scripts/addons
- (If addon won't appear as a choice in the Blender's preferences, click
Refresh
button or restart the application.)
- Extract zipfile to
- Option A (automatic):
- Activate the addon in Blender preferences: toggle on the checkbox
[✓]
form3addon
entry in Add-ons tab.
- Bug tracker: Encountered a bug? Feel free to report, but please include any relevant details. For general import/export related bugs, provide a name of the model. Assuming it comes from either StarCraft II or Heroes of the Storm, the filename is enough - we can obtain the file(s) from local installation directory to reproduce the issue.
- SC2Mapster Discord Server: Gathers a small community of people familiar with this addon (channel
#artisttavern
). That's the place to go for some general guidance and support.
The Python scripts can be used as a Blender addon.
Currently, the following content can be exported and imported:
- Animations
- Meshes with up to 4 UV layers
- The following known M3 materials:
- standard materials
- displacement materials
- composite materials
- terrain materials
- volume materials
- creep materials
- volume noise materials
- splat terrain bake materials
- M3 particle systems
- M3 forces
- M3 attachment points and volumes
- M3 cameras
- M3 lights
- M3 rigid bodies
- M3 physics joints
- M3 projections
- M3 ribbons
- M3 billboard behaviors
- M3 turret behaviors
- M3 inverse kinematic chains
The script m3ToXml.py
can also be used to convert an m3 file into an XML file. It
takes an m3 file as an argument and prints the XML on the command line.
The script xmlToM3.py
can convert the XML files exported by m3ToXml.py
back into an m3 file.
The file structures.xml gets used by the m3.py
library to parse the m3 files.
Modifying this XML file will have an impact on the above scripts and the Blender addon.
The Blender addon adds panels to the scene tab of the properties editor.
To create a particle system and preview it in the Starcraft 2 editor you can perform the following steps:
- Add an animation sequence by clicking on
+
in theM3 Animation Sequences
panel - Add a material by clicking on
+
in theM3 Materials
panel - Select a diffuse image:
- Select
Diffuse
in theM3 Materials Layer
panel - Specify an image path like
Assets/Textures/Glow_Blue2.dds
in theM3 Materials Layer
panel
- Select
- Add a particle system by clicking on
+
in theM3 Particle Systems
panel - Validate that your particle system has a valid material selected
- Specify an absolute file path in the
M3 Quick Export
panel - Click on
Export As M3
in theM3 Quick Export
panels - Open the previewer in the Starcraft 2 editor by using the menu
Window/Previewer
- Use the previewer menu "File/Add File" to preview the exported model in the SC2 Editor
- You can right click on UI elements to view the source code which displays that element.
- File/Save User Settings can be used to determine the default state of Blender.
- You can save your export path this way!
- You can make yourself a default view that shows SC2 properties panels where you want them
- The
m3.py
file is a python library for reading and writing m3 files. It uses the structures.xml file to do so. - The file structures.xml specifies how the script should parse and export an m3 file
- The importing of m3 files works like this:
- The method loadModel of the
m3.py
file gets called to create a python data structure of the m3 file content. - This data structure gets then used to create corresponding Blender data structures
- The method loadModel of the
- The exporting works the other way round:
- The data structures of Blender get used to create
m3.py
data structures that represent an m3 file. - The method saveAndInvalidateModel of the
m3.py
file gets used to convert the latter data structure into an m3 file.
- The data structures of Blender get used to create
The m3 file format is a list of sections. Each section contains an array of a certain structure in a certain version.
The first section of an m3 file contains always a single structure of type MD34
in version 11. It is defined at the bottom of the structures.xml
file:
<structure name="MD34" version="11" size="24">
<description>Header of a M3 file: Can be found at the start of the file.</description>
<versions>
<version number="11" size="24" />
</versions>
<fields>
<field name="tag" type="tag" />
<field name="indexOffset" type="uint32"/>
<field name="indexSize" type="uint32" />
<field name="model" type="Reference" refTo="MODL" />
</fields>
</structure>
Structures may reference other sections via a data structure called Reference (or SmallReference in some exceptions).
The MD34
structure for example has a field called model, that is referencing a MODL
structure within another section. To which structure type a reference is pointing is indicated by the refTo field.
References typically do not however reference a single structure, but the whole section and thus an array of structures. Thus theoretically a MD34
structure could reference multiple MODL
structures but that has never been observed in a valid m3 file.
The m3.py
file requires all structures to be first defined in the structures.xml
file before they get used/referenced by another structure. For this reason, the structure MD34
is defined at the bottom.
An m3 file contains also an index/overview about those sections it contains, which can be found at the location specified by indexOffset and indexSize. If you want to get a list of the sections in an m3 file programmatic wise you can use the m3.py
method loadSections(filename)
.
However, it is usually easier to just work with a tree-like representation of the MODL
structure in which all references to structure arrays have been replaced by a list of the section that got referenced. This is possible via the m3.py
python function loadModel(filename)
.
A structure definition defines a structure for all versions that exist of it:
For the creep material for example (structure name CREP
) 2 versions exist that have different sizes. In the structures.xml
file there is however just a single structure definition:
<structure name="CREP">
<description>Creep Material</description>
<versions>
<version number="0" size="24" />
<version number="1" size="28" />
</versions>
<fields>
<field name="name" type="Reference" refTo="CHAR" />
<field name="creepLayer" type="Reference" refTo="LAYR" />
<field name="unknownda1b4eb3" size="4" expected-value="0x00000000" since-version="1" />
</fields>
</structure>
The <versions>
block contains a <verson>
element for all known versions of that structure. For each structure, the
size needs to be known and defined in the <version>
element. When an m3 file contains a version of a structure that is not yet known, an exception will be thrown and information about the structure will be logged that contains also a guess on the size of the structure.
A newer version of a structure might have additional fields. The attribute since-version
can be used to indicate that a field exists since a certain version. The attribute till-version
can be used to indicate that a field exists only till a certain version of the structure.
The m3.py
file checks that the defined fields have indeed the sizes specified in the <version>
elements. So when you add a new version you probably also need to find out what new fields got added and which fields stayed the same.
A field needs either to have a size or type attribute. The type attribute can be one of the following primitive types:
- uint32: a 32 bit unsigned integer
- int32: a 32 bit signed integer
- int16: a 16 bit signed integer
- uint8: an 8 bit unsigned integer
- uint8: an 8 bit signed integer
- float: a classical floating point type that fits in 32 bit
- tag: Up to 4 characters that are used to store structure names
- fixed8: a fixed point value that gets stored in 8 bits
In addition to that, all structures that got defined above the structure in the structures.xml
file can be also be used as type. However, a Version suffix with V + version number needs to be added to the structure name. e.g. VEC3V0 to get version 0 of the structure VEC3.
Exception: XYZ_V4.unknown0 has value 42 instead of the expected value int(0)
In the structures.xml
file, it's configured what structures exist, what fields those structures have, and what their default or expected value is. The exceptions mean that the field "unknown0" of the structure XYZ_
has been configured in the structures.xml
file to be 0, but it was actually 42.
For each structure exists an XML element in the structures.xml
file. Just search for the structure name (XYZ_
in the example) to find it. In the structure xml element there are field elements.
To fix the given error message we would search in the structure element for the field with the name attribute unknown0
and would replace the attribute expected-value="0"
with default-value="0"
.
Exception: There were 1 unknown sections
The error message means that the m3 file contained a structure that it is unknown to the script since it has not been defined in the structures.xml
file.
You can fix the error message by defining the unknown section. To do that have a look at the log, it will contain a message like this:
ERROR: Unknown section at offset 436124 with tag=XYZ_ version=1 repetitions=2 sectionLengthInBytes=32 guessedUnusedSectionBytes=4 guessedBytesPerEntry=14.0
The error message means that it found a section in the m3 file that contains two (repetitions=2) entries of type XYZ_. The script guesses that 4 bytes are unused and knowns that the section is 32 bytes long. So it calculates 2*X-4=32
where X is the number of guessed bytes per entry.
The result of this calculation is printed at the end "guessedBytesPerEntry=14.0". So the script guesses that version 1 of the structure XYZ_
is 14 bytes long.
To fix this error you would have to define the structure XYZ_
in version 1 in the structures.xml
file. See the section about the structures.xml
file to learn about how to do that.
Field ABCDV7.xyz can be marked as a reference pointing to XYZ_V1
:
To fix this example error message, we would search in the structures.xml
file for a structure called "ABCD" with the attribute version="7".
It will contain a xml element field with the attribute name="xyz". To this field we would add an attribute refTo="XYZ_V1".
Exception: Unable to load all data: There were 1 unreferenced sections. View log for details
When this error occurs, you will find in the log a message like this:
WARNING: XYZ_V1 (2 repetitions) got 0 times referenced
Every section in an m3 file gets usually referenced exactly 1 time(except for the header). The error message means that there is a section that contains 2 structures of type XYZ_ in version 1, but which got not referenced from anywhere. Most likely there is actually a reference to this section, but it hasn't been configured as such in the structures.xml
file.
If you are lucky, then there will be exactly 1 line below the former warning which looks like this:
-> Found a reference at offset 56 in a section of type ABCDV7
To fix the error message we need to change the structure definition of ABCD in version 7 to contain a field definition like this:
<field name="xyzData" type="Reference" refTo="XYZ_V1" />