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)
| Type | Extension (old one, still opened) | Main JSON entry | format |
|---|---|---|---|
| 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
formatfield. (The format names start withkcad, 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>.tmpin 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
rotationis 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:
| Key | Meaning |
|---|---|
version | The 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_by | The Crusade that saved it, e.g. "0.0.4". Written since 0.0.3; older files don't have it. |
app | Parts 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_byis newer than the running Crusade, orversionis 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 asaved_bynewer 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.
Links between files
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:
| Key | Holds |
|---|---|
source | The full path: /home/you/Projects/Press/Base.cprt or C:/Users/you/Projects/Press/Base.cprt. |
rel | The 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:
rel, if that file exists.sourceas saved (a relative one is taken from the file's folder).- A file of the same name in the file's own folder.
- The fastener library: any path containing
/Crusade/Fasteners/(or the old/KCAD/Fasteners/) is looked for under this computer'sDocuments/Crusade/Fasteners/. - 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
| Key | Type | Meaning | Missing means | Status |
|---|---|---|---|---|
format, version, saved_by, app | see above | stable | ||
units | object | units 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>_decimals | millimeters and grams | stable |
features | array | the feature tree, in order (below) | required | stable |
rollback | int or null | how many features are active (the rollback bar); null = all | null | stable |
material | string | material name; "" = none | "" | stable |
finish | string | "None", "Black Oxide", "Anodized (Type II)", "Powder Coat", "Paint", … | "None" | stable |
finish_color | [r,g,b] or null | color of an anodize or paint finish, 0–1 | null | stable |
appearance | [r,g,b] or null | the part's color, 0–1; null = from material and finish | null | stable |
face_colors | array | [{"face": face signature, "rgb": [r,g,b]}] | [] | stable |
defaults | object | this file's Materials & Finishes overrides | {} | stable |
variables | object | {name: expression} (below) | {} | stable |
variable_tables | array | linked variable tables (below) | [] | stable |
configurations, active_config | object, string | configurations (below) | one, "Default" | stable; the table parts are new |
pictures | object | Sketch 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 |
folders | array | tree folders [{"id", "name", "features": [ids]}] (display only) | [] | stable |
properties | object | custom properties (see Properties) | empty | stable |
sim | object or null | the SimpleSim strength check's settings | null | new |
context | object | in-context design (below); only written when set | null | new |
Features (stable, except where marked)
Every feature has:
| Key | Meaning |
|---|---|
id | "F<n>", unique in the file; other features refer to it |
kind | the feature type (table below) |
name | the name in the tree, e.g. "Boss-Extrude1" |
suppressed | suppressed in the active configuration |
exprs | only 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).
kind | Main keys |
|---|---|
sketch | plane_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 |
plane | base, face, offset, flip, plane; mode "angle" (angle, axis, axis_ref) or "mid" (base2, face2) |
extrude | sketch_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 |
revolve | sketch_id, axis_id, angle, reverse, operation; optional midplane, dir2/angle2, thin, regions, segments, merge_faces |
fillet | edges, radius, optional radius2 |
chamfer | edges, distance, chamfer_type (equal distances angle), distance2, angle, flip |
shell | faces, thickness, outward |
hole | sketch_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 |
thread | edge, size ("M10x1.5" or "Custom"), pitch, end, length, offset, flip, left_hand, optional cosmetic |
loft / sweep | profiles / sketch_id + path_id, operation; a loft also has ruled |
helix | sketch_id, pitch, turns, start_angle, clockwise, reverse, taper |
rib | sketch_id, thickness, flip, end, reverse, dir2, depth2 |
draft, dome, delete_face, offset_face, move_face, wrap, split_line | faces plus their values |
linear_pattern, circular_pattern, mirror, sketch_driven_pattern, fill_pattern, curve_pattern | seeds (feature ids), then direction, count and spacing; skip for removed instances |
combine, body_delete, body_move, scale, split_body | body operations |
structural_member | sketch_ids, profile (rect_tube round_tube rect_bar round_bar angle channel), dims, angle, end caps, gussets |
gear | every 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_corner | sheet metal: thickness, radius, k_factor, … |
insert_part | another part, inserted (below) |
import | an 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 addcenter,radius,arc; most addfaces(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 kind | Keys | Id |
|---|---|---|
point | x, y, fixed | P<n> |
line | p1, p2 (point ids), construction | L<n> |
circle | center, radius, construction | C<n> |
arc | center, start, end (counterclockwise), construction | A<n> |
ellipse | center, major (point id), minor (mm), construction | E<n> |
spline | points (closed when the first is the last), construction | S<n> |
text | anchor, text, height, angle, font, bold, construction | T<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.valueis in mm, and in degrees forangle. - 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, andexprs(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,.tsvor.txtfile, or a.kvar(a.cvarfrom before October 22, 2026: the same file under its old name, still read). cacheholds the values last read, so the part still rebuilds without the table.pathandrelare 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).
| Key | Where | Holds |
|---|---|---|
variables | each configuration | {variable: text}: the variable's value in this configuration |
suppressed | each configuration | {feature id: bool}. The active configuration's entry matches each feature's own suppressed. |
values | each configuration | {column key: text}: configured values of features and dimensions (new) |
columns | the first configuration only | the 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'svariables["thick"].{"kind": "value", "target": T}: a feature's value or a dimension. Its cells are each configuration'svalues[key].{"kind": "suppress", "feature": "F4"}: a feature's suppression. Its cells are each configuration'ssuppressed["F4"].
Targets and their keys:
| Target | Key |
|---|---|
{"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:
| Key | Meaning |
|---|---|
source | the other part (see Links) |
config | its configuration; "" = its active one |
operation | new, add or remove |
body_point | [x,y,z]: which body, for a part with several |
mirror | "" or a default plane's name |
dx, dy, dz | moved by, mm |
frozen | new: 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
| Key | Meaning | Status |
|---|---|---|
format, version, saved_by | see above | stable |
units | always "mm"; not read | stable |
display_units | units shown, as a part's units | stable |
components | the components (below) | stable |
mates | the mates (below) | stable |
variables, variable_tables | as in parts | stable |
explode | {"on", "factor", "offsets": {component id: [x,y,z] mm}} | stable |
properties, defaults | as in parts | stable |
motion | the motion study; only when set | stable |
folders | [{"id", "name", "components": [ids]}]; only when any | stable |
sketches | layout sketches [{"id", "name", "plane", "sketch", "hidden"}]; only when any | stable |
hidden_refs | reference geometry hidden one by one ("Origin", "X axis", …, "Front Plane", …) | new |
patterns | component patterns (below) | new |
configurations, active_config | configurations (below); only when there is more than Default | new |
config_renames | {old name: new name} of renamed configurations (as a part's); only when there is one | new |
shared | {"app", "date"}: only in File ▸ Share copies | stable |
Components
| Key | Meaning | Status | |
|---|---|---|---|
id | "C<n>" | stable | |
name | e.g. "Base<1>" | stable | |
source, rel | the part or subassembly (see Links) | stable; rel new | |
rotation | rotation vector, radians: a point p of the part is at R(rotation)·p + translation | stable | |
translation | mm | stable | |
fixed | doesn't move | stable | |
config | the part's or subassembly's configuration; "" = its active one | stable | |
hidden, transparent | how it shows | stable | |
suppressed | left out (not shown, solved, listed or weighed), in the active configuration | new | |
flexible | a subassembly whose parts move as its own mates allow | new | |
flex | for a flexible subassembly: {its component id: [rx, ry, rz, tx, ty, tz]}, each part's place in the subassembly; changes with every solve | new | |
pattern | the id of the component pattern this is a copy of; "" for an ordinary component | new | |
sketches | its part's sketches shown or hidden in this assembly only: `{sketch feature id: "shown" \ | "hidden"}`; written only when there is one | new |
Mates (stable)
{"id": "M<n>", "name", "kind", "a", "b", "value", "aligned", "suppressed", "expr", "pose", "extra", "limits"}.
aandbare{"component": id, "face" | "edge" | "vertex": signature or point}. A selection can also be the assembly's own origin, an axis or a plane:"component": nullwith"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"}. valueis mm or degrees; forgearandlinear_couplerit's a plain ratio.poseis 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
configorvalue. - The
suppressed,configandvalueon 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 usesaxis,center,angle(the total whenequal, else between neighbors) andcount. skiplists deleted instances ("i,j,k"linear,"k"circular).copiesmaps"<seed>|<instance>"to the copy's component id. The copies are ordinary, fixed entries incomponentswithpatternset; theirsourceandconfigfollow 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
| Key | Meaning | Status |
|---|---|---|
format, version, saved_by | see above | stable |
sheet, scale, title_style | the 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 sheets | stable |
title, drawn_by, company, logo | title block (logo: base64 PNG) | stable |
views, dims, tables, balloons, notes, symbols | the drawing's contents (below) | stable |
marks | center marks and centerlines placed by hand | new |
display_units, properties | as in assemblies | stable |
sheets, active_sheet | several sheets (below) | new |
dual, dual_place | dual dimensions: "mm" or "in"; "below" or "after" | new |
projection, projection_symbol | "first" angle (third is the default); show the symbol | new |
standard | "ansi" or "iso" | new |
style | line weights, text sizes and font that differ from the standard's | new |
layers, current_layer | layers [{"name", "color", "weight", "line", "shown"}] | new |
sketches | lines, rectangles and circles drawn on the sheet | new |
balloon_style, hatch_by_part, fasteners_unhatched, part_hatch, zones | balloon and hatching defaults, border zones | new |
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,
sheetholds 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)
| Key | Meaning |
|---|---|
id, name | "V<n>", "Drawing View1" |
source, rel | the part or assembly shown (see Links) |
orientation | Front, Back, Top, Bottom, Right, Left, Isometric, Flat Pattern, Auxiliary |
x, y, scale | where on the sheet; scale null = the sheet's |
kind, parent | model, section, detail or projected; the view it comes from |
p1, p2, radius, flip, label, section_type, path, profile | section and detail settings |
config | the part's or assembly's configuration |
hidden_lines, center_marks, thread_callouts, tangent_edges, shading | what shows |
breaks, cutouts, crop | broken views, broken-out sections, crop outline |
align_to, align_axis, upright, show_caption, caption, hidden_marks, more_marks, hatch, sheet | the rest |
Annotations
- Dimensions (
dims, stable):id,view_id,kind(aligned,horizontal,vertical,diameter,radius,angle,ordinate_x,ordinate_y,chamfer,arc_length), pointsp1–p4in view coordinates,offset.- New:
refs, which tie the dimension to the model's edges so it follows changes. Alsotext("<DIM>"stands for the value), tolerances (tol,tol_upper,tol_lower,fit),decimals,dual,value_style,callout,layer.
- Tables (
tables):kindisbom,revisions,holesorcutlist, withsource/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'
textcan hold{Property}fields. - Symbols are
gdt,datum,finish,weldortarget. - New: leaders,
layer, andtie(follows the model).
- Notes'
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>.brepfile and named by the view'sshade→pngin the index. crusade/core/stored.pydocuments 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
fingerprintmatches. 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.pydocuments 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:**saveauto(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######.jsonis the main JSON exactly as saved.blobsmaps 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.schemeisletters(A, B, … skipping I O Q S X Z, then AA),numbersorpadded(01, 02, …). - A file saved before history existed has no
history/. Its next save adds it, with the earlier contents kept as alegacyversion.
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"}]}
}
summarycan holdauthor,keywords,comments,titleandsubject; empty ones aren't written.typeisText,Date,NumberorYes or No.valueis the text as typed. A value that is a link's name (Mass,File Name…) is worked out by Crusade (crusade/core/properties.pylists them); inside other text a link is$PRP:"Mass". Files written before 0.0.5 may saySW-Mass(or"SW-Mass@Part1.SLDPRT") and name propertiesNumberandWeight: they're read asMass,Document NumberandMass, and written that way at the next save.configholds 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"}].bodyis 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": falseappears 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
| File | What it is |
|---|---|
.cvar | A 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. |
.cdwt | A drawing template: {"format": "crusade-drawing-template", "version": 1, "saved_by", ...} with the sheet, title block and style settings |
.crusade/crusade-number-registry.json | In 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-was | A 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>.tmp | A 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 / .cprt | File ▸ 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
| Command | Does |
|---|---|
FILE | Print 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=you | Set summary information |
FILE --release B --description "ECO 1234" | Release a revision |
FILE --scheme letters | Change the revision scheme |
FILE --version 7 --extract OUT | Write version 7 as a file of its own |
--by NAMErecords who made a change (the default is the login name).--note TEXTadds 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, orrevision), so Crusade shows it and can roll it back.