reduce string allocations to minimum possible w/ g-code v0.1.1

master
Sameer Puri 4 years ago
parent 126217a21f
commit 178448be7b

9
Cargo.lock generated

@ -133,13 +133,12 @@ checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e"
[[package]] [[package]]
name = "g-code" name = "g-code"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60958e3fe54d293ae76fb1e0c433161595a8dbe9c1c74663b24f76b9f98b962c" checksum = "a4b0866fca554553ea6d676c69ce31f6260ceb5680d0e8bb3bf6711f89799a38"
dependencies = [ dependencies = [
"codespan", "codespan",
"codespan-reporting", "codespan-reporting",
"lazy_static",
"num", "num",
"num-rational", "num-rational",
"paste", "paste",
@ -178,9 +177,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.93" version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]] [[package]]
name = "log" name = "log"

@ -6,7 +6,7 @@ edition = "2018"
description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine." description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine."
[dependencies] [dependencies]
g-code = "0.1.0" g-code = "0.1.1"
lyon_geom = "0" lyon_geom = "0"
euclid = "0.22" euclid = "0.22"
structopt = "0.3" structopt = "0.3"

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
use g_code::{command, emit::Token}; use g_code::{command, emit::Token};
@ -10,7 +11,6 @@ use svgtypes::{
LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox, LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox,
}; };
use crate::machine::*;
use crate::turtle::*; use crate::turtle::*;
/// High-level output options /// High-level output options
@ -34,9 +34,11 @@ impl Default for ProgramOptions {
} }
} }
pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Vec<Token> { pub fn svg2program<'input>(
let mut turtle = Turtle::new(mach); doc: &Document,
options: ProgramOptions,
turtle: &'input mut Turtle<'input>,
) -> Vec<Token<'input>> {
let mut program = command!(UnitsMillimeters {}) let mut program = command!(UnitsMillimeters {})
.into_token_vec() .into_token_vec()
.drain(..) .drain(..)
@ -123,9 +125,9 @@ pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Ve
comment += &node_name(&node); comment += &node_name(&node);
program.push(Token::Comment { program.push(Token::Comment {
is_inline: false, is_inline: false,
inner: comment, inner: Cow::Owned(comment),
}); });
program.extend(apply_path(&mut turtle, &options, d)); program.extend(apply_path(turtle, &options, d));
} else { } else {
warn!("There is a path node containing no actual path: {:?}", node); warn!("There is a path node containing no actual path: {:?}", node);
} }
@ -188,7 +190,11 @@ fn width_and_height_into_transform(
} }
} }
fn apply_path(turtle: &mut Turtle, options: &ProgramOptions, path: &str) -> Vec<Token> { fn apply_path<'a, 'input>(
turtle: &'a mut Turtle<'input>,
options: &ProgramOptions,
path: &str,
) -> Vec<Token<'input>> {
use PathSegment::*; use PathSegment::*;
PathParser::from(path) PathParser::from(path)
.map(|segment| segment.expect("could not parse path segment")) .map(|segment| segment.expect("could not parse path segment"))

@ -1,8 +1,4 @@
use g_code::{ use g_code::{command, emit::Token, parse::ast::Snippet};
command,
emit::Token,
parse::{ast::Snippet, token::Field},
};
/// Whether the tool is active (i.e. cutting) /// Whether the tool is active (i.e. cutting)
#[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@ -52,13 +48,13 @@ pub struct Machine<'input> {
impl<'input> Machine<'input> { impl<'input> Machine<'input> {
/// Output gcode to turn the tool on. /// Output gcode to turn the tool on.
pub fn tool_on<'a>(&'a mut self) -> Vec<Token> { pub fn tool_on(&mut self) -> Vec<Token<'input>> {
if self.tool_state == Some(Tool::Off) || self.tool_state == None { if self.tool_state == Some(Tool::Off) || self.tool_state == None {
self.tool_state = Some(Tool::On); self.tool_state = Some(Tool::On);
self.tool_on_action self.tool_on_action
.iter() .iter()
.flat_map(|s| s.iter_fields()) .flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f)) .map(Token::from)
.collect() .collect()
} else { } else {
vec![] vec![]
@ -66,36 +62,36 @@ impl<'input> Machine<'input> {
} }
/// Output gcode to turn the tool off. /// Output gcode to turn the tool off.
pub fn tool_off<'a>(&'a mut self) -> Vec<Token> { pub fn tool_off(&mut self) -> Vec<Token<'input>> {
if self.tool_state == Some(Tool::On) || self.tool_state == None { if self.tool_state == Some(Tool::On) || self.tool_state == None {
self.tool_state = Some(Tool::Off); self.tool_state = Some(Tool::Off);
self.tool_off_action self.tool_off_action
.iter() .iter()
.flat_map(|s| s.iter_fields()) .flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f)) .map(Token::from)
.collect() .collect()
} else { } else {
vec![] vec![]
} }
} }
pub fn program_begin<'a>(&'a self) -> Vec<Token> { pub fn program_begin(&self) -> Vec<Token<'input>> {
self.program_begin_sequence self.program_begin_sequence
.iter() .iter()
.flat_map(|s| s.iter_fields()) .flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f)) .map(Token::from)
.collect() .collect()
} }
pub fn program_end<'a>(&'a self) -> Vec<Token> { pub fn program_end(&self) -> Vec<Token<'input>> {
self.program_end_sequence self.program_end_sequence
.iter() .iter()
.flat_map(|s| s.iter_fields()) .flat_map(|s| s.iter_fields())
.map(|f: &Field| Token::from(f)) .map(Token::from)
.collect() .collect()
} }
/// Output absolute distance field if mode was relative or unknown. /// Output absolute distance field if mode was relative or unknown.
pub fn absolute(&mut self) -> Vec<Token> { pub fn absolute(&mut self) -> Vec<Token<'input>> {
if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None { if self.distance_mode == Some(Distance::Relative) || self.distance_mode == None {
self.distance_mode = Some(Distance::Absolute); self.distance_mode = Some(Distance::Absolute);
command!(AbsoluteDistanceMode {}).into_token_vec() command!(AbsoluteDistanceMode {}).into_token_vec()
@ -105,7 +101,7 @@ impl<'input> Machine<'input> {
} }
/// Output relative distance field if mode was absolute or unknown. /// Output relative distance field if mode was absolute or unknown.
pub fn relative(&mut self) -> Vec<Token> { pub fn relative(&mut self) -> Vec<Token<'input>> {
if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None { if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None {
self.distance_mode = Some(Distance::Relative); self.distance_mode = Some(Distance::Relative);
command!(RelativeDistanceMode {}).into_token_vec() command!(RelativeDistanceMode {}).into_token_vec()

@ -6,7 +6,7 @@ use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use std::path::PathBuf; use std::path::PathBuf;
use g_code::parse::{ast::Snippet, ParseError, snippet_parser}; use g_code::parse::{ast::Snippet, snippet_parser, ParseError};
use structopt::StructOpt; use structopt::StructOpt;
/// Converts an SVG to GCode in an internal representation /// Converts an SVG to GCode in an internal representation
@ -22,6 +22,7 @@ mod turtle;
use converter::ProgramOptions; use converter::ProgramOptions;
use machine::Machine; use machine::Machine;
use turtle::Turtle;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(name = "svg2gcode", author, about)] #[structopt(name = "svg2gcode", author, about)]
@ -81,10 +82,11 @@ fn main() -> io::Result<()> {
} }
}; };
let mut options = ProgramOptions::default(); let options = ProgramOptions {
options.tolerance = opt.tolerance; tolerance: opt.tolerance,
options.feedrate = opt.feedrate; feedrate: opt.feedrate,
options.dpi = opt.dpi; dpi: opt.dpi,
};
let snippets = [ let snippets = [
opt.tool_on_sequence.as_ref().map(parse_snippet).transpose(), opt.tool_on_sequence.as_ref().map(parse_snippet).transpose(),
@ -139,7 +141,8 @@ fn main() -> io::Result<()> {
let document = roxmltree::Document::parse(&input).expect("Invalid or unsupported SVG file"); let document = roxmltree::Document::parse(&input).expect("Invalid or unsupported SVG file");
let mut program = converter::svg2program(&document, options, machine); let mut turtle = Turtle::new(machine);
let mut program = converter::svg2program(&document, options, &mut turtle);
let origin = opt let origin = opt
.origin .origin
@ -155,12 +158,12 @@ fn main() -> io::Result<()> {
} }
} }
fn parse_snippet<'input>(gcode: &'input String) -> Result<Snippet<'input>, ParseError> { fn parse_snippet(gcode: &'_ String) -> Result<Snippet<'_>, ParseError> {
snippet_parser(gcode) snippet_parser(gcode)
} }
fn tokens_into_gcode<W: std::io::Write>( fn tokens_into_gcode<W: std::io::Write>(
program: Vec<g_code::emit::Token>, program: Vec<g_code::emit::Token<'_>>,
mut w: W, mut w: W,
) -> io::Result<()> { ) -> io::Result<()> {
use g_code::emit::Token::*; use g_code::emit::Token::*;
@ -169,8 +172,8 @@ fn tokens_into_gcode<W: std::io::Write>(
match token { match token {
Field(f) => { Field(f) => {
if !preceded_by_newline { if !preceded_by_newline {
if matches!(f.letters.as_str(), "G" | "M") { if matches!(f.letters.as_ref(), "G" | "M") {
writeln!(w, "")?; writeln!(w)?;
} else { } else {
write!(w, " ")?; write!(w, " ")?;
} }
@ -197,7 +200,7 @@ fn tokens_into_gcode<W: std::io::Write>(
} }
// Ensure presence of trailing newline // Ensure presence of trailing newline
if !preceded_by_newline { if !preceded_by_newline {
writeln!(w, "")?; writeln!(w)?;
} }
Ok(()) Ok(())
} }
@ -205,6 +208,7 @@ fn tokens_into_gcode<W: std::io::Write>(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::turtle::Turtle;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
fn get_actual(input: &str) -> String { fn get_actual(input: &str) -> String {
@ -219,7 +223,8 @@ mod test {
}; };
let document = roxmltree::Document::parse(input).unwrap(); let document = roxmltree::Document::parse(input).unwrap();
let mut program = converter::svg2program(&document, options, machine); let mut turtle = Turtle::new(machine);
let mut program = converter::svg2program(&document, options, &mut turtle);
postprocess::set_origin(&mut program, lyon_geom::point(0., 0.)); postprocess::set_origin(&mut program, lyon_geom::point(0., 0.));
let mut actual = vec![]; let mut actual = vec![];

@ -7,15 +7,15 @@ use lyon_geom::{point, vector, Point};
type F64Point = Point<f64>; type F64Point = Point<f64>;
/// Moves all the commands so that they are beyond a specified position /// Moves all the commands so that they are beyond a specified position
pub fn set_origin(tokens: &mut [Token], origin: F64Point) { pub fn set_origin(tokens: &mut [Token<'_>], origin: F64Point) {
let offset = -get_bounding_box(tokens.iter()).min.to_vector() + origin.to_vector(); let offset = -get_bounding_box(tokens.iter()).min.to_vector() + origin.to_vector();
let mut is_relative = false; let mut is_relative = false;
let mut current_position = point(0f64, 0f64); let mut current_position = point(0f64, 0f64);
let x = "X".to_string(); let x = "X";
let y = "Y".to_string(); let y = "Y";
let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone()); let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD);
let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone()); let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD);
for token in tokens { for token in tokens {
match token { match token {
abs if *abs == abs_tok => is_relative = false, abs if *abs == abs_tok => is_relative = false,
@ -45,14 +45,14 @@ pub fn set_origin(tokens: &mut [Token], origin: F64Point) {
} }
} }
fn get_bounding_box<'a, I: Iterator<Item = &'a Token>>(tokens: I) -> Box2D<f64> { fn get_bounding_box<'a, I: Iterator<Item = &'a Token<'a>>>(tokens: I) -> Box2D<f64> {
let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64)); let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64));
let mut is_relative = false; let mut is_relative = false;
let mut current_position = point(0f64, 0f64); let mut current_position = point(0f64, 0f64);
let x = "X".to_string(); let x = "X";
let y = "Y".to_string(); let y = "Y";
let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone()); let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD);
let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone()); let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD);
for token in tokens { for token in tokens {
match token { match token {
abs if *abs == abs_tok => is_relative = false, abs if *abs == abs_tok => is_relative = false,

@ -6,6 +6,7 @@ use g_code::{
use lyon_geom::euclid::{default::Transform2D, Angle}; use lyon_geom::euclid::{default::Transform2D, Angle};
use lyon_geom::{point, vector, Point}; use lyon_geom::{point, vector, Point};
use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc};
use std::borrow::Cow;
type F64Point = Point<f64>; type F64Point = Point<f64>;
@ -36,7 +37,7 @@ impl<'input> Turtle<'input> {
/// Move the turtle to the given absolute/relative coordinates in the current transform /// Move the turtle to the given absolute/relative coordinates in the current transform
/// https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
pub fn move_to<X, Y>(&mut self, abs: bool, x: X, y: Y) -> Vec<Token> pub fn move_to<X, Y>(&mut self, abs: bool, x: X, y: Y) -> Vec<Token<'input>>
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -84,17 +85,17 @@ impl<'input> Turtle<'input> {
.collect() .collect()
} }
fn linear_interpolation(x: f64, y: f64, z: Option<f64>, f: Option<f64>) -> Vec<Token> { fn linear_interpolation(x: f64, y: f64, z: Option<f64>, f: Option<f64>) -> Vec<Token<'static>> {
let mut linear_interpolation = command! {LinearInterpolation { X: x, Y: y, }}; let mut linear_interpolation = command! {LinearInterpolation { X: x, Y: y, }};
if let Some(z) = z { if let Some(z) = z {
linear_interpolation.push(Field { linear_interpolation.push(Field {
letters: "Z".to_string(), letters: Cow::Borrowed("Z"),
value: Value::Float(z), value: Value::Float(z),
}); });
} }
if let Some(f) = f { if let Some(f) = f {
linear_interpolation.push(Field { linear_interpolation.push(Field {
letters: "F".into(), letters: Cow::Borrowed("F"),
value: Value::Float(f), value: Value::Float(f),
}); });
} }
@ -103,7 +104,7 @@ impl<'input> Turtle<'input> {
/// Close an SVG path, cutting back to its initial position /// Close an SVG path, cutting back to its initial position
/// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand /// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
pub fn close<Z, F>(&mut self, z: Z, f: F) -> Vec<Token> pub fn close<Z, F>(&mut self, z: Z, f: F) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -134,7 +135,7 @@ impl<'input> Turtle<'input> {
/// Draw a line from the current position in the current transform to the specified position /// Draw a line from the current position in the current transform to the specified position
/// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
pub fn line<X, Y, Z, F>(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec<Token> pub fn line<X, Y, Z, F>(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec<Token<'input>>
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -186,7 +187,7 @@ impl<'input> Turtle<'input> {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Token> { ) -> Vec<Token<'input>> {
let z = z.into(); let z = z.into();
let f = f.into(); let f = f.into();
let last_point = std::cell::Cell::new(self.current_position); let last_point = std::cell::Cell::new(self.current_position);
@ -227,7 +228,7 @@ impl<'input> Turtle<'input> {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Token> ) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -269,7 +270,7 @@ impl<'input> Turtle<'input> {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Token> ) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -307,7 +308,7 @@ impl<'input> Turtle<'input> {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Token> ) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -339,7 +340,7 @@ impl<'input> Turtle<'input> {
tolerance: f64, tolerance: f64,
z: Z, z: Z,
f: F, f: F,
) -> Vec<Token> ) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,
@ -376,7 +377,7 @@ impl<'input> Turtle<'input> {
z: Z, z: Z,
f: F, f: F,
tolerance: f64, tolerance: f64,
) -> Vec<Token> ) -> Vec<Token<'input>>
where where
Z: Into<Option<f64>>, Z: Into<Option<f64>>,
F: Into<Option<f64>>, F: Into<Option<f64>>,

Loading…
Cancel
Save