Files
craft-libs/crates/plotter/src/dmpl.rs
T
Dylan M. Taylor 5375f49ffc Add craft-plotter (HP-GL, DMPL, GP-GL); craft-gcode: bare output and Grbl replies (#863)
* craft-gcode: bare output for line-by-line senders, and Grbl replies

Writer::bare writes the same moves as Writer::new with every line a
command: no comments, coordinates at up to three decimals without
trailing zeros, M5 at the start, and the return to the origin at the
end. A sender that waits for an ok after each line can stream it as is.
Writer::set_feed changes the feed rate between contours.

The new grbl module has the real-time command bytes (status query, feed
hold, cycle start, soft reset) and the parsers of the controller's
answers: the state of a status report (read up to MAX_REPLY_LEN bytes)
and the ok / error / ALARM acknowledgement of a line. No I/O.

* Add craft-plotter: HP-GL, DMPL and GP-GL from a job in millimetres

One crate for the command languages of vinyl cutters and pen plotters,
one module per language: hpgl, dmpl, gpgl (plain) and gpgl::silhouette
(the Silhouette dialect in its three generations, with registration
marks, the device queries and the parsers of their replies).

A Job is ordered operations: polylines in millimetres plus tool
settings. Each encode gives a Program: the commands, their framing, and
where a sender waits for the registration marks. The crate does no I/O
and builds for wasm.

A job is refused past 64 operations, 2,000,000 points, 10 passes or a
2 mm overcut, or when a value is not finite. Replies are read up to 256
bytes and report at most 8 tool holders. Every limit has a test, and
tests/golden.rs fixes the exact commands of each language.

The README records where the protocol facts come from.

* craft-plotter: optional serde for Mat, as its short name

A caller that keeps a mat in a settings file or a machine table needs to
read and write it. The serde feature serializes Mat as its short name
("12x24") and refuses an unknown name. Off by default; the crate still
has thiserror as its only required dependency.
2026-11-11 17:29:14 -05:00

41 lines
1.6 KiB
Rust

//! Houston Instrument DMPL, as sign cutters take it.
//!
//! The stream is `;:` and `EC1` (0.001 in resolution), then per path `Ux,y` to its start and one
//! `Dx,y,…` through the rest, then `U0,0` and `@`. One command per line. The origin is
//! bottom-left and y points up, so the design is flipped about its bottom edge. Tool holders,
//! force and speed are not addressed.
//!
//! This framing was not checked against a published manual: treat it as experimental.
use crate::{Command, Error, Framing, Job, Program, y_up};
/// Device units per mm under `EC1`: 0.001 in.
pub const UNITS_PER_MM: f64 = 1.0 / 0.0254;
/// Encodes `job` as DMPL. A job with no geometry gives an empty program.
pub fn encode(job: &Job) -> Result<Program, Error> {
let Some(prepared) = job.prepare()? else {
return Ok(Program::default());
};
let p = y_up(job, prepared.bbox_mm.2, UNITS_PER_MM);
let mut c = vec![Command::Raw(";:".to_string()), Command::Raw("EC1".to_string())];
for path in prepared.operations.iter().flatten() {
let Some((first, rest)) = path.split_first() else {
continue;
};
let (x, y) = p(first);
c.push(Command::Raw(format!("U{x},{y}")));
let pts: Vec<String> = rest
.iter()
.map(|q| {
let (x, y) = p(q);
format!("{x},{y}")
})
.collect();
c.push(Command::Raw(format!("D{}", pts.join(","))));
}
c.push(Command::Raw("U0,0".to_string()));
c.push(Command::Raw("@".to_string()));
Ok(Program { commands: c, bbox_mm: Some(prepared.bbox_mm), framing: Framing::Newline, registration_wait: None })
}