No description
  • Rust 99.6%
  • Just 0.2%
  • PowerShell 0.2%
Find a file
Chris Kruining e99ff85acd
Handover notes for the machine reinstall
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 15:10:30 +01:00
.cargo Set up the Rust workspace skeleton (choose-tech-stack) 2026-11-02 13:51:36 +01:00
.claude Initial commit: project scaffold and choose-tech-stack proposal 2026-11-02 13:33:54 +01:00
assets App icon; propose build assemblies 2026-11-03 11:16:04 +01:00
crates Render on the main thread: resizing no longer hangs 2026-11-03 13:27:37 +01:00
data/connectors/parts Pose the reference build (gundam-posing) 2026-11-03 06:40:53 +01:00
docs Archive choose-tech-stack and sync its specs 2026-11-02 14:23:32 +01:00
openspec Archive the completed changes 2026-11-03 13:48:05 +01:00
xtask App icon; propose build assemblies 2026-11-03 11:16:04 +01:00
.gitignore Set up the Rust workspace skeleton (choose-tech-stack) 2026-11-02 13:51:36 +01:00
Cargo.lock App icon; propose build assemblies 2026-11-03 11:16:04 +01:00
Cargo.toml Open and save with file dialogs (file-dialogs) 2026-11-03 00:49:46 +01:00
HANDOVER.md Handover notes for the machine reinstall 2026-11-03 15:10:30 +01:00
justfile Open builds from the file manager in the running BrickForge 2026-11-03 09:58:14 +01:00
LICENSE Initial commit: project scaffold and choose-tech-stack proposal 2026-11-02 13:33:54 +01:00
README.md Tabs: several builds open at once 2026-11-03 10:09:22 +01:00
rust-toolchain.toml Set up the Rust workspace skeleton (choose-tech-stack) 2026-11-02 13:51:36 +01:00

BrickForge

An open source LEGO® build designer: inspired by BrickLink Stud.io, but open, native and mechanism-aware.

Early days: the parts library works (install, updates, search), builds can be opened (LDraw and Stud.io) and viewed with a Stud.io-like camera, but there is no editor yet. See openspec/changes/ for work in progress.

Goals

  • Native on Linux (primary), Windows and macOS. The web is not a target.
  • Parts updates without app updates: the LDraw parts library is versioned and updated separately from the app.
  • Mechanisms done properly: parts, assemblies and mates, solved by a real constraint solver.
  • Flexible parts that work well: based on splines.
  • Photoreal rendering (Cycles, shipped with BrickForge) with a node-based post-processing pipeline.
  • Free software under the AGPL.

Stack

Area Choice
Language Rust
App / viewport Bevy (in the app crate only)
Parts LDraw library
Photoreal rendering Cycles, built as a shared library; Bevy Solari as an optional preview
Physics (later) Box3D

See docs/architecture.md for the rules that keep this maintainable.

Workspace

Crate What it is
crates/core (brickforge-core) The headless core: everything except the interactive UI. Never depends on Bevy.
crates/cli (brickforge) Command-line interface to the core; runs without a display.
crates/app (brickforge-app) The desktop application; the only crate that uses Bevy.
crates/conventions Tests that enforce the workspace rules (Bevy-free core, AGPL licence).
xtask Project automation (cargo xtask check).

Building

The Rust toolchain is pinned in rust-toolchain.toml, so rustup installs the right version automatically.

cargo run -p brickforge-cli -- --version   # headless CLI
cargo run -p brickforge-app                # desktop app
cargo xtask check                          # formatting, clippy (warnings denied) and all tests

cargo xtask check is everything a CI pipeline needs to run. Debug builds optimise dependencies (Bevy is too slow otherwise), so the first debug build takes a while.

With just: just app [FILE], just dev [FILE] (rebuilds and restarts the app, keeping the view, whenever the sources change), just hot [FILE] (hot-patches changed systems into the running app; needs the Dioxus CLI dx 0.7.0-rc.0), just check, just snap …, and just gundam (opens the reference build; set BRICKFORGE_GUNDAM to its path if it lives elsewhere).

Checking interactions

--input "click 0.5,0.4; drag 80,0; key M; key ctrl+z" plays mouse and keyboard input into the app once a build is shown (positions are fractions of the window), through the same handlers a person uses. Combine it with cargo xtask snap to see the result.

Seeing what the app shows

cargo xtask snap [--release] [--at 3,10] [--out target/snaps] [-- APP ARGS] builds and starts the app, saves what is on screen at each moment (captured by the operating system, so a window that never draws shows up as such), and closes it. On Windows it captures the app's window; elsewhere the whole screen (grim on Wayland, ImageMagick's import on X11, screencapture on macOS).

Linux system packages

Bevy needs a few system libraries on Linux. On Debian/Ubuntu:

sudo apt install g++ pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0 libwayland-dev libxkbcommon-dev

For other distributions (including NixOS), see Bevy's Linux dependencies.

Parts library

BrickForge uses the LDraw parts library and manages it itself, separately from app releases: the app installs it on first run and keeps it current in the background (it checks LDraw.org's small release list at most once an hour and downloads only new releases). The CLI never changes the library on its own:

brickforge library status                 # release, sources, part count, settings
brickforge library install [--from ZIP]   # from LDraw.org, or a local complete.zip (offline)
brickforge library update [--check|--full]
brickforge library unofficial on|off      # unofficial parts from the Parts Tracker (off by default)
brickforge library folder add|remove DIR  # your own parts, or an existing LDraw directory
brickforge library folder add --fallback DIR   # only for parts the library lacks (see below)
brickforge library search QUERY
brickforge library path

The library lives in the OS data directory (Linux ~/.local/share/brickforge/library, Windows %APPDATA%\brickforge\data\library); override it with --library-dir or BRICKFORGE_LIBRARY_DIR. Your choices are in settings.toml there:

auto_update = true          # the app keeps the library current
folders = []                # local part folders, highest precedence first

[unofficial]
enabled = false             # opt-in
freshness = "7d"            # "always", "<N>d" or "manual"

Folders rank above everything else, so they can replace official parts. Fallback folders (fallback_folders in settings.toml) rank below everything, so they only fill gaps. Builds made in Stud.io often use parts LDraw doesn't have yet (newer parts, BrickLink's bl_* parts); with Stud.io installed, add its folders as fallbacks:

brickforge library folder add --fallback "C:\Program Files\Studio 2.0\ldraw\UnOfficial"
brickforge library folder add --fallback "C:\Program Files\Studio 2.0\ldraw"

They are read in place and never copied or added to the mesh pack.

Parts from the LDraw Parts Library (https://library.ldraw.org), licensed under CC BY 2.0 or CC BY 4.0 (see each part's !LICENSE line).

Part geometry

BrickForge builds a mesh for each part from its LDraw files: triangles all facing outwards (the BFC rules are applied), edge lines kept separate, and the part's own colour left as a placeholder so one mesh serves every colour.

brickforge part show 3001.dat                       # triangles, edges, colour groups, bounds, problems
brickforge part show 3001.dat --colour 4 --obj brick.obj   # also export OBJ/MTL (Y-up) for Blender etc.
brickforge part check [--pack]                      # build (or load) every part: timings and problems
brickforge part prepare [--full]                    # prepare the mesh pack (normally automatic)
brickforge part thumbnail 3001.dat brick.png --colour 4   # a picture, as the part browser shows it

Meshes are prepared ahead of time into a mesh pack (cache/ in the library directory) whenever the library is installed or updated, by the app in the background or by the CLI's install/update commands. Opening a build then only loads ready-made meshes. brickforge library status shows whether the pack is current.

Builds

BrickForge reads LDraw (.ldr, .mpd) and Stud.io (.io) files into its own document model, and writes LDraw. For Stud.io files, part definitions embedded in the file are used for parts the LDraw library doesn't have.

brickforge model info "My model.io"          # models, parts, steps, missing parts
brickforge model export "My model.io" out.mpd
brickforge model parts "My model.io" [--csv parts.csv]      # what it is made of, by part and colour
brickforge model picture "My model.io" model.png [--step 3]   # a picture, drawn without a GPU
brickforge model steps "My model.io" steps/                  # step-001.png, … all framed alike

Connectors

Where parts can connect (studs, anti-studs, pin and axle holes, pins, axles, clips, hinges, ball joints) comes from the LDCad Shadow Library (CC BY-SA 4.0), which the library manages like the unofficial parts: downloaded on install, refreshed by the app (freshness = "30d"), on by default. Parts inherit connectors from the primitives they use, so a brick gets its studs from stud.dat.

brickforge library connectors on|off
brickforge part connectors 3001.dat         # a part's connectors
brickforge part check --connectors          # coverage over the whole library
brickforge model connections "My model.io"  # connections by freedom, unconnected parts, connected groups
brickforge model connections "My model.io" --near 3069b.dat   # why a part does (not) connect (PART#N: placement N)
brickforge model connections "My model.io" --gaps 12          # what keeps the 12 largest pieces apart

BrickForge adds its own connection data for parts the shadow library doesn't have yet (data/connectors, below the download), starting with the Technic large rotation joints (69787–69790). For a part without connectors, --near lists the connectors around each of its placements in the part's own space: where its connectors belong. Connectors up to 3 LDU off still connect when nothing else joins the two parts (hand-placed builds are a little off), and for posing, parts that touch without connecting are held together (armour rides with what it rests on).

Corrections go in local folders laid out like the shadow library ([connectors] folders = [...] in settings.toml), which win over the download. In the app, U outlines parts without any connection in red (the build panel counts them), and C shows connectors (connected ones in green); --connectors starts with them shown.

Mechanisms

From the connections, BrickForge works out which parts move together (rigid groups: two studs, two pins, an axle in an axle hole…) and how the groups may move relative to each other (mates: revolute, prismatic, cylindrical, ball). A solver moves a mate and everything beyond it, keeping closed loops (linkages) closed.

brickforge model mechanism "My model.io"              # groups, mates, loops, degrees of freedom, timing
brickforge model mechanism "My model.io" --groups 20  # the largest groups and what their mate moves
brickforge model mechanism "My model.io" --no-sliding # pins and axles only turn

Sliding has limits: a part slides along an axle, pin or bar only as far as it stays as engaged as it is, without sections colliding (a pin's collar stops at a hole's narrow middle) and without passing other parts on the same axle. An axle rides with the part it has least room in, and loops are closed within the limits. A seated pin has no room to slide, so it only turns.

Spur gears are recognised by their names ("Technic Gear 24 Tooth"); meshing gears on a common frame turn together at the right ratio (1.25 LDU of pitch radius per tooth).

Hoses: Add a hose in the parts panel, then click two parts. The hose runs between their nearest connectors with some slack, follows the parts when they move, and stops moves that would stretch it. It is saved as plain LDraw (a hose-N.ldr submodel with a 0 !BRICKFORGE HOSE line).

In the app, left-click a part to select its group (yellow, with its mate's axis in magenta) and left-drag to turn it (Shift + left-drag slides it along an axle); R resets the pose, Esc clears the selection, and Keep pose (build panel) writes the pose into the build so saving keeps it (undoable). --turn PART|group:N DEGREES turns a group once the build is shown and --slide PART|group:N LDU slides it (for screenshots).

Editing

The Parts panel searches the library and shows each part's picture in the current colour (--search TEXT starts with a search); choose a part, and a translucent copy follows the cursor, snapped to where it would attach on the part under it (at the nearest connectors, where it connects most). [ / ] turn it 90° about that connection, a click places it, Esc stops placing. With a part selected: M picks it up (it follows the cursor the same way; click to put it down, Esc to put it back), Ctrl+click adds more parts to the selection (or takes one out) for the keys and colours below, arrows move it a stud, PgUp/PgDn a plate, [ / ] turn it 90°, Del deletes it, a swatch (or the full colour list) recolours it, Ctrl+D places a copy, H hides it (Shift+H shows every part). The build panel's step slider (or , and .) shows the build up to a step. Ctrl+Z / Ctrl+Y undo and redo; Ctrl+S saves as LDraw next to the file (<name>.brickforge.mpd; the original is never overwritten), or where Save as… last saved. Open… opens a build with the system's file dialog (on Linux through the XDG desktop portal). For screenshots: --attach PART TARGET attaches a part on top of another once the build is shown, --place PART starts placing, and --cursor X,Y sets a virtual cursor (fractions of the window).

Viewing a build

cargo run --release -p brickforge-app -- "My model.io"     # or drop a file onto the window

The camera works like Stud.io's: right-drag orbits, middle-drag or Shift + right-drag pans, the scroll wheel zooms, and F frames the whole build. Started without a file, the window says how to open one. --screenshot out.png saves a picture of what the app renders once the build is shown and exits.

Every open build has a tab (above the viewport) with its own history, selection and view; opening a build that has a tab switches to it. Only one BrickForge runs at a time: starting it with a file while it is running opens the file in a new tab of the running window. On Windows, just register (or brickforge-app --register) lets the file manager open .io, .ldr and .mpd files in BrickForge: it then shows under "Open with", where "Always" makes it the default.

Rendering elsewhere

brickforge model export "My model.io" model.glb writes binary glTF 2.0: one mesh per part and colour (shared by every placement), physically based materials from the LDraw colours, Y up in metres. Blender imports it, so builds can be rendered with Cycles there until BrickForge bundles Cycles itself.

Post-processing

Renders are finished with compositor graphs: exposure, contrast, saturation, blur, bloom, vignette, tilt-shift and mixing, written as TOML ([[node]] tables naming their inputs: render or earlier nodes).

brickforge-app "My model.io" --screenshot render.png          # a render without panels
brickforge image compose render.png out.png --preset tilt-shift   # or cinematic, soft
brickforge image compose render.png out.png --graph my-look.toml
brickforge image presets --show tilt-shift                    # a starting point for your own graph

Development workflow

This project uses OpenSpec for spec-driven development. Changes live in openspec/changes/, accepted specs in openspec/specs/.

License

AGPL-3.0-or-later

LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize or endorse this project.