parent
b89849555f
commit
f7e037cfdd
@ -1,236 +1,343 @@
|
|||||||
|
use core::convert::TryFrom;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
/// Fields are the basic unit of GCode.
|
/// Collapses GCode words into higher-level commands
|
||||||
trait Field {
|
/// Relies on the first word being a command.
|
||||||
/// An uppercase letter
|
pub struct CommandVec {
|
||||||
const LETTER: char;
|
pub inner: Vec<Word>,
|
||||||
/// 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>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arguments, described by a letter and a value, belong to a field.
|
impl Default for CommandVec {
|
||||||
pub struct Argument<'a> {
|
fn default() -> Self {
|
||||||
letter: char,
|
Self {
|
||||||
value: &'a str,
|
inner: vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! field {
|
impl IntoIterator for CommandVec {
|
||||||
($(#[$outer:meta])* $fieldName: ident {$letter: pat, $number: pat, $fraction: pat, {$($(#[$inner:meta])* $argument: ident : $type: ty), *} }) => {
|
type Item = Command;
|
||||||
$(#[$outer])*
|
type IntoIter = CommandVecIntoIterator;
|
||||||
struct $fieldName {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
CommandVecIntoIterator {
|
||||||
|
vec: self,
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommandVecIntoIterator {
|
||||||
|
vec: CommandVec,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for CommandVecIntoIterator {
|
||||||
|
type Item = Command;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.vec.inner.len() == self.index {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i = self.index + 1;
|
||||||
|
while i < self.vec.inner.len() {
|
||||||
|
if CommandWord::is_command(&self.vec.inner[i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
Command::try_from(&self.vec.inner[self.index..i]).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fundamental unit of GCode: a value preceded by a descriptive letter.
|
||||||
|
/// A float is used here to encompass all the possible variations of a value.
|
||||||
|
/// Some flavors of GCode may allow strings, but that is currently not supported.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
|
pub struct Word {
|
||||||
|
pub letter: char,
|
||||||
|
pub value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! command {
|
||||||
|
($commandWord: expr, {$($argument: ident : $value: expr,)*}) => {
|
||||||
|
paste::expr! (Command::new($commandWord, vec![$(Word {
|
||||||
|
letter: stringify!([<$argument:upper>]).chars().next().unwrap(),
|
||||||
|
value: $value as f64,
|
||||||
|
},)*]))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! commands {
|
||||||
|
($($(#[$outer:meta])* $commandName: ident {$letter: pat, $number: pat, $fraction: pat, {$($(#[$inner:meta])* $argument: ident), *} },)*) => {
|
||||||
|
|
||||||
|
/// Commands are the operational unit of GCode
|
||||||
|
/// They consist of an identifying word followed by arguments
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Command {
|
||||||
|
command_word: CommandWord,
|
||||||
|
arguments: Vec<Word>
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::item! {
|
||||||
|
impl Command {
|
||||||
|
pub fn new(command_word: CommandWord, mut arguments: Vec<Word>) -> Self {
|
||||||
|
Self {
|
||||||
|
command_word: command_word.clone(),
|
||||||
|
arguments: arguments.drain(..).filter(|w| {
|
||||||
|
match command_word {
|
||||||
|
$(CommandWord::$commandName => match w.letter.to_lowercase() {
|
||||||
|
$($argument => true,)*
|
||||||
|
_ => false
|
||||||
|
},)*
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, argument: Word) {
|
||||||
|
match self.command_word {
|
||||||
|
$(CommandWord::$commandName => match argument.letter.to_lowercase() {
|
||||||
|
$($argument => {
|
||||||
|
self.arguments.push(argument);
|
||||||
|
})*
|
||||||
|
_ => {}
|
||||||
|
},)*
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::item! {
|
||||||
|
impl Into<Vec<Word>> for Command {
|
||||||
|
fn into(self) -> Vec<Word> {
|
||||||
|
let mut args = self.arguments;
|
||||||
|
args.insert(0, self.command_word.into());
|
||||||
|
args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paste::item! {
|
||||||
|
impl TryFrom<&[Word]> for Command {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(words: &[Word]) -> Result<Self, ()> {
|
||||||
|
let command_word = CommandWord::try_from(&words[0])?;
|
||||||
|
let mut arguments = Vec::with_capacity(words.len() - 1);
|
||||||
|
for i in 1..words.len() {
|
||||||
|
match command_word {
|
||||||
|
$(CommandWord::$commandName => match words[i].letter.to_lowercase() {
|
||||||
|
$($argument => {
|
||||||
|
arguments.push(words[i].clone());
|
||||||
|
})*
|
||||||
|
_ => {}
|
||||||
|
},)*
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
command_word,
|
||||||
|
arguments
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub enum CommandWord {
|
||||||
$(
|
$(
|
||||||
$(#[$inner])*
|
$(#[$outer])*
|
||||||
$argument: Option<$type>,
|
$commandName,
|
||||||
)*
|
)*
|
||||||
|
/// A comment is a special command: it is a semicolon followed by text until the end of the line
|
||||||
|
Comment(Box<String>),
|
||||||
|
/// Letter N followed by an integer (with no sign) between 0 and 99999 written with no more than five digits
|
||||||
|
LineNumber(u16),
|
||||||
|
/// Byte-sized checksums are used by some GCode generators at the end of each line
|
||||||
|
Checksum(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
paste::item! {
|
paste::item! {
|
||||||
impl Field for $fieldName {
|
impl CommandWord {
|
||||||
const LETTER: char = $letter;
|
pub fn is_command(word: &Word) -> bool {
|
||||||
const NUMBER: Option<u16> = $number;
|
let number = word.value as u16;
|
||||||
const FRACTION: Option<u16> = $fraction;
|
let fraction_numeric =
|
||||||
fn from_arguments<'a>(arguments: &[Argument<'a>]) -> Self {
|
f64::from_bits(word.value.fract().to_bits() & 0x00_00_FF_FF_FF_FF_FF_FF) as u16;
|
||||||
let mut field = Self {
|
let fraction = if fraction_numeric == 0 {
|
||||||
$($argument: None,)*
|
None
|
||||||
|
} else {
|
||||||
|
Some(fraction_numeric)
|
||||||
};
|
};
|
||||||
for arg in arguments.iter() {
|
match (word.letter, number, fraction) {
|
||||||
$(if arg.letter == stringify!([<$argument:upper>]).chars().next().unwrap() {
|
$(($letter, $number, $fraction) => true,)*
|
||||||
field.$argument = Some(arg.value.parse().unwrap());
|
('*', _checksum, None) => true,
|
||||||
})*
|
('N', _line_number, None) => true,
|
||||||
|
(_, _, _) => false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
paste::item! {
|
||||||
|
impl TryFrom<&Word> for CommandWord {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(word: &Word) -> Result<Self, ()> {
|
||||||
|
let number = word.value as u16;
|
||||||
|
let fraction_numeric =
|
||||||
|
f64::from_bits(word.value.fract().to_bits() & 0x00_00_FF_FF_FF_FF_FF_FF) as u16;
|
||||||
|
let fraction = if fraction_numeric == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(fraction_numeric)
|
||||||
|
};
|
||||||
|
match (word.letter, number, fraction) {
|
||||||
|
$(($letter, $number, $fraction) => Ok(Self::$commandName),)*
|
||||||
|
('*', checksum, None) => Ok(Self::Checksum(checksum as u8)),
|
||||||
|
('N', line_number, None) => Ok(Self::LineNumber(line_number)),
|
||||||
|
(_, _, _) => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paste::item!{
|
||||||
|
impl Into<Word> for CommandWord {
|
||||||
|
fn into(self) -> Word {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Self::$commandName {} => Word {
|
||||||
|
letter: $letter,
|
||||||
|
// TODO: fix fraction
|
||||||
|
value: $number as f64 + ($fraction.unwrap_or(0) as f64)
|
||||||
|
},
|
||||||
)*
|
)*
|
||||||
args
|
Self::Checksum(value) => Word {
|
||||||
|
letter: '*',
|
||||||
|
value: value as f64
|
||||||
|
},
|
||||||
|
Self::LineNumber(value) => Word {
|
||||||
|
letter: 'N',
|
||||||
|
value: value as f64
|
||||||
|
},
|
||||||
|
Self::Comment(_string) => Word {
|
||||||
|
letter: ';',
|
||||||
|
value: 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
field!(
|
commands!(
|
||||||
/// Moves the head at the fastest possible speed to the desired speed.
|
/// Moves the head at the fastest possible speed to the desired speed
|
||||||
/// Never enter a cut with rapid positioning.
|
/// Never enter a cut with rapid positioning
|
||||||
/// Some older machines may "dog leg" rapid positioning, moving one axis at a time.
|
/// Some older machines may "dog leg" rapid positioning, moving one axis at a time
|
||||||
RapidPositioning {
|
RapidPositioning {
|
||||||
'G', Some(0), None, {
|
'G', 0, None, {
|
||||||
x: f64,
|
x,
|
||||||
y: f64,
|
y,
|
||||||
z: f64,
|
z,
|
||||||
e: f64,
|
e,
|
||||||
f: f64,
|
f,
|
||||||
h: f64,
|
h,
|
||||||
r: f64,
|
r,
|
||||||
s: f64,
|
s,
|
||||||
a: f64,
|
a,
|
||||||
b: f64,
|
b,
|
||||||
c: f64
|
c
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Typically used for "cutting" motion
|
/// Typically used for "cutting" motion
|
||||||
LinearInterpolation {
|
LinearInterpolation {
|
||||||
'G', Some(1), None, {
|
'G', 1, None, {
|
||||||
x: f64,
|
x,
|
||||||
y: f64,
|
y,
|
||||||
z: f64,
|
z,
|
||||||
e: f64,
|
e,
|
||||||
f: f64,
|
f,
|
||||||
h: f64,
|
h,
|
||||||
r: f64,
|
r,
|
||||||
s: f64,
|
s,
|
||||||
a: f64,
|
a,
|
||||||
b: f64,
|
b,
|
||||||
c: f64
|
c
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
/// This will keep the axes unmoving for the period of time in seconds specified by the P number
|
||||||
field!(
|
|
||||||
/// This will keep the axes unmoving for the period of time in seconds specified by the P number.
|
|
||||||
Dwell {
|
Dwell {
|
||||||
'G', Some(4), None, {
|
'G', 4, None, {
|
||||||
/// Time in seconds
|
/// Time in seconds
|
||||||
p: f64
|
p
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Use inches for length units
|
/// Use inches for length units
|
||||||
UnitsInches {
|
UnitsInches {
|
||||||
'G', Some(20), None, {}
|
'G', 20, None, {}
|
||||||
});
|
},
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Use millimeters for length units
|
/// Use millimeters for length units
|
||||||
UnitsMillimeters {
|
UnitsMillimeters {
|
||||||
'G', Some(21), None, {}
|
'G', 21, None, {}
|
||||||
});
|
},
|
||||||
|
|
||||||
field!(
|
|
||||||
/// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
|
/// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
|
||||||
AbsoluteDistanceMode {
|
AbsoluteDistanceMode {
|
||||||
'G', Some(90), None, {}
|
'G', 90, None, {}
|
||||||
});
|
},
|
||||||
|
/// In relative distance mode, axis numbers usually represent increments from the current values of the numbers
|
||||||
field!(
|
RelativeDistanceMode {
|
||||||
/// In incremental distance mode, axis numbers usually represent increments from the current values of the numbers.
|
'G', 91, None, {}
|
||||||
IncrementalDistanceMode {
|
},
|
||||||
'G', Some(91), None, {}
|
|
||||||
});
|
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Start spinning the spindle clockwise with speed `p`
|
/// Start spinning the spindle clockwise with speed `p`
|
||||||
StartSpindleClockwise {
|
StartSpindleClockwise {
|
||||||
'M', Some(3), None, {
|
'M', 3, None, {
|
||||||
/// Speed
|
/// Speed
|
||||||
p: f64
|
p
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Start spinning the spindle counterclockwise with speed `p`
|
/// Start spinning the spindle counterclockwise with speed `p`
|
||||||
StartSpindleCounterclockwise {
|
StartSpindleCounterclockwise {
|
||||||
'M', Some(4), None, {
|
'M', 4, None, {
|
||||||
/// Speed
|
/// Speed
|
||||||
p: f64
|
p
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Stop spinning the spindle
|
/// Stop spinning the spindle
|
||||||
StopSpindle {
|
StopSpindle {
|
||||||
'M', Some(5), None, {}
|
'M', 5, None, {}
|
||||||
}
|
},
|
||||||
);
|
|
||||||
|
|
||||||
field!(
|
|
||||||
/// Signals the end of a program
|
/// Signals the end of a program
|
||||||
ProgramEnd {
|
ProgramEnd {
|
||||||
'M', Some(20), None, {}
|
'M', 20, None, {}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Checksums are used by some GCode generators at the end of each line
|
/// Rudimentary regular expression GCode validator
|
||||||
struct Checksum {
|
|
||||||
/// Checksum value
|
|
||||||
value: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
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![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
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![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rudimentary regular expression GCode validator.
|
|
||||||
pub fn validate_gcode(gcode: &&str) -> bool {
|
pub fn validate_gcode(gcode: &&str) -> bool {
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
let re = Regex::new(r##"^(?:(?:%|\(.*\)|(?:[A-Z^E^U][+-]?\d+(?:\.\d*)?))\h*)*$"##).unwrap();
|
let re = Regex::new(r##"^(?:(?:%|\(.*\)|(?:[A-Z^E^U][+-]?\d+(?:\.\d*)?))\h*)*$"##).unwrap();
|
||||||
gcode.lines().all(|line| re.is_match(line))
|
gcode.lines().all(|line| re.is_match(line))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Documentation
|
/// Writes a GCode program (or sequence) to a Writer
|
||||||
// TODO: This function is too large
|
pub fn program2gcode<W: Write>(program: Vec<Command>, mut w: W) -> io::Result<()> {
|
||||||
pub fn program2gcode<W: Write>(p: &Program, mut w: W) -> io::Result<()> {
|
for command in program.into_iter() {
|
||||||
|
match &command.command_word {
|
||||||
let mut last_feedrate: Option<f64> = None;
|
CommandWord::Comment(string) => {
|
||||||
let letter = '*';
|
writeln!(w, ";{}", string)?;
|
||||||
let number = Some(0);
|
},
|
||||||
let fraction = None;
|
_other => {
|
||||||
|
let words: Vec<Word> = command.into();
|
||||||
macro_rules! match_field {
|
let mut it = words.iter();
|
||||||
($($fieldName: ident)*) => {
|
if let Some(command_word) = it.next() {
|
||||||
match (letter, number, fraction) {
|
write!(w, "{}{}", command_word.letter, command_word.value)?;
|
||||||
$(($fieldName::LETTER, $fieldName::NUMBER, $fieldName::FRACTION) => {
|
for word in it {
|
||||||
Some($fieldName::from_arguments(arguments))
|
write!(w, " {}{} ", word.letter, word.value)?;
|
||||||
},)*
|
}
|
||||||
_ => {None}
|
writeln!(w, "")?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
for code in p.iter() {
|
|
||||||
match_field!(LineNumber);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue