CRUSADE
DOCS

Crusade file format (for a vault, PDM or scripts)

This is what is inside a Crusade part, assembly or drawing file, written for people building a vault, a PDM system, a converter or a script that reads or changes Crusade files. It describes Crusade 0.0.4 (October 2026).

How settled each part is. Every section is marked:

  • Stable: in the files since the first releases (0.0.1–0.0.3). It may gain fields, but what's here keeps its meaning, and files from those versions still open.
  • New: added in October 2026 (configuration tables, in-context design, patterns, several sheets, and so on). It works and is saved like everything else, but its shape may still change in the next versions. Crusade will always read the forms it wrote before; a script that writes these parts may need updating.

The simplest safe way to change a file from outside is the command-line tool at the end of this page: it changes properties and revisions, records the change in the file's own history and never leaves a half-written file.

The container (stable)

TypeExtension (old one, still opened)Main JSON entryformat
Part.cprt (.kcad)document.json"kcad-document"
Assembly.casm (.kasm)assembly.json"kcad-assembly"
Drawing.cdwg (.kdrw)drawing.json"kcad-drawing"
  • Every file is a zip. Identify it by its content, not its extension: the main JSON entry and its format field. (The format names start with kcad, from before the app was called Crusade; they stay that way so every file saved so far keeps working.)
  • Files are written atomically: a temporary .<name>.<random>.tmp in the same folder, then renamed over the old file. A reader never sees half a file. Ignore those temporary files.
  • JSON is UTF-8. Geometry is always in millimeters and angles in degrees, whatever units the file shows; the file's units only change what is shown and typed. (One exception: an assembly component's rotation is in radians.)

What a zip can hold:

document.json | assembly.json | drawing.json    the current state (the "recipe")
shapes/F3.brep                                  parts: an imported body, or a frozen Insert Part (OCCT BREP, text)
shapes/ctx-C2.brep                              parts: another part's shape for in-context design (new; below)
pictures/F5.img                                 parts: a Sketch Picture's image (PNG or JPEG bytes)
results/index.json, results/<n>.brep            stored results: assemblies, drawings, Share copies (below)
results/part/index.json, results/part/<n>.bin   parts: the stored model (below)
history/index.json                              versions and revisions (below)
history/v000012.json                            the main JSON of version 12, exactly as it was saved
history/blobs/<sha1>.<ext>                      the other entries of versions, stored once

Only the main JSON, shapes/ and pictures/ make up the document. results/ can be thrown away (Crusade rebuilds it), and history/ is the file's own record of earlier versions.

Versions, older and newer Crusades

Each main JSON carries:

KeyMeaning
versionThe file's format version: 1, or 2 when the file uses something an older Crusade would show or build wrong (below). Missing means 1.
saved_byThe Crusade that saved it, e.g. "0.0.4". Written since 0.0.3; older files don't have it.
appParts only: "Crusade". Information only.

When version 2 is written (new):

  • Part: a feature uses another part's shape (in-context design).
  • Assembly: it has configurations besides Default, or a suppressed or flexible component.
  • Drawing: it has more than one sheet, or a sheet with a portrait, custom or newer size (A0, ANSI D, ANSI E).

Everything else, new fields included, stays version 1.

Opening a file:

  • A newer Crusade's file is refused. If saved_by is newer than the running Crusade, or version is above what it reads (2 today), the file doesn't open and Crusade says to update. So nothing in it is shown wrong or lost when saved again. **A script must never write a saved_by newer than the Crusade that will open the file.**
  • Older files open as they are. There is no upgrade step: missing fields get their defaults, and old forms are read as before.
  • Fields Crusade doesn't know (for example, ones a script added) are left out when the file is read, and are gone after the next save. Keep your own data in custom properties instead.
  • **A part feature of an unknown kind** stops the part from opening.
  • Going back to an older Crusade. The first time a newer Crusade saves a file, the file as the older one left it is kept in its history (kind older). An older Crusade that can't open the file offers to open that version as a separate copy.

Parts insert other parts, assemblies use parts and subassemblies, and drawings show parts and assemblies. Paths are always saved with forward slashes, so a file saved on Windows opens on Linux and the other way round.

Assemblies and drawings (components, drawing views, tables) save two paths:

KeyHolds
sourceThe full path: /home/you/Projects/Press/Base.cprt or C:/Users/you/Projects/Press/Base.cprt.
relThe path relative to the file, written only when the target is in the file's folder, below it, or at most 2 folders up: "Base.cprt", "Sub/Base.cprt", "../Fasteners/M5.cprt". It is left out for the fastener library, another drive, or anything further away. Files saved before October 19 have no rel.

Parts (Insert Part, in-context links) save only source. It is relative (forward slashes) when the target is in the part's folder or below it, and the full path otherwise.

How Crusade finds a linked file, in order:

  1. rel, if that file exists.
  2. source as saved (a relative one is taken from the file's folder).
  3. A file of the same name in the file's own folder.
  4. The fastener library: any path containing /Crusade/Fasteners/ (or the old /KCAD/Fasteners/) is looked for under this computer's Documents/Crusade/Fasteners/.
  5. A moved project: the last folders of the saved path are looked for under the file's folder and up to 3 folders above it.

For a vault: prefer rel and treat source as a hint. A STEP assembly import builds in a hidden folder (.<name>.importing-<n>/) and renames it when done, so its source paths can point at a folder that's gone while rel is right. A vault that moves or renames files should rewrite both. Crusade's own Projects browser does: it rewrites source and rel, Insert Part source, variable-table links and in-context links in every file that uses the moved one, and records a version noted "Links updated".

Variable tables save path (the full path) and rel (relative to the file, however many folders up), both with forward slashes. rel is tried first, then path and the same steps 3–5 as above. If the table isn't found, the file still rebuilds with the values cached in it. Files saved before October 21 may have backslashes in these paths (saved on Windows, or moved there with the Projects browser); Crusade reads either kind, and its next save writes forward slashes.

Parts (document.json)

Top-level keys

KeyTypeMeaningMissing meansStatus
format, version, saved_by, appsee abovestable
unitsobjectunits shown and typed: length (mm cm m in ft), angle (deg rad), mass (g kg lb oz), density (g/cm3 kg/m3 lb/in3 lb/ft3), each with <name>_decimalsmillimeters and gramsstable
featuresarraythe feature tree, in order (below)requiredstable
rollbackint or nullhow many features are active (the rollback bar); null = allnullstable
materialstringmaterial name; "" = none""stable
finishstring"None", "Black Oxide", "Anodized (Type II)", "Powder Coat", "Paint", …"None"stable
finish_color[r,g,b] or nullcolor of an anodize or paint finish, 0–1nullstable
appearance[r,g,b] or nullthe part's color, 0–1; null = from material and finishnullstable
face_colorsarray[{"face": face signature, "rgb": [r,g,b]}][]stable
defaultsobjectthis file's Materials & Finishes overrides{}stable
variablesobject{name: expression} (below){}stable
variable_tablesarraylinked variable tables (below)[]stable
configurations, active_configobject, stringconfigurations (below)one, "Default"stable; the table parts are new
picturesobjectSketch Pictures by sketch id: {"name", "u", "v", "width", "height", "angle", "opacity"}; the image is pictures/<id>.img{}stable
hidden_bodies[string]bodies hidden in the view[]stable
hidden_refs[string]reference geometry hidden one by one: "Origin", "Front Plane", "Top Plane", "Right Plane" or a plane feature's id; only written when not empty[]new
foldersarraytree folders [{"id", "name", "features": [ids]}] (display only)[]stable
propertiesobjectcustom properties (see Properties)emptystable
simobject or nullthe SimpleSim strength check's settingsnullnew
contextobjectin-context design (below); only written when setnullnew

Features (stable, except where marked)

Every feature has:

KeyMeaning
id"F<n>", unique in the file; other features refer to it
kindthe feature type (table below)
namethe name in the tree, e.g. "Boss-Extrude1"
suppressedsuppressed in the active configuration
exprsonly when used: {field: equation}, a value driven by variables (e.g. {"depth": "thick*2"}); worked out in the file's units at each rebuild

The other keys depend on kind. Optional keys are left out when they hold their default, and a missing key means its default. operation is "new", "add" or "remove". End conditions are blind, through_all, midplane, up_to_surface, up_to_vertex, up_to_next, up_to_body. Faces and edges are referred to by geometric signatures (below).

kindMain keys
sketchplane_name ("Front Plane", "Top Plane", "Right Plane", a plane feature's id, or "" on a face), plane {origin, xdir, ydir}, face (signature or null), sketch (below), show
planebase, face, offset, flip, plane; mode "angle" (angle, axis, axis_ref) or "mid" (base2, face2)
extrudesketch_id, depth, end, reverse, operation; optional up_to, offset, start_offset, dir2/end2/depth2, thin/thin_t/thin_mode/thin_flip, draft/draft_outward, regions [[u,v]], segments, merge_faces
revolvesketch_id, axis_id, angle, reverse, operation; optional midplane, dir2/angle2, thin, regions, segments, merge_faces
filletedges, radius, optional radius2
chamferedges, distance, chamfer_type (equal distances angle), distance2, angle, flip
shellfaces, thickness, outward
holesketch_ids, placement (sketch 3d), points, hole_type (simple counterbore countersink tapped), size, fit, diameter, depth, through_all, cb_diameter, cb_depth, csk_diameter, csk_angle; optional reverse, bottom_angle, thread_depth
threadedge, size ("M10x1.5" or "Custom"), pitch, end, length, offset, flip, left_hand, optional cosmetic
loft / sweepprofiles / sketch_id + path_id, operation; a loft also has ruled
helixsketch_id, pitch, turns, start_angle, clockwise, reverse, taper
ribsketch_id, thickness, flip, end, reverse, dir2, depth2
draft, dome, delete_face, offset_face, move_face, wrap, split_linefaces plus their values
linear_pattern, circular_pattern, mirror, sketch_driven_pattern, fill_pattern, curve_patternseeds (feature ids), then direction, count and spacing; skip for removed instances
combine, body_delete, body_move, scale, split_bodybody operations
structural_membersketch_ids, profile (rect_tube round_tube rect_bar round_bar angle channel), dims, angle, end caps, gussets
gearevery gear setting (gear_type, module, teeth, pressure_angle, face_width, bore_type, hub, …)
sm_base, sm_flange, sm_sketched_bend, sm_jog, sm_miter, sm_convert, sm_corner_relief, sm_closed_cornersheet metal: thickness, radius, k_factor, …
insert_partanother part, inserted (below)
importan imported STEP/IGES/STL body: the shape is shapes/<id>.brep; source is the original file, never read again

There are 43 kinds; crusade/core/document.py has each one's full field list (its dataclass and to_dict).

{"id": "F2", "name": "Boss-Extrude1", "suppressed": false, "kind": "extrude",
 "exprs": {"depth": "thick"}, "sketch_id": "F1", "depth": 10, "end": "blind",
 "reverse": false, "operation": "new", "merge_faces": true}

Face and edge signatures (stable)

Faces and edges aren't numbered. They are described by their geometry and found again after each rebuild:

  • Face: {"desc": [surface type, normal or axis …], "center": [x,y,z], "area": mm²}
  • Edge: {"type": "line" | "circle" | "ellipse" | "bspline" | "other", "mid": [x,y,z], "dir": [x,y,z], "length": mm}. Circles add center, radius, arc; most add faces (the descriptions of the faces it joins).
  • New: a face or edge of another part (in-context design) also has "context": "<context id>/<component id>".

Sketches (stable; groups are new)

A sketch feature's sketch is {"entities": [...], "constraints": [...], "groups": {...}}. Coordinates are the sketch's own (u, v) in mm. The origin point "O" is implied and never written.

Entity kindKeysId
pointx, y, fixedP<n>
linep1, p2 (point ids), constructionL<n>
circlecenter, radius, constructionC<n>
arccenter, start, end (counterclockwise), constructionA<n>
ellipsecenter, major (point id), minor (mm), constructionE<n>
splinepoints (closed when the first is the last), constructionS<n>
textanchor, text, height, angle, font, bold, constructionT<n>

An entity can also carry ref, meaning it was copied from other geometry and follows it: {"sketch", "entity"}, {"edge": signature, "at": "curve" | "start" | "end" | "center"} or {"silhouette": face signature, "side", "at"}.

Constraints: {"id": "K<n>", "kind", "refs": [entity ids], "value": number or null, "options": {...}}.

  • Relations: coincident, horizontal, vertical, parallel, perpendicular, tangent, equal, concentric, midpoint, symmetric, collinear, fix, offset, point_on_line, point_on_circle, point_on_ellipse.
  • Dimensions: distance, hdistance, vdistance, length, radius, diameter, angle, point_line_distance, line_line_distance, point_line_diameter, line_line_diameter, ellipse_major, ellipse_minor, offset_distance. value is in mm, and in degrees for angle.
  • Options:
    • name: the dimension's name, e.g. "Length1" (new; older files get names when opened).
    • expr: an equation driving it.
    • driven: a reference dimension.
    • dx, dy: where its label sits.
    • and a few per kind (sign, flip_a, arc_a, …).

Groups (new; only written when there are any):

  • A live sketch pattern, id P<n>: {"kind": "sketch_pattern", "params": {...}, "copies": {"<instance>|<source id>": copy id}, "members": [...]}.
    • params: kind (linear/circular), entities, direction, spacing, count, two, spacing2, count2, center, ccount, total, equal, skip, reverse, centered, dir_vec, direction2, dir2_vec, reverse2, centered2, center_xy, and exprs (pattern values driven by variables).
    • The copies are ordinary entities in entities.
  • A gear profile, id G<n>: {"kind": "gear_profile", "params": {...}, "members": [...]}.

Variables and variable tables (stable)

variables is {name: expression}. A name is a letter or _ followed by letters, digits or _. Expressions are in the file's units and may carry their own ("25mm", '1"').

variable_notes (new, October 2026; only when there is one) is {name: text}: the comment on a variable, the Variables screen's Comment column. It doesn't change the model; an older app ignores it.

variable_renames (new, October 2026; only when there is one) is {old name: new name}: variables renamed in the Variables screen, so a drawing's note that still says {old} shows the variable's value. It doesn't change the model; an older app ignores it.

variable_tables links tables of variables: [{"path", "rel", "cache": [[name, value, unit, description]], "mtime"}].

  • The table can be a .cvar, .xlsx, .xlsm, .ods, .csv, .tsv or .txt file, or a .kvar (a .cvar from before October 22, 2026: the same file under its old name, still read).
  • cache holds the values last read, so the part still rebuilds without the table.
  • path and rel are saved with forward slashes; see Links between files.
  • Order of precedence: the table's values, then the part's own variables, then the active configuration's. With several tables, the first one listed wins for a name they share.

Configurations

The basic shape is stable: each configuration has variables (variable values it changes) and suppressed ({feature id: true/false}). The rest, the configuration table's columns and value cells, is new (October 2026).

KeyWhereHolds
variableseach configuration{variable: text}: the variable's value in this configuration
suppressedeach configuration{feature id: bool}. The active configuration's entry matches each feature's own suppressed.
valueseach configuration{column key: text}: configured values of features and dimensions (new)
columnsthe first configuration onlythe table's columns, in order (new). A copy may appear in another configuration (copied with it); it is ignored. A file without columns gets them worked out from what its configurations change.

The first configuration is the default one (it can be renamed; a new part's is "Default"). active_config names the one shown. config_renames (new, October 2026; only when there is one) is {old name: new name}: configurations renamed, so an assembly or drawing that still says the old name gets the renamed one. An older app ignores it.

Columns:

  • {"kind": "value", "variable": "thick"}: a variable. Its cells are each configuration's variables["thick"].
  • {"kind": "value", "target": T}: a feature's value or a dimension. Its cells are each configuration's values[key].
  • {"kind": "suppress", "feature": "F4"}: a feature's suppression. Its cells are each configuration's suppressed["F4"].

Targets and their keys:

TargetKey
{"feature": "F3", "attr": "depth"}F3:depth
{"feature": "F1", "dim": "K7"} (a sketch dimension)F1:dim:K7
{"feature": "F1", "gear": "G1", "param": "teeth"}F1:gear:G1:teeth
{"feature": "F1", "pattern": "P1", "param": "count"}F1:pattern:P1:count

Cell text:

  • Empty means the feature's or variable's normal value.
  • A number with its unit, e.g. "25.4 mm", "1 in", "30°", or a plain number for a count.
  • **Starting with =**: a formula, worked out again at every rebuild, e.g. "=thick * 2". (Text typed without = is worked out once and saved as the number. Older cells without = that use a variable still count as formulas.)
  • References between cells are saved in double quotes as "#<key>|<title>", e.g. ="#F1:dim:K9|Length1 [Sketch1]" / 4. Only the key counts; the title is just for reading and is shown from the current names. A variable column is referred to by its name. A " right after a digit is an inch mark, not a reference.
  • Formulas are worked out per configuration, in order of what refers to what; loops are errors.
"variables": {"thick": "5"},
"configurations": {
  "Default": {"variables": {}, "suppressed": {"F1": false, "F2": false},
              "values": {"F1:dim:K9": "40 mm", "F2:depth": "=\"#F1:dim:K9|Length1 [Sketch1]\" / 4"},
              "columns": [{"kind": "value", "target": {"feature": "F1", "dim": "K9"}},
                          {"kind": "value", "target": {"feature": "F2", "attr": "depth"}},
                          {"kind": "value", "variable": "thick"},
                          {"kind": "suppress", "feature": "F2"}]},
  "Big":     {"variables": {"thick": "=2.5 * 2"}, "suppressed": {"F1": false, "F2": false},
              "values": {"F1:dim:K9": "60 mm", "F2:depth": "10 mm"}}
},
"active_config": "Default"

Insert Part, castings and machined parts (stable; frozen is new)

A part made from another (File ▸ New Part from This, a casting and its machined part) starts with an insert_part feature:

KeyMeaning
sourcethe other part (see Links)
configits configuration; "" = its active one
operationnew, add or remove
body_point[x,y,z]: which body, for a part with several
mirror"" or a default plane's name
dx, dy, dzmoved by, mm
frozennew: the link is broken; the shape is kept in this file as shapes/<feature id>.brep and no longer follows the other part

A part that inserts itself, directly or through others, is an error.

{"id": "F1", "name": "Insert-Casting", "suppressed": false, "kind": "insert_part",
 "source": "Casting.cprt", "operation": "new", "config": "Raw", "dz": 2.5}

In-context design (new)

A part edited inside an assembly can use other parts' faces and edges. The part keeps everything; the assembly stores nothing for it.

  • **context** (top level): ``json "context": {"id": "3fa9c2e1b7d0", "assembly": "/home/you/Press/Press.casm", "component": "C4", "locked": false, "broken": false, "refs": {"C2": {"name": "Base Plate", "file": "Base Plate.cprt", "stamps": {"assembly": {"C2": [0,0,0, 0,0,0], "C4": [0,0,1.5708, 50,0,10]}, "part": "(1729470000000000000, 48213)"}}}}
    • id: random, 12 hex characters.
    • assembly: the assembly it was edited in: its name alone when it's in the part's folder (or a path below it), else its full path (never ..).
    • component: this part's component id there.
    • locked: Lock References. broken: Break References.
    • refs: by component id, the other parts it uses:
      • name, file (paths as for Insert Part).
      • stamps.assembly: where both components were, [rx, ry, rz, tx, ty, tz] (rotation vector in radians, then mm).
      • stamps.part: a stamp of the other file that only means something on the computer that wrote it.
  • **shapes/ctx-<component id>.brep**: each other part's shape as it was, already placed in this part's coordinates. So the part rebuilds without the assembly or the other parts.
  • Signatures pointing at these shapes carry "context" (see above), and such a file is version 2.

A whole small part

{"format": "kcad-document", "version": 1, "app": "Crusade", "saved_by": "0.0.4",
 "units": {"length": "mm", "length_decimals": 2, "angle": "deg", "angle_decimals": 2,
           "mass": "g", "mass_decimals": 3, "density": "g/cm3", "density_decimals": 4},
 "features": [
  {"id": "F1", "name": "Sketch1", "suppressed": false, "kind": "sketch", "plane_name": "Front Plane",
   "plane": {"origin": [0, 0, 0], "xdir": [1, 0, 0], "ydir": [0, 1, 0]}, "face": null,
   "sketch": {"entities": [
     {"id": "P1", "x": 0.0, "y": 0.0, "fixed": false, "kind": "point"},
     {"id": "P2", "x": 40.0, "y": 0.0, "fixed": false, "kind": "point"},
     {"id": "C3", "center": "P1", "radius": 5.0, "construction": false, "kind": "circle"},
     {"id": "L4", "p1": "P1", "p2": "P2", "construction": true, "kind": "line"}],
    "constraints": [{"id": "K5", "kind": "length", "refs": ["L4"], "value": 40.0,
                     "options": {"name": "Length1", "dx": 0.0, "dy": -8.0}}]}},
  {"id": "F2", "name": "Extrude1", "suppressed": false, "kind": "extrude", "sketch_id": "F1",
   "depth": 10, "end": "blind", "reverse": false, "operation": "new"}],
 "rollback": null, "material": "Aluminum 6061-T6", "finish": "None", "finish_color": null, "defaults": {},
 "variables": {}, "variable_tables": [],
 "configurations": {"Default": {"variables": {}, "suppressed": {"F1": false, "F2": false}}},
 "active_config": "Default", "appearance": null, "face_colors": [], "pictures": {}, "hidden_bodies": [],
 "folders": [], "properties": {"summary": {}, "custom": [], "config": {}}, "sim": null}

Assemblies (assembly.json)

Top-level keys

KeyMeaningStatus
format, version, saved_bysee abovestable
unitsalways "mm"; not readstable
display_unitsunits shown, as a part's unitsstable
componentsthe components (below)stable
matesthe mates (below)stable
variables, variable_tablesas in partsstable
explode{"on", "factor", "offsets": {component id: [x,y,z] mm}}stable
properties, defaultsas in partsstable
motionthe motion study; only when setstable
folders[{"id", "name", "components": [ids]}]; only when anystable
sketcheslayout sketches [{"id", "name", "plane", "sketch", "hidden"}]; only when anystable
hidden_refsreference geometry hidden one by one ("Origin", "X axis", …, "Front Plane", …)new
patternscomponent patterns (below)new
configurations, active_configconfigurations (below); only when there is more than Defaultnew
config_renames{old name: new name} of renamed configurations (as a part's); only when there is onenew
shared{"app", "date"}: only in File ▸ Share copiesstable

Components

KeyMeaningStatus
id"C<n>"stable
namee.g. "Base<1>"stable
source, relthe part or subassembly (see Links)stable; rel new
rotationrotation vector, radians: a point p of the part is at R(rotation)·p + translationstable
translationmmstable
fixeddoesn't movestable
configthe part's or subassembly's configuration; "" = its active onestable
hidden, transparenthow it showsstable
suppressedleft out (not shown, solved, listed or weighed), in the active configurationnew
flexiblea subassembly whose parts move as its own mates allownew
flexfor a flexible subassembly: {its component id: [rx, ry, rz, tx, ty, tz]}, each part's place in the subassembly; changes with every solvenew
patternthe id of the component pattern this is a copy of; "" for an ordinary componentnew
sketchesits part's sketches shown or hidden in this assembly only: `{sketch feature id: "shown" \"hidden"}`; written only when there is onenew

Mates (stable)

{"id": "M<n>", "name", "kind", "a", "b", "value", "aligned", "suppressed", "expr", "pose", "extra", "limits"}.

  • a and b are {"component": id, "face" | "edge" | "vertex": signature or point}. A selection can also be the assembly's own origin, an axis or a plane: "component": null with "origin": true, or "plane" and "name".
  • New: a part inside a subassembly adds "sub": its component id there.
  • New: a line, point, circle or arc of a part's shown sketch is {"component": id, "part_sketch": sketch feature id, "entity": its id in the sketch} (with "sub" for a subassembly's part). A layout sketch's is {"component": null, "sketch": id, "entity"}.
  • value is mm or degrees; for gear and linear_coupler it's a plain ratio.
  • pose is set for lock-type mates; it changes with solving.

Configurations (new)

"configurations": {"Default": {"components": {"C1": {"suppressed": false, "config": ""}},
                               "mates": {"M1": {"suppressed": false, "value": 0.0}},
                               "patterns": {"P1": {"suppressed": false}}},
                   "No Hardware": {"components": {"C3": {"suppressed": true, "config": ""}},
                                   "mates": {}, "patterns": {"P1": {"suppressed": true}}}},
"active_config": "Default"
  • Default is always first.
  • Anything not listed in a configuration is not suppressed and uses its saved config or value.
  • The suppressed, config and value on the components and mates themselves are the active configuration's.
  • Each configuration's own properties are in properties.config.

Component patterns (new)

"patterns": [{"id": "P1", "name": "LocalLPattern1", "kind": "linear", "seeds": ["C3"],
  "directions": [{"axis": "X", "reverse": false, "spacing": 50.0, "count": 4, "centered": false}],
  "axis": "Z", "center": [0, 0, 0], "angle": 360.0, "count": 4, "equal": true,
  "suppressed": false, "skip": ["2"], "copies": {"C3|1": "C4", "C3|3": "C6"}, "retired": {}}]
  • Linear uses directions (up to 3). Circular uses axis, center, angle (the total when equal, else between neighbors) and count.
  • skip lists deleted instances ("i,j,k" linear, "k" circular).
  • copies maps "<seed>|<instance>" to the copy's component id. The copies are ordinary, fixed entries in components with pattern set; their source and config follow their seed.
  • retired: copies the pattern no longer makes but that still have mates (kept, suppressed).

A whole small assembly

{"format": "kcad-assembly", "version": 1, "saved_by": "0.0.4", "units": "mm",
 "display_units": {"length": "in", "length_decimals": 3, "angle": "deg", "angle_decimals": 2,
                   "mass": "lb", "mass_decimals": 3, "density": "lb/in3", "density_decimals": 4},
 "components": [
  {"id": "C1", "name": "Plate<1>", "source": "/home/you/Projects/Press/Plate.cprt", "rel": "Plate.cprt",
   "rotation": [0, 0, 0], "translation": [0, 0, 0], "fixed": true, "config": "", "hidden": false,
   "transparent": false, "suppressed": false, "flexible": false, "flex": {}, "pattern": ""},
  {"id": "C2", "name": "Pin<1>", "source": "/home/you/Projects/Press/Pin.cprt", "rel": "Pin.cprt",
   "rotation": [0, 0, 0], "translation": [20, 10, 25], "fixed": false, "config": "", "hidden": false,
   "transparent": false, "suppressed": false, "flexible": false, "flex": {}, "pattern": ""}],
 "mates": [{"id": "M1", "name": "Concentric1", "kind": "concentric",
   "a": {"component": "C2", "face": {"desc": ["cylinder", 0.0, 0.0, 1.0], "center": [0, 0, 15], "area": 942.48}},
   "b": {"component": "C1", "face": {"desc": ["cylinder", 0.0, 0.0, 1.0], "center": [20, 10, 5], "area": 314.16}},
   "value": 0.0, "aligned": false, "suppressed": false, "expr": "", "pose": null, "extra": [], "limits": null}],
 "variables": {}, "variable_tables": [], "explode": {"on": false, "factor": 1.0, "offsets": {}},
 "properties": {"summary": {}, "custom": [], "config": {}}, "defaults": {}}

The bill of materials isn't saved in the recipe; it is worked out, and saved in the stored results (below).

Drawings (drawing.json)

Positions are on the sheet in mm, from its bottom-left corner, with y going up.

Top-level keys

KeyMeaningStatus
format, version, saved_bysee abovestable
sheet, scale, title_stylethe first sheet's size, scale and title block. The size is always one every Crusade knows (A4 … A1, ANSI A … ANSI C): a newer, portrait or custom sheet writes the nearest of those here, and its real size is in sheetsstable
title, drawn_by, company, logotitle block (logo: base64 PNG)stable
views, dims, tables, balloons, notes, symbolsthe drawing's contents (below)stable
markscenter marks and centerlines placed by handnew
display_units, propertiesas in assembliesstable
sheets, active_sheetseveral sheets (below)new
dual, dual_placedual dimensions: "mm" or "in"; "below" or "after"new
projection, projection_symbol"first" angle (third is the default); show the symbolnew
standard"ansi" or "iso"new
styleline weights, text sizes and font that differ from the standard'snew
layers, current_layerlayers [{"name", "color", "weight", "line", "shown"}]new
sketcheslines, rectangles and circles drawn on the sheetnew
balloon_style, hatch_by_part, fasteners_unhatched, part_hatch, zonesballoon and hatching defaults, border zonesnew

The new keys are only written when they are set, except marks (always written, [] when there are none). A missing on/off key (projection_symbol, hatch_by_part, fasteners_unhatched, zones) means off, so older drawings look as they did.

Sheets (new)

sheets is a list of {"id": "S1", "name": "Sheet1", "size", "scale", "title_style", "portrait"?, "custom"?: [w, h]}. It is only written when there is more than one sheet, or a sheet an older Crusade can't show.

  • When it's written, sheet holds the nearest older size, so older readers still find a size.
  • Views, tables, notes, symbols and sheet sketches carry sheet (a sheet id; "" means the first).
  • Dimensions, balloons and marks go with their view.

Views (stable; most of the later keys are new)

KeyMeaning
id, name"V<n>", "Drawing View1"
source, relthe part or assembly shown (see Links)
orientationFront, Back, Top, Bottom, Right, Left, Isometric, Flat Pattern, Auxiliary
x, y, scalewhere on the sheet; scale null = the sheet's
kind, parentmodel, section, detail or projected; the view it comes from
p1, p2, radius, flip, label, section_type, path, profilesection and detail settings
configthe part's or assembly's configuration
hidden_lines, center_marks, thread_callouts, tangent_edges, shadingwhat shows
breaks, cutouts, cropbroken views, broken-out sections, crop outline
align_to, align_axis, upright, show_caption, caption, hidden_marks, more_marks, hatch, sheetthe rest

Annotations

  • Dimensions (dims, stable):
    • id, view_id, kind (aligned, horizontal, vertical, diameter, radius, angle, ordinate_x, ordinate_y, chamfer, arc_length), points p1–p4 in view coordinates, offset.
    • New: refs, which tie the dimension to the model's edges so it follows changes. Also text ("<DIM>" stands for the value), tolerances (tol, tol_upper, tol_lower, fit), decimals, dual, value_style, callout, layer.
  • Tables (tables):
    • kind is bom, revisions, holes or cutlist, with source/rel, x, y, view_id.
    • New: config, columns, sort, descending, structure, sheet, layer.
    • The rows aren't saved in the recipe.
  • Balloons, notes, symbols:
    • Notes' text can hold {Property} fields.
    • Symbols are gdt, datum, finish, weld or target.
    • New: leaders, layer, and tie (follows the model).

A whole small drawing

{"format": "kcad-drawing", "saved_by": "0.0.4", "version": 1, "sheet": "A3", "scale": 1.0,
 "title": "Plate", "drawn_by": "you", "company": "",
 "views": [{"id": "V1", "name": "Drawing View1", "source": "/home/you/Projects/Press/Plate.cprt",
   "rel": "Plate.cprt", "orientation": "Front", "x": 200, "y": 150, "scale": null, "hidden_lines": true,
   "center_marks": true, "thread_callouts": true, "kind": "model", "parent": "", "p1": [0, 0], "p2": [0, 0],
   "radius": 0.0, "flip": false, "label": "", "config": "", "show_caption": false, "caption": "", "breaks": [],
   "sheet": ""}],
 "dims": [], "tables": [], "balloons": [], "notes": [], "symbols": [], "marks": [],
 "display_units": {"length": "mm", "length_decimals": 2, "angle": "deg", "angle_decimals": 2,
                   "mass": "g", "mass_decimals": 3, "density": "g/cm3", "density_decimals": 4},
 "properties": {"summary": {}, "custom": [], "config": {}},
 "projection_symbol": true, "hatch_by_part": true, "fasteners_unhatched": true, "zones": true}

Stored shapes (results/)

None of these are needed to open a file with its parts: Crusade can always rebuild them. They let a file be shown without rebuilding, and are not part of the version history.

Stored results (results/index.json + results/<n>.brep; stable):

  • Assemblies and drawings write them at every save, so a shared assembly or drawing opens without its parts.
    • Assemblies: each part's shape once, the components' places and colors, and the bill of materials.
    • Drawings: the views' lines, tables and title block.
  • Parts don't write them, except File ▸ Share's one-file copy.
  • The header is {"format": "crusade-results", "version": 1, "saved_by", "kind": "part" | "assembly" | "drawing", ...}.
  • Shapes are OCCT BREP. A drawing view's shaded picture is a PNG, kept as another results/<n>.brep file and named by the view's shade → png in the index.
  • crusade/core/stored.py documents every field.

A part's stored model (results/part/index.json + results/part/<n>.bin; new, October 18):

  • This is the shape the part was last built into, so a big part (and anything using it) opens at once.
  • The header is {"format": "crusade-part-model", "version": 2, "configs": {name: {"fingerprint", "body", ...}}}.
  • Shapes are OCCT binary BREP.
  • Crusade uses an entry only when its fingerprint matches. That is a hash of everything the rebuild reads: the recipe, the configuration, the files it inserts or links, the Crusade and OCCT versions, and a hash of the code that builds parts. Even then, it checks the model with a rebuild in the background.
  • Saving never starts a rebuild, so a part saved before it was built has none.
  • crusade/core/part_store.py documents it.

Kept on this computer, not in any file: Crusade also keeps the models of parts it rebuilt in a cache folder (~/.cache/Crusade/models on Linux, %LOCALAPPDATA%\Crusade\models on Windows, at most 1 GB). It can be deleted at any time, and a vault can ignore it.

History (stable)

Each file carries its own versions and revisions in history/; no vault or server is needed. history/index.json:

{"format": "kcad-history", "version": 1, "scheme": "letters",
 "versions": [{"n": 12, "time": "2026-10-11T17:05:00+00:00", "kind": "auto", "note": "",
               "author": "you", "revision": null, "entry": "history/v000012.json", "saved_by": "0.0.4",
               "blobs": {"shapes/F3.brep": "history/blobs/ab12….brep"}}],
 "revisions": [{"rev": "A", "version": 9, "time": "…", "description": "First release",
                "author": "you"}]}
  • **kind:**
    • save
    • auto (Auto Save)
    • named (a Checkpoint, or Save Version with a note)
    • restore (an older version brought back)
    • revision (a released revision)
    • external (changed by another program through the command-line tool)
    • legacy (what a file held before it had history)
    • older (the file as an older Crusade last saved it; new)
  • Automatic versions thin out with age; every other kind is kept.
  • Version numbers count up and are never reused. Times are UTC.
  • v######.json is the main JSON exactly as saved. blobs maps the version's other entries (shapes, pictures) to files stored once, however many versions share them.
  • The current revision is the last entry of revisions. scheme is letters (A, B, … skipping I O Q S X Z, then AA), numbers or padded (01, 02, …).
  • A file saved before history existed has no history/. Its next save adds it, with the earlier contents kept as a legacy version.

Properties (stable)

In the main JSON of all three types:

"properties": {
  "summary": {"author": "you", "title": "Base plate"},
  "custom": [{"name": "PartNo", "type": "Text", "value": "KC-1042"},
             {"name": "Mass", "type": "Text", "value": "Mass"}],
  "config": {"Long": [{"name": "PartNo", "type": "Text", "value": "KC-1042-L"}]}
}
  • summary can hold author, keywords, comments, title and subject; empty ones aren't written.
  • type is Text, Date, Number or Yes or No.
  • value is the text as typed. A value that is a link's name (Mass, File Name …) is worked out by Crusade (crusade/core/properties.py lists them); inside other text a link is $PRP:"Mass". Files written before 0.0.5 may say SW-Mass (or "SW-Mass@Part1.SLDPRT") and name properties Number and Weight: they're read as Mass, Document Number and Mass, and written that way at the next save.
  • config holds each configuration's own properties.
  • parts (parts only; new in 0.0.5, left out when empty): part numbers typed in File ▸ Properties ▸ Parts where they differ from the suggested ones (the document number, -1, -2 … for several bodies, a color code): [{"body": "Boss-Extrude1", "number": "P-0003-SPACER", "description": "Spacer block"}]. body is the body's name in the tree ("" for a one-body part); "number": "" means the body has none. Several rows may share a number.
  • "viewer_export": false appears in a File ▸ Share copy whose sender didn't allow exporting it; otherwise it isn't written.
  • To get the worked-out values without writing your own evaluator, use the tool below.

Other files a vault will see

FileWhat it is
.cvarA variable table: {"format": "kcad-variables", "version": 1, "variables": [{"name", "value", "unit", "description"}]} (stable). Older shared tables use .kvar; they're read the same way. The format name inside stays kcad-variables.
.cdwtA drawing template: {"format": "crusade-drawing-template", "version": 1, "saved_by", ...} with the sheet, title block and style settings
.crusade/crusade-number-registry.jsonIn the top Projects folder: the number registry (new): {"format": "crusade-registry", "version": 1, "documents": {number: {"description", "file", "type", "revision", "version", "parts", "status", "saved", "also", "drawings"}}, "parts": {number: {"description", "mass" (grams), "materials", "documents", "uses", "differs"}}}. Written in one step under .crusade/crusade-number-registry.lock.
.crusade/spares/<name>Next to saved files: a copy of each file's last good save, replaced at every save and pruned 90 days after the file is gone (new). **Exclude .crusade/ from a vault.**
.<name>.crusade-move, .<name>.crusade-move-old, .<name>.crusade-wasA file being renamed or moved by the Projects browser, or one it's replacing, for a moment
.<name>.importing-<n>/A STEP assembly being imported, renamed when done
.<name>.<random>.tmpA file being saved, for a moment
<name>.cprt.bak (and .casm.bak, .cdwg.bak)The command-line tool's copy while it saves a change; removed once the save worked
<name> (shared).casm / .cdwg / .cprtFile ▸ Share copies: one file with its stored results, "shared" set, source (and variable tables' path and rel) reduced to the file name; components keep their rel
Documents/Crusade/Fasteners/The fastener library: ordinary .cprt parts

The command-line tool (stable)

For reading and changing properties, revisions and versions without opening Crusade. Run it with Crusade's own Python:

  • Installed on Linux: ~/.local/opt/crusade/runtime/bin/python3.12 -m crusade.fileinfo FILE
  • Installed on Windows: "%LOCALAPPDATA%\Programs\Crusade\runtime\python.exe" -m crusade.fileinfo FILE
CommandDoes
FILEPrint everything as JSON: type, revision, configurations, summary, custom and configuration properties with their evaluated values, revisions and versions
FILE --get NAME [--config CFG]Print one property's worked-out value
FILE --set NAME=VALUE … [--type T] [--config CFG]Add or change properties
FILE --unset NAME [--config CFG]Delete a property
FILE --summary author=youSet summary information
FILE --release B --description "ECO 1234"Release a revision
FILE --scheme lettersChange the revision scheme
FILE --version 7 --extract OUTWrite version 7 as a file of its own
  • --by NAME records who made a change (the default is the login name). --note TEXT adds a note to the version the change is saved as.
  • Exit status: 0 on success, 1 for bad input (an unknown configuration, property or field, a revision released already), and 2 when the file can't be read or written, or the command line itself is wrong.
  • Every change becomes a new version in the file's own history (kind external, or revision), so Crusade shows it and can roll it back.