mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 10:03:51 +00:00
* 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.
48 lines
2.0 KiB
Rust
48 lines
2.0 KiB
Rust
//! Graphtec GP-GL. This module is the plain language as Graphtec's own cutters take it; the
|
|
//! dialect of the Silhouette machines, with its setup preamble, registration marks and device
|
|
//! queries, is [`silhouette`].
|
|
//!
|
|
//! The plain stream is `!<speed>` when an operation sets one, then per path `M<down>,<right>` to
|
|
//! its start and `D<down>,<right>` to each further point, then `M<below>,0` to park below the cut.
|
|
//! Every command ends with ETX. Coordinates are feed axis first, in steps of 0.1 mm, which must be
|
|
//! the GP-GL step size set on the machine's panel. Force is set on the panel.
|
|
|
|
pub mod silhouette;
|
|
|
|
use crate::{Command, Error, Framing, Job, Program, units};
|
|
|
|
/// Command terminator: every GP-GL command is followed by ETX (0x03).
|
|
pub const ETX: u8 = 0x03;
|
|
|
|
/// Plain GP-GL steps per mm (0.1 mm steps).
|
|
pub const UNITS_PER_MM: f64 = 10.0;
|
|
|
|
/// Encodes `job` as plain GP-GL. 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 (dm, rm) = (job.origin_down_mm, job.origin_right_mm);
|
|
let g = |&(x, y): &(f64, f64)| (units(y + dm, UNITS_PER_MM), units(x + rm, UNITS_PER_MM));
|
|
let mut c = Vec::new();
|
|
for (op, paths) in job.operations.iter().zip(&prepared.operations) {
|
|
if let Some(s) = op.settings.speed {
|
|
c.push(Command::Raw(format!("!{s}")));
|
|
}
|
|
for path in paths {
|
|
let Some((first, rest)) = path.split_first() else {
|
|
continue;
|
|
};
|
|
let (d, r) = g(first);
|
|
c.push(Command::Raw(format!("M{d},{r}")));
|
|
for q in rest {
|
|
let (d, r) = g(q);
|
|
c.push(Command::Raw(format!("D{d},{r}")));
|
|
}
|
|
}
|
|
}
|
|
// Park below the cut, head up.
|
|
c.push(Command::Raw(format!("M{},0", units(prepared.bbox_mm.2 + job.end_feed_mm.max(0.0), UNITS_PER_MM))));
|
|
Ok(Program { commands: c, bbox_mm: Some(prepared.bbox_mm), framing: Framing::Etx, registration_wait: None })
|
|
}
|