Begin translation to macro-based GCode impl (does not compile)

master
Sameer Puri 5 years ago
parent aceba84fac
commit b89849555f

64
Cargo.lock generated

@ -142,11 +142,52 @@ dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "paste"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"paste-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "paste-impl"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quote"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.3.6"
@ -202,6 +243,7 @@ dependencies = [
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_geom 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
"paste 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"svgdom 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uom 0.27.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -230,6 +272,16 @@ dependencies = [
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termcolor"
version = "1.1.0"
@ -264,6 +316,11 @@ name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uom"
version = "0.27.0"
@ -335,7 +392,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lyon_geom 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76579b1f0a1ab9c3565c48faf951152d772d67ea35292fa9d36a991916b19a21"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum paste 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "092d791bf7847f70bbd49085489fba25fc2c193571752bff9e36e74e72403932"
"checksum paste-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "406c23fb4c45cc6f68a9bbabb8ec7bd6f8cfcbd17e9e8f72c2460282f8325729"
"checksum proc-macro-hack 0.5.14 (registry+https://github.com/rust-lang/crates.io-index)" = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420"
"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"checksum roxmltree 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "330d8f80a274bc3cb608908ee345970e7e24b96907f1ad69615a498bec57871c"
@ -345,11 +407,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum svgdom 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4ae7d6df72b3a36c0f003f1f8919c96be43d20d7c015f8dcf2f82531a4fc33b"
"checksum svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff"
"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum uom 0.27.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51fc04fb44bcb7806da71885872cb15d123b681e459a476ef8a0bab287bee0cd"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"

@ -13,3 +13,4 @@ log = "0"
env_logger = "0"
uom = "0"
regex = "1"
paste = "0"

@ -46,3 +46,7 @@ cat out.gcode
* What about a generic PPD driver for using a plotter as a printer? I thought about doing something like this where you package ghostscript + inkscape + svg2gcode but since plotter dimensions and capabilities vary, this is an exercise left to the reader for now.
## Reference Documents
* [RepRap G-code](https://reprap.org/wiki/G-code)
* [G-Code and M-Code Reference List for Milling](https://www.cnccookbook.com/g-code-m-code-reference-list-cnc-mills/)

@ -1,215 +1,236 @@
/// TODO: Documentation
use std::io::{self, Write};
use std::ops::AddAssign;
// TODO: Documentation
#[derive(Clone, PartialEq, Eq)]
pub enum Direction {
Clockwise,
AntiClockwise,
/// Fields are the basic unit of GCode.
trait Field {
/// An uppercase letter
const LETTER: char;
/// A number if the field has a fixed number.
const NUMBER: Option<u16>;
/// A fraction if the field has a fixed fraction following a fixed number.
const FRACTION: Option<u16>;
fn from_arguments<'a>(arguments: &[Argument<'a>]) -> Self;
fn into_arguments<'a>(&'a self) -> Vec<Argument<'a>>;
}
// TODO: Documentation
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Tool {
Off,
On,
/// Arguments, described by a letter and a value, belong to a field.
pub struct Argument<'a> {
letter: char,
value: &'a str,
}
// TODO: Documentation
#[derive(Clone, PartialEq)]
pub enum Distance {
Absolute,
Incremental,
macro_rules! field {
($(#[$outer:meta])* $fieldName: ident {$letter: pat, $number: pat, $fraction: pat, {$($(#[$inner:meta])* $argument: ident : $type: ty), *} }) => {
$(#[$outer])*
struct $fieldName {
$(
$(#[$inner])*
$argument: Option<$type>,
)*
}
paste::item! {
impl Field for $fieldName {
const LETTER: char = $letter;
const NUMBER: Option<u16> = $number;
const FRACTION: Option<u16> = $fraction;
fn from_arguments<'a>(arguments: &[Argument<'a>]) -> Self {
let mut field = Self {
$($argument: None,)*
};
for arg in arguments.iter() {
$(if arg.letter == stringify!([<$argument:upper>]).chars().next().unwrap() {
field.$argument = Some(arg.value.parse().unwrap());
})*
}
field
}
fn into_arguments<'a>(&'a self) -> Vec<Argument<'a>> {
let mut args = vec![];
$(
if let Some(value) = self.$argument {
args.push(Argument {
letter: stringify!([<$argument:upper>]).chars().next().unwrap(),
value: &value.to_string()
});
}
)*
args
}
}
}
};
}
// TODO: Documentation
#[derive(Default, PartialEq, Clone)]
pub struct Program(Vec<GCode>);
// TODO: Documentation
impl std::ops::Deref for Program {
type Target = [GCode];
// TODO: Documentation
fn deref(&self) -> &Self::Target {
self.0.deref()
field!(
/// Moves the head at the fastest possible speed to the desired speed.
/// Never enter a cut with rapid positioning.
/// Some older machines may "dog leg" rapid positioning, moving one axis at a time.
RapidPositioning {
'G', Some(0), None, {
x: f64,
y: f64,
z: f64,
e: f64,
f: f64,
h: f64,
r: f64,
s: f64,
a: f64,
b: f64,
c: f64
}
}
});
// TODO: Documentation
impl AddAssign for Program {
fn add_assign(&mut self, mut other: Program) {
self.0.extend(other.0.drain(..));
field!(
/// Typically used for "cutting" motion
LinearInterpolation {
'G', Some(1), None, {
x: f64,
y: f64,
z: f64,
e: f64,
f: f64,
h: f64,
r: f64,
s: f64,
a: f64,
b: f64,
c: f64
}
});
field!(
/// This will keep the axes unmoving for the period of time in seconds specified by the P number.
Dwell {
'G', Some(4), None, {
/// Time in seconds
p: f64
}
});
field!(
/// Use inches for length units
UnitsInches {
'G', Some(20), None, {}
});
field!(
/// Use millimeters for length units
UnitsMillimeters {
'G', Some(21), None, {}
});
field!(
/// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
AbsoluteDistanceMode {
'G', Some(90), None, {}
});
field!(
/// In incremental distance mode, axis numbers usually represent increments from the current values of the numbers.
IncrementalDistanceMode {
'G', Some(91), None, {}
});
field!(
/// Start spinning the spindle clockwise with speed `p`
StartSpindleClockwise {
'M', Some(3), None, {
/// Speed
p: f64
}
}
);
field!(
/// Start spinning the spindle counterclockwise with speed `p`
StartSpindleCounterclockwise {
'M', Some(4), None, {
/// Speed
p: f64
}
}
);
field!(
/// Stop spinning the spindle
StopSpindle {
'M', Some(5), None, {}
}
);
field!(
/// Signals the end of a program
ProgramEnd {
'M', Some(20), None, {}
}
);
/// Checksums are used by some GCode generators at the end of each line
struct Checksum {
/// Checksum value
value: u8,
}
// TODO: Documentation
impl From<Vec<GCode>> for Program {
fn from(v: Vec<GCode>) -> Self {
Self(v)
impl Field for Checksum {
const LETTER: char = '*';
const NUMBER: Option<u16> = None;
const FRACTION: Option<u16> = None;
fn from_arguments<'a>(arguments: &[Argument<'a>]) -> Self {
Self { value: 0 }
}
fn into_arguments(&self) -> Vec<Argument> {
vec![]
}
}
// TODO: Documentation
impl Program {
pub fn push(&mut self, g: GCode) {
self.0.push(g)
}
/// A line number is the letter N followed by an integer (with no sign) between 0 and 99999 written with no more than five digits
struct LineNumber {
/// Line number
value: u16,
}
// TODO: Documentation
macro_rules! write_if_some {
($w:expr, $s:expr, $v:ident) => {
if let Some(v) = $v {
write!($w, $s, v)
} else {
Ok(())
impl Field for LineNumber {
const LETTER: char = 'N';
const NUMBER: Option<u16> = None;
const FRACTION: Option<u16> = None;
fn from_arguments<'a>(arguments: &[Argument<'a>]) -> Self {
Self { value: 0 }
}
fn into_arguments(&self) -> Vec<Argument> {
vec![]
}
};
}
// TODO: Documentation
// Rudimentary regular expression GCode validator.
/// Rudimentary regular expression GCode validator.
pub fn validate_gcode(gcode: &&str) -> bool {
use regex::Regex;
let re = Regex::new(r##"^(?:(?:%|\(.*\)|(?:[A-Z^E^U][+-]?\d+(?:\.\d*)?))\h*)*$"##).unwrap();
gcode.lines().all(|line| re.is_match(line))
}
// TODO: Documentation
#[derive(Clone, PartialEq)]
pub enum GCode {
RapidPositioning {
x: Option<f64>,
y: Option<f64>,
},
LinearInterpolation {
x: Option<f64>,
y: Option<f64>,
z: Option<f64>,
f: Option<f64>,
},
Dwell {
p: f64,
},
UnitsInches,
UnitsMillimeters,
ProgramEnd,
StartSpindle {
d: Direction,
s: f64,
},
StopSpindle,
DistanceMode(Distance),
Comment(Box<String>),
Raw(Box<String>),
}
// TODO: Documentation
// TODO: This function is too large
pub fn program2gcode<W: Write>(p: &Program, mut w: W) -> io::Result<()> {
use GCode::*;
let mut last_feedrate: Option<f64> = None;
for code in p.iter() {
match code {
RapidPositioning { x, y } => {
if let (None, None) = (x, y) {
continue;
}
write!(w, "G0")?;
write_if_some!(w, " X{}", x)?;
write_if_some!(w, " Y{}", y)?;
writeln!(w)?;
}
LinearInterpolation { x, y, z, f } => {
if let (None, None, None, None) = (x, y, z, f) {
continue;
}
let f = match (last_feedrate, f) {
(None, None) => {
return Err(io::Error::new(
io::ErrorKind::Other,
"Linear interpolation without previously set feedrate",
))
}
(Some(last), Some(new)) => {
if (last - *new).abs() < std::f64::EPSILON {
last_feedrate = Some(*new);
Some(new)
} else {
None
}
}
(None, Some(new)) => {
last_feedrate = Some(*new);
Some(new)
}
(Some(_), None) => None,
};
write!(w, "G1")?;
write_if_some!(w, " X{}", x)?;
write_if_some!(w, " Y{}", y)?;
write_if_some!(w, " Z{}", z)?;
write_if_some!(w, " F{}", f)?;
writeln!(w)?;
}
Dwell { p } => {
writeln!(w, "G4 P{}", p)?;
}
UnitsInches => {
writeln!(w, "G20")?;
}
UnitsMillimeters => {
writeln!(w, "G21")?;
}
ProgramEnd => {
writeln!(w, "M20")?;
let mut last_feedrate: Option<f64> = None;
let letter = '*';
let number = Some(0);
let fraction = None;
macro_rules! match_field {
($($fieldName: ident)*) => {
match (letter, number, fraction) {
$(($fieldName::LETTER, $fieldName::NUMBER, $fieldName::FRACTION) => {
Some($fieldName::from_arguments(arguments))
},)*
_ => {None}
}
StartSpindle { d, s } => {
let d = match d {
Direction::Clockwise => 3,
Direction::AntiClockwise => 4,
};
writeln!(w, "M{} S{}", d, s)?;
}
StopSpindle => {
writeln!(w, "M5")?;
}
DistanceMode(mode) => {
writeln!(
w,
"G{}",
match mode {
Distance::Absolute => 90,
Distance::Incremental => 91,
}
)?;
}
Comment(name) => {
writeln!(w, "({})", name)?;
}
Raw(raw) => {
writeln!(w, "{}", raw)?;
}
}
for code in p.iter() {
match_field!(LineNumber);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_gcode() {
panic!("TODO: basic passing test");
}
#[test]
fn test_program2gcode() {
panic!("TODO: basic passing test");
}
}

@ -1,9 +1,27 @@
/// TODO: Documentation
use crate::code::*;
// TODO: Documentation
// Generic machine state simulation, assuming nothing is known about the machine when initialized.
//// Direction of the machine spindle
#[derive(Clone, PartialEq, Eq)]
pub enum Direction {
Clockwise,
Counterclockwise,
}
/// Whether the tool is active (i.ee. cutting)
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Tool {
Off,
On,
}
/// The distance mode for movement commands
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Distance {
Absolute,
Relative,
}
/// Generic machine state simulation, assuming nothing is known about the machine when initialized.
pub struct Machine {
tool_state: Option<Tool>,
distance_mode: Option<Distance>,
@ -11,9 +29,8 @@ pub struct Machine {
pub tool_off_action: Vec<GCode>,
}
// TODO: Documentation
// Assigns reasonable default settings that apply to most gcode applications.
impl Default for Machine {
/// Assigns reasonable default settings that apply to most gcode applications.
impl Machine {
fn default() -> Self {
Self {
tool_state: None,
@ -66,13 +83,13 @@ impl Machine {
if is_absolute {
self.absolute()
} else {
self.incremental()
self.relative()
}
}
// Outputs gcode command to use absolute motion
pub fn absolute(&mut self) -> Vec<GCode> {
if self.distance_mode == Some(Distance::Incremental) || self.distance_mode == None {
if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None {
self.distance_mode = Some(Distance::Absolute);
vec![GCode::DistanceMode(Distance::Absolute)]
} else {
@ -80,43 +97,14 @@ impl Machine {
}
}
// Outputs gcode command to use relative motion
pub fn incremental(&mut self) -> Vec<GCode> {
/// Set the distance mode to relative
pub fn relative(&mut self) -> Vec<GCode> {
if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None {
self.distance_mode = Some(Distance::Incremental);
vec![GCode::DistanceMode(Distance::Incremental)]
self.distance_mode = Some(Distance::Relative);
vec![GCode::DistanceMode(Distance::Relative)]
} else {
vec![]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_on() {
panic!("TODO: basic passing test");
}
#[test]
fn test_tool_off() {
panic!("TODO: basic passing test");
}
#[test]
fn test_distance() {
panic!("TODO: basic passing test");
}
#[test]
fn test_absolute() {
panic!("TODO: basic passing test");
}
#[test]
fn test_incremental() {
panic!("TODO: basic passing test");
}
}

Loading…
Cancel
Save