# linkage-blaze
[](https://github.com/CarlKCarlK/linkage-blaze)
[](https://crates.io/crates/linkage-blaze)
[](https://docs.rs/linkage-blaze)
3D turtle graphics for animated jointed mechanisms and figures. Describe a figure with moves,
turns, branches, links, joints, disks, and spheres, then animate parameters to bring it to life. Runs both `no_std` and `std`.
<p>
<img src="https://raw.githubusercontent.com/CarlKCarlK/linkage-blaze/main/crates/linkage-blaze/tests/assets/armatron.png" alt="Armatron demo preview" height="200" />
<img src="https://raw.githubusercontent.com/CarlKCarlK/linkage-blaze/main/crates/linkage-blaze/tests/assets/ballet.png" alt="Ballet demo preview" height="200" />
</p>
## Project Links
- **[Live demo gallery](https://carlkcarlk.github.io/linkage-blaze/demos/)** -
Preview and run the interactive browser demos.
- **[Crate on crates.io](https://crates.io/crates/linkage-blaze)** - Releases,
installation information, and package metadata.
- **[Documentation on docs.rs](https://docs.rs/linkage-blaze)**
- **[GitHub repository](https://github.com/CarlKCarlK/linkage-blaze)** - Source,
issues, and development history.
- **[Release checklist](docs/release_checklist.md)** - Crate, demo-version,
GitHub Pages, and publication procedure.
## What is Linkage Blaze?
Linkage Blaze is a Rust-based domain-specific language (DSL) for making animated
jointed drawings. It works like 3D turtle graphics: move forward, turn, branch,
draw links, place joints, and add simple shapes such as disks and spheres.
Animate a few parameters, and the drawing moves.
The demos include robot arms, clocks, and motion-controlled skeletons. The
workspace targets microcontrollers through Device Envoy's CYD (Cheap Yellow
Display) APIs ([ESP32](https://docs.rs/device-envoy-esp/latest/device_envoy_esp/cyd/index.html)
, [RP](https://docs.rs/device-envoy-rp/latest/device_envoy_rp/cyd/index.html), [WASM](https://docs.rs/device-envoy-core/latest/device_envoy_core/wasm/struct.CydWasm.html)).
The default crate configuration is `no_std` and allocation-free, so figures
live in flash and animate on small microcontrollers. An opt-in `alloc` feature
adds heap-based conveniences where an allocator is available.
## Gallery
The live gallery is the main showcase: **[carlkcarlk.github.io/linkage-blaze/demos/](https://carlkcarlk.github.io/linkage-blaze/demos/)**
It shows preview images of each demo and links to the live, interactive WASM versions.
<a href="https://carlkcarlk.github.io/linkage-blaze/demos/"><img src="https://raw.githubusercontent.com/CarlKCarlK/linkage-blaze/main/crates/linkage-blaze/tests/assets/gallery.png" alt="Linkage Blaze demo gallery preview" width="800" /></a>
## Articles
- [Nine Rules for Compile-Time Work with Rust `const fn` (Part 1)](https://medium.com/gitconnected/nine-rules-for-compile-time-work-with-rust-const-fn-part-1-a29f7dd62b2f),
published in *Level Up Coding*, includes examples from Linkage Blaze and Device
Envoy.
- [Nine Rules for Compile-Time Work with Rust `const fn` (Part 2)](https://medium.com/gitconnected/nine-rules-for-compile-time-work-with-rust-const-fn-part-2-76ccd0e8a965)
uses Linkage Blaze to demonstrate a compile-time DSL and examines the practical
limits of `const fn`.
## Usage
`linkage-blaze` can run on embedded systems:
- It does not require the Rust standard library (`no_std`).
- It does not use heap allocation.
- ESP and RP applications can use the `LinkageFixed` access generated by
`linkage_file!`.
It can also run, with more features, in `std` runs:
- `alloc` enables owned parsing.
- `bvh` enables host-side APIs for reading Biovision Hierarchy (BVH)
motion-capture files.
```toml
[dependencies]
linkage-blaze = "<latest version>"
```
Replace `<latest version>` with the current release shown on
[crates.io](https://crates.io/crates/linkage-blaze).
Install the Biovision Hierarchy (BVH) converter with:
```bash
cargo install linkage-blaze --features bvh --bin bvh-to-lb
```
## Platform Examples
- **[Raspberry Pi Pico / RP](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-rp)** - Pico 1 and Pico 2 examples, including Pico W variants.
- **[ESP32](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-esp)** - Cheap Yellow Display examples across supported ESP32 families and boards.
- **[Browser / WASM](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-wasm)** - Browser builds behind the live gallery.
The platform examples and browser adapter use the workspace version but are not
separately published to crates.io. WASM applications provide their own
`cdylib` and may depend on `linkage-blaze` with the `alloc` feature.
## Quick Start
The core workflow is: construct one or more linkages, combine them, obtain a
borrowed [`LinkageView`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageView.html),
and evaluate that view for geometry or a final pose.
### 1. Construct a linkage from steps
[`LinkageFixed`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageFixed.html)
stores an allocation-free linkage in fixed-capacity arrays. Start at the origin,
optionally define normalized parameters, and append movement and drawing steps
with the fluent methods:
```rust
# use linkage_blaze::LinkageFixed;
const ARM: LinkageFixed<1, 1, 8> = LinkageFixed::start()
.define_param("shoulder", 0.5)
.yaw_param("shoulder", -90.0, 90.0)
.forward(3.0)
.mark("hand");
```
The const generic arguments are the parameter count, mark-slot count, and step
capacity. Unused step capacity is allowed.
### 2. Combine linkages
[`LinkageFixed::combine`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageFixed.html#method.combine)
appends another linkage without replaying its initial `Start` step:
```rust
# use linkage_blaze::LinkageFixed;
const BASE: LinkageFixed<0, 0, 2> = LinkageFixed::start().forward(2.0);
const TIP: LinkageFixed<0, 0, 2> = LinkageFixed::start().left(1.0);
const FIGURE: LinkageFixed<0, 0, 4> = BASE.combine(TIP.view());
```
The output type states the combined parameter, mark, and step capacities.
### 3. Evaluate the final pose
Call [`LinkageFixed::view`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageFixed.html#method.view)
to borrow a linkage, then pass one normalized value per parameter to
[`LinkageView::final_pose`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageView.html#method.final_pose):
```rust
# use linkage_blaze::{LinkageFixed, Vec3};
# fn main() -> Result<(), linkage_blaze::Error> {
const LINKAGE: LinkageFixed<1, 0, 4> = LinkageFixed::start()
.define_param("reach", 0.5)
.forward_param("reach", 1.0, 5.0);
let pose = LINKAGE.view().final_pose(&[0.5])?;
assert!(pose.position().is_close_to(&Vec3::from([3.0, 0.0, 0.0]), 1e-5));
# Ok(())
# }
```
### 4. Evaluate for rendering
[`LinkageView::draw_items_3d`](https://docs.rs/linkage-blaze/latest/linkage_blaze/struct.LinkageView.html#method.draw_items_3d)
evaluates strokes and shapes as an iterator of
[`render::Item3d`](https://docs.rs/linkage-blaze/latest/linkage_blaze/render/enum.Item3d.html)
values. A platform renderer can project and draw each item without allocating:
```rust
# use linkage_blaze::{LinkageFixed, render::Item3d};
# fn main() -> Result<(), linkage_blaze::Error> {
const LINKAGE: LinkageFixed<0, 0, 3> = LinkageFixed::start()
.forward(2.0)
.left(1.0);
let stroke_count = LINKAGE
.view()
.draw_items_3d(&[])?
.filter(|item| matches!(item, Item3d::Stroke(_)))
.count();
assert_eq!(stroke_count, 2);
# Ok(())
# }
```
See the complete [ESP32](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-esp),
[RP](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-rp),
and [WASM](https://github.com/CarlKCarlK/linkage-blaze/tree/main/crates/linkage-blaze-examples-wasm)
examples for display integration.
### 5. Save and import a large linkage
Put a long fluent expression in a `.lb.rs` asset file. The file contains one
`linkage![...]` expression with leading-dot methods:
```rust
# use linkage_blaze::{LinkageFixed, linkage};
# macro_rules! __linkage_blaze_start {
# () => { LinkageFixed::<1, 1, 4>::start() };
# }
# let linkage: LinkageFixed<1, 1, 4> =
linkage![
.define_param("reach", 0.5)
.forward_param("reach", 1.0, 5.0)
.mark("tip")
];
# assert_eq!(linkage.view().dof(), 1);
```
Import it with [`linkage_file!`](https://docs.rs/linkage-blaze/latest/linkage_blaze/macro.linkage_file.html).
The macro measures the asset at compile time and creates a module containing
its exact fixed type, value, and borrowed view. Choose `view()` for a
borrowed, allocation-free handle, `fixed()` when you need the compile-time
owner, or `buf()` under `alloc` when the linkage must be growable:
```text
use linkage_blaze::linkage_file;
linkage_file! {
figure {
file: "assets/figure.lb.rs",
}
}
type FigureFixed = figure::Fixed;
type FigureView = figure::View;
const FIGURE: FigureFixed = figure::fixed();
const FIGURE_VIEW: FigureView = figure::view();
#[cfg(feature = "alloc")]
let figure_buf: figure::Buf = figure::buf();
```
The import is a text excerpt because rustdoc cannot provide the external asset file at the macro call site. The repository's `linkage_file` integration test compile-checks the complete external-file import path.
### 6. Edit a large linkage
Open the [interactive Linkage Blaze editor](https://carlkcarlk.github.io/linkage-blaze/demos/editor/v2/)
to edit and preview `.lb.rs` assets. Open an existing file or paste an
expression, adjust its generated parameter controls while watching the 3D
preview, and use **Save** or **Save As** to write the edited `.lb.rs` file.
Editor output uses the same syntax as the allocator-backed parser, so it can be
checked before use:
```rust
# #[cfg(feature = "alloc")]
# fn main() -> Result<(), String> {
use linkage_blaze::LinkageBuf;
let edited_source = r#"
linkage![
.define_param("reach", 0.5)
.forward_param("reach", 1.0, 5.0)
.mark("tip")
]
"#;
let linkage = LinkageBuf::<1, 1>::from_lb_rs(edited_source)?;
assert_eq!(linkage.view().dof(), 1);
# Ok(())
# }
# #[cfg(not(feature = "alloc"))]
# fn main() {}
```
For motion-capture input, see the
[`bvh` module](https://docs.rs/linkage-blaze/latest/linkage_blaze/bvh/index.html)
and its Biovision Hierarchy conversion APIs.
## Policy on AI-assisted development and contributions
The use of AI tools is permitted for development and contributions to this repository. AI may be used as a productivity aid for drafting, exploration, and refactoring.
All code and documentation contributed to this repository must be reviewed, edited, and validated by a human contributor. AI tools are not a substitute for design judgment, testing, or responsibility for correctness.
[AGENTS.md](https://github.com/CarlKCarlK/linkage-blaze/blob/main/AGENTS.md) contains the general instructions and constraints given to AI tools used during development of this repository.
## License
Licensed under either:
- [MIT license](https://github.com/CarlKCarlK/linkage-blaze/blob/main/LICENSE-MIT)
- [Apache License, Version 2.0](https://github.com/CarlKCarlK/linkage-blaze/blob/main/LICENSE-APACHE)
at your option.