Reverse-engineered specification of the tool library export format used by
Makera Studio (CAM software for the Makera Carvera CNC). Verified against
files produced by Makera Studio in September 2026 (library version = 1).
A reference parser/writer in Python (tlslib.py) round-trips real exports
byte-for-byte.
Status: everything below has been confirmed by probe exports (changing a value in the UI and diffing the file) except where marked unverified.
The file is not text. It is a Qt QDataStream
serialization written in Qt's default (big-endian) byte order. There is no
magic number, no header signature, and no length prefix for the whole file.
Opening it as UTF‑16 in a text editor shows the strings because QString
is stored as UTF‑16BE, but the bytes between strings are binary integers,
doubles and booleans, not text.
| Type | Encoding |
|---|---|
int32 |
4 bytes, big-endian, signed |
double |
8 bytes, big-endian IEEE‑754 binary64 |
bool |
1 byte, 0x00 or 0x01 |
QString |
uint32 byte length N, then N bytes of UTF‑16BE (no BOM, no terminator). A null string is the single value 0xFFFFFFFF with no payload. An empty string is 0x00000000. |
All strings observed are ASCII, so N is always 2 × character count.
Records are packed with no alignment padding. Because a bool is one
byte, offsets after the first per-material record are odd, which is why hex
dumps look "shifted" halfway through the file.
Library
int32 version = 1
QString id (null in all observed files)
QString name e.g. "Custom Tools"
QString description (null in all observed files)
int32 tool_count
Tool[tool_count]
int32 material_count = 15 in stock installs
Material[material_count]
<EOF>
The file ends immediately after the last Material; a correct parser
consumes exactly the file length.
Tool
QString uuid "{xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx}" (QUuid::toString, braces, random v4)
QString name user-visible tool name
int32 tool_number "Tool Number" field in the UI
int32 tool_type enum, see §3.1
double[13] geometry see §3.2
int32 metal Non-Metal / Metal radio: 0 = Non-Metal, 1 = Metal
int32 material_count number of ToolMaterial records that follow (0..N)
ToolMaterial[material_count]
metal was verified by importing a tool with metal = 0, which opened with
Non-Metal selected.
material_count may be less than the library's material count, including
zero. The app's own exports always write one record per library material,
but a file with two records (or none) imports correctly and the Tool
Properties table lists only the materials present.
| value | UI label | default name in stock library |
|---|---|---|
| 0 | Ball Nose | ballnose |
| 1 | Flat End | flatend |
| 2 | V‑Bit | vbit |
| 3 | Engraving | engraving |
| 4 | Bull Nose | bullnose |
| 5 | Drill | drill |
| 6 | Thread | thread |
| 7 | Tapered Ball Nose | taperedballnose |
The name string is free text; only tool_type determines the shape.
Every tool stores all 13 values regardless of type. The UI simply hides the ones that don't apply and leaves them at their defaults, so a writer should always emit the defaults for unused slots.
| idx | field | UI label | default | used by |
|---|---|---|---|---|
| 0 | DS shank (handle) diameter |
Handle Diameter (DS) | 3.175 | all |
| 1 | unknown | — (never shown) | 12.0 | — |
| 2 | LS shoulder length |
Shoulder Length (LS) | 12.0 | all |
| 3 | LC flute length |
Flute Length (LC) | 5.0 | all |
| 4 | DC cutting diameter |
Diameter (DC) | 3.175 | all |
| 5 | D1 tip diameter |
Tip Diameter (D1) | see note | engraving |
| 6 | CR corner radius |
Corner Radius (CR) | 1.0 | bullnose, taperedballnose |
| 7 | TI included angle |
Angle (TI) | 60.0 | vbit |
| 8 | AN half angle |
Half Angle (AN) | 30.0 | engraving, drill |
| 9 | thread specification | Thread Specification | 0.0 | thread (stock default 2.5) |
| 10 | PI thread pitch |
Pitch (PI) | 0.0 | thread (stock default 0.45) |
| 11 | TA thread angle |
Thread Angle (TA) | 0.0 | thread |
| 12 | thread drill diameter | Drill Diameter | 0.0 | thread (stock default 2.05) |
D1 note: for flatend and ballnose the app writes D1 = DC (it
follows DC when DC is edited). For every other type the stock default is
0.2.
Index 1: always 12.0 in every observed file, unchanged by editing any
visible field. Probably a total/overall length that the current UI does not
expose. Leave it at 12.0.
Verification of the mapping: setting DS=6.35, LS=20, LC=8, DC=3.0 on a flat-end tool changed exactly indices 0, 2, 3, 4 (and 5, which followed DC).
One record per material in the library's material list (§5), in the same order the app keeps them (which is not the order of §5). A material the user has not enabled for the tool is still present, with all numeric fields zero.
ToolMaterial
QString uuid "{...}" random v4, braces
QString material_id matches Material.id (§5)
QString material_name matches Material.name
int32 spindle_rpm Spindle Speed (r.p.m)
int32 feed_rate Feed Rate (mm/min)
int32 plunge_rate Plunge Rate (mm/min)
double step_over_pct Step Over (%)
double step_over_mm Step Over in mm — the greyed-out derived field
double step_down_mm Step Down (mm)
int32 coolant Coolant Category: 0 = none, 1 = Air Cooling (any other value shows a blank dropdown)
bool enabled the material's Enable checkbox (see note)
QString note null in all observed records
Total fixed-size payload between material_name and note is 41 bytes
(3×4 + 3×8 + 4 + 1).
step_over_mm is stored, not recomputed on load: after DC was changed
from 3.175 to 3.0 the file still held the old 0.064. The app writes
round(DC × step_over_pct / 100, 3) (3.175 × 2 % → 0.064, i.e. it stores the
3‑decimal display value). A writer should compute and store this itself.
enabled: on import a material is treated as enabled if this byte is
1 or any of its numeric parameters is non-zero. Both were verified:
a record with enabled = 1 and all-zero values imports as enabled, and a
record with enabled = 0 and non-zero feeds also imports as enabled (with
the Enable checkbox ticked). Makera Studio's own exports write 0 here even
for enabled materials, relying on the non-zero rule. Writers should set
enabled = 1 on every active material anyway; it costs nothing and does not
depend on the derived rule.
Material
QString id UUID v7 string, no braces, e.g. "019bfae0-a419-75bf-9d86-d8d4ede8560d"
QString id_again identical to id in all observed records (*purpose unknown*)
QString category_id UUID v7 string, shared by materials in the same UI group
QString name
QString description (null in all observed records)
bool flag 1 in all observed records (*meaning unknown*)
Stock Makera Studio materials and their UI groups (grouping inferred from
shared category_id):
| group | materials |
|---|---|
| Wood | Hardwood, Softwood |
| Plastic | Acrylic, ABS, Polycarbonate, Delrin |
| Aluminum Alloys | 6061 Aluminum, 7075 Aluminum |
| Copper Alloys | Brass, Copper |
| PCB | PCB |
| Composites | Bakelite, Carbon Fiber, Synthetic Stone, Epoxy Tooling |
The material and category IDs are fixed per Makera Studio install/version.
When generating a library, copy the Material list (and the IDs used in
ToolMaterial.material_id) from a real export rather than inventing them.
Importing a .tlslibrary in Makera Studio creates a new nested group named
after Library.name under the tool library. It does not merge into an
existing group, so give the library a meaningful name.
UUIDs are not de-duplicated on import. The same file imported twice
produces two groups, each with the full set of tools, even though every
Tool.uuid is identical between them. Generated files should still use
fresh random v4 UUIDs for Tool.uuid and ToolMaterial.uuid so that tools
remain distinguishable if the app ever keys on them, but nothing observed
requires it. Only ToolMaterial.material_id must match the app's existing
material IDs.
One early import of a valid file produced an empty group; the same file later imported correctly and the failure could not be reproduced.
Zero-parameter tools import fine. A tool whose ToolMaterial records
are all zero imports as a tool with no enabled materials. Default geometry
(DS == DC, D1 == DC) is also accepted, as are tools with fewer than the full
set of ToolMaterial records, or none.
Material.id / category_id are references to materials the app already
knows, not definitions: a file with zero tools and a full Material list
imports as an empty group, and the app itself exports an empty library as a
4-byte file with no material list at all. There is no evidence that new
materials can be created through this file.
Confirmed working imports: a generated file with one fresh-UUID flatend
tool and feeds on two of fifteen materials (parameters appeared on exactly
those two); a stock export re-generated by the reference writer; and
single-field variants of a real export (feeds zeroed; geometry reset).
Minimal recipe:
- Parse an existing non-empty export to obtain the 15
Materialrecords and their IDs (an empty export contains no material list). - For each tool to add, emit a
Toolwith a fresh v4 UUID, the 13 geometry doubles (defaults for unused slots,D1 = DCfor flat/ball),metal = 1, and oneToolMaterialper material you want active, withenabled = 1and computedstep_over_mm. Emitting zero/disabled records for the other materials is optional (the app does it; the importer doesn't require it). - Emit the unchanged
Materiallist. - Give
Library.namea distinct value; it becomes the group name on import. - Write big-endian, no padding,
QStringas length-prefixed UTF‑16BE.
A writer is correct if write(parse(f)) == f for a real export.
00 00 00 01 int32 version = 1
FF FF FF FF QString id = null
00 00 00 18 00 43 00 75 ... QString name: 24 bytes → "Custom Tools"
FF FF FF FF QString description = null
00 00 00 08 int32 tool_count = 8
00 00 00 4C 00 7B 00 30 ... QString tool.uuid: 76 bytes → "{07168478-...}"
and one ToolMaterial payload with Hardwood set to spindle 3, feed 4,
plunge 5, step-down 1.0, step-over 2 %, air cooling, on a 3.175 mm tool:
00 00 00 03 spindle_rpm = 3
00 00 00 04 feed_rate = 4
00 00 00 05 plunge_rate = 5
40 00 00 00 00 00 00 00 step_over_pct = 2.0
3F B0 62 4D D2 F1 A9 FC step_over_mm = 0.064
3F F0 00 00 00 00 00 00 step_down_mm = 1.0
00 00 00 01 coolant = 1 (Air Cooling)
00 enabled = 0 (see §4 note)
FF FF FF FF note = null
geometry[1](always 12.0): meaning. Setting it to 99 changed nothing visible in the tool editor or preview.Material.flag,Material.id_again: meaning.- Whether
ToolMaterial.enabledhas any effect beyond the import-time rule in §4 (the app's exporter always writes 0). - Whether Makera Studio tolerates a
Toolwith fewerToolMaterialrecords than the material list has (untested — safest to always emit all). - Whether a
Materialrecord with an unknownidcreates a new material in the app, or is ignored. - Behaviour with non-ASCII tool names (should be fine given UTF‑16 storage, untested).
Contributions welcome — a probe export plus a one-line description of what was changed is enough to settle any of these.