parent
aceba84fac
commit
b89849555f
@ -1,215 +1,236 @@
|
|||||||
/// TODO: Documentation
|
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::ops::AddAssign;
|
|
||||||
|
|
||||||
// TODO: Documentation
|
/// Fields are the basic unit of GCode.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
trait Field {
|
||||||
pub enum Direction {
|
/// An uppercase letter
|
||||||
Clockwise,
|
const LETTER: char;
|
||||||
AntiClockwise,
|
/// 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
|
/// Arguments, described by a letter and a value, belong to a field.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
pub struct Argument<'a> {
|
||||||
pub enum Tool {
|
letter: char,
|
||||||
Off,
|
value: &'a str,
|
||||||
On,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Documentation
|
macro_rules! field {
|
||||||
#[derive(Clone, PartialEq)]
|
($(#[$outer:meta])* $fieldName: ident {$letter: pat, $number: pat, $fraction: pat, {$($(#[$inner:meta])* $argument: ident : $type: ty), *} }) => {
|
||||||
pub enum Distance {
|
$(#[$outer])*
|
||||||
Absolute,
|
struct $fieldName {
|
||||||
Incremental,
|
$(
|
||||||
|
$(#[$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
|
field!(
|
||||||
#[derive(Default, PartialEq, Clone)]
|
/// Moves the head at the fastest possible speed to the desired speed.
|
||||||
pub struct Program(Vec<GCode>);
|
/// 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
|
field!(
|
||||||
impl std::ops::Deref for Program {
|
/// Typically used for "cutting" motion
|
||||||
type Target = [GCode];
|
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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: Documentation
|
field!(
|
||||||
fn deref(&self) -> &Self::Target {
|
/// This will keep the axes unmoving for the period of time in seconds specified by the P number.
|
||||||
self.0.deref()
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: Documentation
|
field!(
|
||||||
impl AddAssign for Program {
|
/// Stop spinning the spindle
|
||||||
fn add_assign(&mut self, mut other: Program) {
|
StopSpindle {
|
||||||
self.0.extend(other.0.drain(..));
|
'M', Some(5), None, {}
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
// TODO: Documentation
|
field!(
|
||||||
impl From<Vec<GCode>> for Program {
|
/// Signals the end of a program
|
||||||
fn from(v: Vec<GCode>) -> Self {
|
ProgramEnd {
|
||||||
Self(v)
|
'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 Field for Checksum {
|
||||||
impl Program {
|
const LETTER: char = '*';
|
||||||
pub fn push(&mut self, g: GCode) {
|
const NUMBER: Option<u16> = None;
|
||||||
self.0.push(g)
|
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
|
/// 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
|
||||||
macro_rules! write_if_some {
|
struct LineNumber {
|
||||||
($w:expr, $s:expr, $v:ident) => {
|
/// Line number
|
||||||
if let Some(v) = $v {
|
value: u16,
|
||||||
write!($w, $s, v)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Documentation
|
impl Field for LineNumber {
|
||||||
// Rudimentary regular expression GCode validator.
|
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
|
|
||||||
#[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: Documentation
|
||||||
// TODO: This function is too large
|
// TODO: This function is too large
|
||||||
pub fn program2gcode<W: Write>(p: &Program, mut w: W) -> io::Result<()> {
|
pub fn program2gcode<W: Write>(p: &Program, mut w: W) -> io::Result<()> {
|
||||||
use GCode::*;
|
|
||||||
let mut last_feedrate: Option<f64> = None;
|
let mut last_feedrate: Option<f64> = None;
|
||||||
for code in p.iter() {
|
let letter = '*';
|
||||||
match code {
|
let number = Some(0);
|
||||||
RapidPositioning { x, y } => {
|
let fraction = None;
|
||||||
if let (None, None) = (x, y) {
|
|
||||||
continue;
|
macro_rules! match_field {
|
||||||
}
|
($($fieldName: ident)*) => {
|
||||||
write!(w, "G0")?;
|
match (letter, number, fraction) {
|
||||||
write_if_some!(w, " X{}", x)?;
|
$(($fieldName::LETTER, $fieldName::NUMBER, $fieldName::FRACTION) => {
|
||||||
write_if_some!(w, " Y{}", y)?;
|
Some($fieldName::from_arguments(arguments))
|
||||||
writeln!(w)?;
|
},)*
|
||||||
}
|
_ => {None}
|
||||||
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")?;
|
|
||||||
}
|
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
for code in p.iter() {
|
||||||
}
|
match_field!(LineNumber);
|
||||||
|
|
||||||
#[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");
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue