refactor turtle to prepare for preprocessing

master
Sameer Puri 4 years ago
parent 3e6ad50eb0
commit 433c4c7482

@ -13,7 +13,7 @@ use structopt::StructOpt;
use svgtypes::LengthListParser; use svgtypes::LengthListParser;
use svg2gcode::{ use svg2gcode::{
set_origin, svg2program, ConversionOptions, Machine, Settings, SupportedFunctionality, Turtle, set_origin, svg2program, ConversionOptions, Machine, Settings, SupportedFunctionality,
}; };
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@ -253,8 +253,7 @@ fn main() -> io::Result<()> {
let document = roxmltree::Document::parse(&input).unwrap(); let document = roxmltree::Document::parse(&input).unwrap();
let mut turtle = Turtle::new(machine); let mut program = svg2program(&document, &settings.conversion, options, machine);
let mut program = svg2program(&document, &settings.conversion, options, &mut turtle);
set_origin(&mut program, settings.postprocess.origin); set_origin(&mut program, settings.postprocess.origin);

@ -1,5 +1,5 @@
use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
use std::{borrow::Cow, fmt::Debug};
use g_code::{command, emit::Token}; use g_code::{command, emit::Token};
use log::{debug, warn}; use log::{debug, warn};
@ -15,7 +15,7 @@ use svgtypes::{
ViewBox, ViewBox,
}; };
use crate::turtle::*; use crate::{turtle::*, Machine};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod length_serde; mod length_serde;
@ -60,39 +60,29 @@ pub struct ConversionOptions {
pub dimensions: [Option<Length>; 2], pub dimensions: [Option<Length>; 2],
} }
struct ConversionVisitor<'a, 'input: 'a> { #[derive(Debug)]
turtle: &'a mut Turtle<'input>, struct ConversionVisitor<'a, T: Turtle> {
terrarium: Terrarium<T>,
name_stack: Vec<String>, name_stack: Vec<String>,
program: Vec<Token<'input>>,
config: &'a ConversionConfig, config: &'a ConversionConfig,
options: ConversionOptions, options: ConversionOptions,
} }
impl<'a, 'input: 'a> ConversionVisitor<'a, 'input> { impl<'a, 'input: 'a> ConversionVisitor<'a, GCodeTurtle<'input>> {
fn begin(&mut self) { fn begin(&mut self) {
self.program = command!(UnitsMillimeters {}) self.terrarium.turtle.begin();
.into_token_vec()
.drain(..)
.collect::<Vec<_>>();
self.program.extend(self.turtle.machine.absolute());
self.program.extend(self.turtle.machine.program_begin());
self.program.extend(self.turtle.machine.absolute());
// Part 1 of converting from SVG to g-code coordinates // Part 1 of converting from SVG to g-code coordinates
self.turtle.push_transform(Transform2D::scale(1., -1.)); self.terrarium.push_transform(Transform2D::scale(1., -1.));
} }
fn end(&mut self) { fn end(&mut self) {
self.turtle.pop_all_transforms(); self.terrarium.pop_all_transforms();
self.program.extend(self.turtle.machine.tool_off()); self.terrarium.turtle.end();
self.program.extend(self.turtle.machine.absolute());
self.program.extend(self.turtle.machine.program_end());
self.program
.append(&mut command!(ProgramEnd {}).into_token_vec());
} }
} }
impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> { impl<'a, T: Turtle> visit::XmlVisitor for ConversionVisitor<'a, T> {
fn visit(&mut self, node: Node) { fn visit(&mut self, node: Node) {
// Depth-first SVG DOM traversal // Depth-first SVG DOM traversal
@ -192,7 +182,7 @@ impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
) )
} }
self.turtle.push_transform( self.terrarium.push_transform(
transforms transforms
.iter() .iter()
.fold(Transform2D::identity(), |acc, t| acc.then(t)), .fold(Transform2D::identity(), |acc, t| acc.then(t)),
@ -200,19 +190,15 @@ impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
if node.tag_name().name() == PATH_TAG_NAME { if node.tag_name().name() == PATH_TAG_NAME {
if let Some(d) = node.attribute("d") { if let Some(d) = node.attribute("d") {
self.turtle.reset(); self.terrarium.reset();
let mut comment = String::new(); let mut comment = String::new();
self.name_stack.iter().for_each(|name| { self.name_stack.iter().for_each(|name| {
comment += name; comment += name;
comment += " > "; comment += " > ";
}); });
comment += &node_name(&node); comment += &node_name(&node);
self.program.push(Token::Comment { self.terrarium.turtle.comment(comment);
is_inline: false, apply_path(&mut self.terrarium, &self.config, d);
inner: Cow::Owned(comment),
});
self.program
.extend(apply_path(&mut self.turtle, &self.config, 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);
} }
@ -222,7 +208,7 @@ impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
self.name_stack.push(node_name(&node)); self.name_stack.push(node_name(&node));
} else { } else {
// Pop transform since this is the only element that has it // Pop transform since this is the only element that has it
self.turtle.pop_transform(); self.terrarium.pop_transform();
let mut parent = Some(node); let mut parent = Some(node);
while let Some(p) = parent { while let Some(p) = parent {
@ -230,7 +216,7 @@ impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
break; break;
} }
// Pop the parent transform since this is the last child // Pop the parent transform since this is the last child
self.turtle.pop_transform(); self.terrarium.pop_transform();
self.name_stack.pop(); self.name_stack.pop();
parent = p.parent(); parent = p.parent();
} }
@ -242,20 +228,24 @@ pub fn svg2program<'a, 'input: 'a>(
doc: &'a Document, doc: &'a Document,
config: &ConversionConfig, config: &ConversionConfig,
options: ConversionOptions, options: ConversionOptions,
turtle: &'a mut Turtle<'input>, machine: Machine<'input>,
) -> Vec<Token<'input>> { ) -> Vec<Token<'input>> {
let mut conversion_visitor = ConversionVisitor { let mut conversion_visitor = ConversionVisitor {
turtle, terrarium: Terrarium::new(GCodeTurtle {
machine,
tolerance: config.tolerance,
feedrate: config.feedrate,
program: vec![],
}),
config, config,
options, options,
name_stack: vec![], name_stack: vec![],
program: vec![],
}; };
conversion_visitor.begin(); conversion_visitor.begin();
visit::depth_first_visit(doc, &mut conversion_visitor); visit::depth_first_visit(doc, &mut conversion_visitor);
conversion_visitor.end(); conversion_visitor.end();
conversion_visitor.program conversion_visitor.terrarium.turtle.program
} }
fn node_name(node: &Node) -> String { fn node_name(node: &Node) -> String {
@ -267,25 +257,25 @@ fn node_name(node: &Node) -> String {
name name
} }
fn apply_path<'input>( fn apply_path<'input, T: Turtle + Debug>(
turtle: &'_ mut Turtle<'input>, turtle: &mut Terrarium<T>,
config: &ConversionConfig, config: &ConversionConfig,
path: &str, 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"))
.flat_map(|segment| { .for_each(|segment| {
debug!("Drawing {:?}", &segment); debug!("Drawing {:?}", &segment);
match segment { match segment {
MoveTo { abs, x, y } => turtle.move_to(abs, x, y), MoveTo { abs, x, y } => turtle.move_to(abs, x, y),
ClosePath { abs: _ } => { ClosePath { abs: _ } => {
// Ignore abs, should have identical effect: [9.3.4. The "closepath" command]("https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand) // Ignore abs, should have identical effect: [9.3.4. The "closepath" command]("https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand)
turtle.close(config.feedrate) turtle.close()
} }
LineTo { abs, x, y } => turtle.line(abs, x, y, config.feedrate), LineTo { abs, x, y } => turtle.line(abs, x, y),
HorizontalLineTo { abs, x } => turtle.line(abs, x, None, config.feedrate), HorizontalLineTo { abs, x } => turtle.line(abs, x, None),
VerticalLineTo { abs, y } => turtle.line(abs, None, y, config.feedrate), VerticalLineTo { abs, y } => turtle.line(abs, None, y),
CurveTo { CurveTo {
abs, abs,
x1, x1,
@ -294,34 +284,14 @@ fn apply_path<'input>(
y2, y2,
x, x,
y, y,
} => turtle.cubic_bezier( } => turtle.cubic_bezier(abs, point(x1, y1), point(x2, y2), point(x, y)),
abs, SmoothCurveTo { abs, x2, y2, x, y } => {
point(x1, y1), turtle.smooth_cubic_bezier(abs, point(x2, y2), point(x, y))
point(x2, y2), }
point(x, y), Quadratic { abs, x1, y1, x, y } => {
config.tolerance, turtle.quadratic_bezier(abs, point(x1, y1), point(x, y))
config.feedrate, }
), SmoothQuadratic { abs, x, y } => turtle.smooth_quadratic_bezier(abs, point(x, y)),
SmoothCurveTo { abs, x2, y2, x, y } => turtle.smooth_cubic_bezier(
abs,
point(x2, y2),
point(x, y),
config.tolerance,
config.feedrate,
),
Quadratic { abs, x1, y1, x, y } => turtle.quadratic_bezier(
abs,
point(x1, y1),
point(x, y),
config.tolerance,
config.feedrate,
),
SmoothQuadratic { abs, x, y } => turtle.smooth_quadratic_bezier(
abs,
point(x, y),
config.tolerance,
config.feedrate,
),
EllipticalArc { EllipticalArc {
abs, abs,
rx, rx,
@ -337,12 +307,9 @@ fn apply_path<'input>(
Angle::degrees(x_axis_rotation), Angle::degrees(x_axis_rotation),
ArcFlags { large_arc, sweep }, ArcFlags { large_arc, sweep },
point(x, y), point(x, y),
config.feedrate,
config.tolerance,
), ),
} }
}) });
.collect()
} }
fn svg_transform_into_euclid_transform(svg_transform: TransformListToken) -> Transform2D<f64> { fn svg_transform_into_euclid_transform(svg_transform: TransformListToken) -> Transform2D<f64> {

@ -42,7 +42,7 @@ mod test {
let options = ConversionOptions { dimensions }; let options = ConversionOptions { dimensions };
let document = roxmltree::Document::parse(input).unwrap(); let document = roxmltree::Document::parse(input).unwrap();
let mut turtle = Turtle::new(Machine::new( let machine = Machine::new(
SupportedFunctionality { SupportedFunctionality {
circular_interpolation, circular_interpolation,
}, },
@ -50,8 +50,8 @@ mod test {
None, None,
None, None,
None, None,
)); );
let mut program = converter::svg2program(&document, &config, options, &mut turtle); let mut program = converter::svg2program(&document, &config, options, machine);
postprocess::set_origin(&mut program, [0., 0.]); postprocess::set_origin(&mut program, [0., 0.]);
let mut acc = String::new(); let mut acc = String::new();

@ -0,0 +1,170 @@
use std::borrow::Cow;
use std::fmt::Debug;
use ::g_code::{command, emit::Token};
use lyon_geom::Point;
use lyon_geom::{CubicBezierSegment, QuadraticBezierSegment, SvgArc};
use crate::arc::{ArcOrLineSegment, FlattenWithArcs};
use crate::machine::Machine;
use super::Turtle;
/// Turtle graphics simulator for mapping path segments into g-code
#[derive(Debug)]
pub struct GCodeTurtle<'input> {
pub machine: Machine<'input>,
pub tolerance: f64,
pub feedrate: f64,
pub program: Vec<Token<'input>>,
}
impl<'input> GCodeTurtle<'input> {
fn circular_interpolation(&self, svg_arc: SvgArc<f64>) -> Vec<Token<'input>> {
debug_assert!((svg_arc.radii.x.abs() - svg_arc.radii.y.abs()).abs() < f64::EPSILON);
match (svg_arc.flags.large_arc, svg_arc.flags.sweep) {
(false, true) => command!(CounterclockwiseCircularInterpolation {
X: svg_arc.to.x,
Y: svg_arc.to.y,
R: svg_arc.radii.x,
F: self.feedrate,
})
.into_token_vec(),
(false, false) => command!(ClockwiseCircularInterpolation {
X: svg_arc.to.x,
Y: svg_arc.to.y,
R: svg_arc.radii.x,
F: self.feedrate,
})
.into_token_vec(),
(true, _) => {
let (left, right) = svg_arc.to_arc().split(0.5);
let mut token_vec = self.circular_interpolation(left.to_svg_arc());
token_vec.append(&mut self.circular_interpolation(right.to_svg_arc()));
token_vec
}
}
}
fn tool_on(&mut self) {
self.program.extend(
self.machine
.tool_on()
.drain(..)
.chain(self.machine.absolute()),
);
}
fn tool_off(&mut self) {
self.program.extend(
self.machine
.tool_off()
.drain(..)
.chain(self.machine.absolute()),
);
}
}
impl<'input> Turtle for GCodeTurtle<'input> {
fn begin(&mut self) {
self.program
.append(&mut command!(UnitsMillimeters {}).into_token_vec());
self.program.extend(self.machine.absolute());
self.program.extend(self.machine.program_begin());
self.program.extend(self.machine.absolute());
}
fn end(&mut self) {
self.program.extend(self.machine.tool_off());
self.program.extend(self.machine.absolute());
self.program.extend(self.machine.program_end());
self.program
.append(&mut command!(ProgramEnd {}).into_token_vec());
}
fn comment(&mut self, comment: String) {
self.program.push(Token::Comment {
is_inline: false,
inner: Cow::Owned(comment),
});
}
fn move_to(&mut self, to: Point<f64>) {
self.tool_off();
self.program.append(
&mut command!(RapidPositioning {
X: to.x as f64,
Y: to.y as f64,
})
.into_token_vec(),
);
}
fn line_to(&mut self, to: Point<f64>) {
self.tool_on();
self.program.append(
&mut command!(LinearInterpolation {
X: to.x,
Y: to.y,
F: self.feedrate,
})
.into_token_vec(),
);
}
fn arc(&mut self, svg_arc: SvgArc<f64>) {
if svg_arc.is_straight_line() {
self.line_to(svg_arc.to);
return;
}
self.tool_on();
if self
.machine
.supported_functionality()
.circular_interpolation
{
FlattenWithArcs::flattened(&svg_arc, self.tolerance)
.drain(..)
.for_each(|segment| match segment {
ArcOrLineSegment::Arc(arc) => {
self.program.append(&mut self.circular_interpolation(arc))
}
ArcOrLineSegment::Line(line) => {
self.line_to(line.to);
}
});
} else {
svg_arc
.to_arc()
.flattened(self.tolerance)
.for_each(|point| self.line_to(point));
};
}
fn cubic_bezier(&mut self, cbs: CubicBezierSegment<f64>) {
self.tool_on();
if self
.machine
.supported_functionality()
.circular_interpolation
{
FlattenWithArcs::<f64>::flattened(&cbs, self.tolerance)
.drain(..)
.for_each(|segment| match segment {
ArcOrLineSegment::Arc(arc) => {
self.program.append(&mut self.circular_interpolation(arc))
}
ArcOrLineSegment::Line(line) => self.line_to(line.to),
});
} else {
cbs.flattened(self.tolerance)
.for_each(|point| self.line_to(point));
};
}
fn quadratic_bezier(&mut self, qbs: QuadraticBezierSegment<f64>) {
self.cubic_bezier(qbs.to_cubic());
}
}

@ -1,40 +1,54 @@
use crate::arc::{ArcOrLineSegment, FlattenWithArcs, Transformed}; use std::fmt::Debug;
use crate::machine::Machine;
use g_code::{command, emit::Token};
use lyon_geom::euclid::{default::Transform2D, Angle}; use lyon_geom::euclid::{default::Transform2D, Angle};
use lyon_geom::{point, vector, Point, Vector}; use lyon_geom::{point, vector, Point, Vector};
use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc};
type F64Point = Point<f64>; use crate::arc::{Transformed};
mod g_code;
pub use self::g_code::GCodeTurtle;
/// Turtle graphics simulator for paths that outputs the g-code representation for each operation. pub trait Turtle: Debug {
/// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html fn begin(&mut self);
fn end(&mut self);
fn comment(&mut self, comment: String);
fn move_to(&mut self, to: Point<f64>);
fn line_to(&mut self, to: Point<f64>);
fn arc(&mut self, svg_arc: SvgArc<f64>);
fn cubic_bezier(&mut self, cbs: CubicBezierSegment<f64>);
fn quadratic_bezier(&mut self, qbs: QuadraticBezierSegment<f64>);
}
/// Wrapper for [Turtle] that handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html
#[derive(Debug)] #[derive(Debug)]
pub struct Turtle<'input> { pub struct Terrarium<T: Turtle + std::fmt::Debug> {
current_position: F64Point, pub turtle: T,
initial_position: F64Point, current_position: Point<f64>,
initial_position: Point<f64>,
current_transform: Transform2D<f64>, current_transform: Transform2D<f64>,
transform_stack: Vec<Transform2D<f64>>, transform_stack: Vec<Transform2D<f64>>,
pub machine: Machine<'input>, previous_quadratic_control: Option<Point<f64>>,
previous_control: Option<F64Point>, previous_cubic_control: Option<Point<f64>>,
} }
impl<'input> Turtle<'input> { impl<T: Turtle + std::fmt::Debug> Terrarium<T> {
/// Create a turtle at the origin with no transform /// Create a turtle at the origin with no transform
pub fn new(machine: Machine<'input>) -> Self { pub fn new(turtle: T) -> Self {
Self { Self {
turtle,
current_position: Point::zero(), current_position: Point::zero(),
initial_position: Point::zero(), initial_position: Point::zero(),
current_transform: Transform2D::identity(), current_transform: Transform2D::identity(),
transform_stack: vec![], transform_stack: vec![],
machine, previous_quadratic_control: None,
previous_control: None, previous_cubic_control: None,
} }
} }
/// 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<'input>> pub fn move_to<X, Y>(&mut self, abs: bool, x: X, y: Y)
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -65,89 +79,31 @@ impl<'input> Turtle<'input> {
let to = self.current_transform.transform_point(point(x, y)); let to = self.current_transform.transform_point(point(x, y));
self.current_position = to; self.current_position = to;
self.initial_position = to; self.initial_position = to;
self.previous_control = None; self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.machine self.turtle.move_to(to);
.tool_off()
.drain(..)
.chain(self.machine.absolute())
.chain(
command!(RapidPositioning {
X: to.x as f64,
Y: to.y as f64,
})
.into_token_vec(),
)
.collect()
}
fn linear_interpolation(x: f64, y: f64, feedrate: f64) -> Vec<Token<'static>> {
command!(LinearInterpolation {
X: x,
Y: y,
F: feedrate,
})
.into_token_vec()
}
fn circular_interpolation(svg_arc: SvgArc<f64>, feedrate: f64) -> Vec<Token<'input>> {
debug_assert!((svg_arc.radii.x.abs() - svg_arc.radii.y.abs()).abs() < f64::EPSILON);
match (svg_arc.flags.large_arc, svg_arc.flags.sweep) {
(false, true) => command!(CounterclockwiseCircularInterpolation {
X: svg_arc.to.x,
Y: svg_arc.to.y,
R: svg_arc.radii.x,
F: feedrate,
})
.into_token_vec(),
(false, false) => command!(ClockwiseCircularInterpolation {
X: svg_arc.to.x,
Y: svg_arc.to.y,
R: svg_arc.radii.x,
F: feedrate,
})
.into_token_vec(),
(true, _) => {
let (left, right) = svg_arc.to_arc().split(0.5);
let mut token_vec = Self::circular_interpolation(left.to_svg_arc(), feedrate);
token_vec.append(&mut Self::circular_interpolation(
right.to_svg_arc(),
feedrate,
));
token_vec
}
}
} }
/// 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(&mut self, feedrate: f64) -> Vec<Token<'input>> { pub fn close(&mut self) {
// See https://www.w3.org/TR/SVG/paths.html#Segment-CompletingClosePath // See https://www.w3.org/TR/SVG/paths.html#Segment-CompletingClosePath
// which could result in a G91 G1 X0 Y0 // which could result in a G91 G1 X0 Y0
if (self.current_position - self.initial_position) if !(self.current_position - self.initial_position)
.abs() .abs()
.lower_than(vector(std::f64::EPSILON, std::f64::EPSILON)) .lower_than(vector(std::f64::EPSILON, std::f64::EPSILON))
.all() .all()
{ {
return vec![]; self.turtle.line_to(self.initial_position);
} }
self.current_position = self.initial_position; self.current_position = self.initial_position;
self.previous_quadratic_control = None;
self.machine self.previous_cubic_control = None;
.tool_on()
.drain(..)
.chain(self.machine.absolute())
.chain(Self::linear_interpolation(
self.initial_position.x,
self.initial_position.y,
feedrate,
))
.collect()
} }
/// 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>(&mut self, abs: bool, x: X, y: Y, feedrate: f64) -> Vec<Token<'input>> pub fn line<X, Y>(&mut self, abs: bool, x: X, y: Y)
where where
X: Into<Option<f64>>, X: Into<Option<f64>>,
Y: Into<Option<f64>>, Y: Into<Option<f64>>,
@ -177,59 +133,10 @@ impl<'input> Turtle<'input> {
let to = self.current_transform.transform_point(point(x, y)); let to = self.current_transform.transform_point(point(x, y));
self.current_position = to; self.current_position = to;
self.previous_control = None; self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.machine
.tool_on()
.drain(..)
.chain(self.machine.absolute())
.chain(Self::linear_interpolation(to.x, to.y, feedrate))
.collect()
}
/// Draw a cubic bezier curve segment self.turtle.line_to(to);
/// The public bezier functions call this command after converting to a cubic bezier segment
/// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
fn bezier(
&mut self,
cbs: CubicBezierSegment<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
let tokens: Vec<_> = if self
.machine
.supported_functionality()
.circular_interpolation
{
FlattenWithArcs::<f64>::flattened(&cbs, tolerance)
.drain(..)
.flat_map(|segment| match segment {
ArcOrLineSegment::Arc(arc) => Self::circular_interpolation(arc, feedrate),
ArcOrLineSegment::Line(line) => {
Self::linear_interpolation(line.to.x, line.to.y, feedrate)
}
})
.collect()
} else {
cbs.flattened(tolerance)
.flat_map(|point| Self::linear_interpolation(point.x, point.y, feedrate))
.collect()
};
self.current_position = cbs.to;
// See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints
self.previous_control = Some(point(
2. * self.current_position.x - cbs.ctrl2.x,
2. * self.current_position.y - cbs.ctrl2.y,
));
self.machine
.tool_on()
.drain(..)
.chain(self.machine.absolute())
.chain(tokens)
.collect()
} }
/// Draw a cubic curve from the current point to (x, y) with specified control points (x1, y1) and (x2, y2) /// Draw a cubic curve from the current point to (x, y) with specified control points (x1, y1) and (x2, y2)
@ -240,9 +147,7 @@ impl<'input> Turtle<'input> {
mut ctrl1: Point<f64>, mut ctrl1: Point<f64>,
mut ctrl2: Point<f64>, mut ctrl2: Point<f64>,
mut to: Point<f64>, mut to: Point<f64>,
tolerance: f64, ) {
feedrate: f64,
) -> Vec<Token<'input>> {
let from = self.current_position; let from = self.current_position;
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); let inverse_transform = self.current_transform.inverse().unwrap();
@ -261,21 +166,24 @@ impl<'input> Turtle<'input> {
ctrl2, ctrl2,
to, to,
}; };
self.bezier(cbs, tolerance, feedrate)
self.current_position = cbs.to;
// See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints
self.previous_cubic_control = Some(point(
2. * self.current_position.x - cbs.ctrl2.x,
2. * self.current_position.y - cbs.ctrl2.y,
));
self.previous_quadratic_control = None;
self.turtle.cubic_bezier(cbs);
} }
/// Draw a shorthand/smooth cubic bezier segment, where the first control point was already given /// Draw a shorthand/smooth cubic bezier segment, where the first control point was already given
/// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
pub fn smooth_cubic_bezier( pub fn smooth_cubic_bezier(&mut self, abs: bool, mut ctrl2: Point<f64>, mut to: Point<f64>) {
&mut self,
abs: bool,
mut ctrl2: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
let from = self.current_position; let from = self.current_position;
let ctrl1 = self.previous_control.unwrap_or(self.current_position); let ctrl1 = self.previous_cubic_control.unwrap_or(self.current_position);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); let inverse_transform = self.current_transform.inverse().unwrap();
let original_current_position = inverse_transform.transform_point(from); let original_current_position = inverse_transform.transform_point(from);
@ -291,20 +199,26 @@ impl<'input> Turtle<'input> {
ctrl2, ctrl2,
to, to,
}; };
self.bezier(cbs, tolerance, feedrate)
self.current_position = cbs.to;
// See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints
self.previous_cubic_control = Some(point(
2. * self.current_position.x - cbs.ctrl2.x,
2. * self.current_position.y - cbs.ctrl2.y,
));
self.previous_quadratic_control = None;
self.turtle.cubic_bezier(cbs);
} }
/// Draw a shorthand/smooth cubic bezier segment, where the control point was already given /// Draw a shorthand/smooth cubic bezier segment, where the control point was already given
/// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
pub fn smooth_quadratic_bezier( pub fn smooth_quadratic_bezier(&mut self, abs: bool, mut to: Point<f64>) {
&mut self,
abs: bool,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
let from = self.current_position; let from = self.current_position;
let ctrl = self.previous_control.unwrap_or(self.current_position); let ctrl = self
.previous_quadratic_control
.unwrap_or(self.current_position);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); let inverse_transform = self.current_transform.inverse().unwrap();
let original_current_position = inverse_transform.transform_point(from); let original_current_position = inverse_transform.transform_point(from);
@ -313,19 +227,22 @@ impl<'input> Turtle<'input> {
to = self.current_transform.transform_point(to); to = self.current_transform.transform_point(to);
let qbs = QuadraticBezierSegment { from, ctrl, to }; let qbs = QuadraticBezierSegment { from, ctrl, to };
self.bezier(qbs.to_cubic(), tolerance, feedrate)
self.current_position = qbs.to;
// See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints
self.previous_quadratic_control = Some(point(
2. * self.current_position.x - qbs.ctrl.x,
2. * self.current_position.y - qbs.ctrl.y,
));
self.previous_cubic_control = None;
self.turtle.quadratic_bezier(qbs);
} }
/// Draw a quadratic bezier segment /// Draw a quadratic bezier segment
/// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands /// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
pub fn quadratic_bezier( pub fn quadratic_bezier(&mut self, abs: bool, mut ctrl: Point<f64>, mut to: Point<f64>) {
&mut self,
abs: bool,
mut ctrl: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
let from = self.current_position; let from = self.current_position;
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); let inverse_transform = self.current_transform.inverse().unwrap();
@ -337,7 +254,17 @@ impl<'input> Turtle<'input> {
to = self.current_transform.transform_point(to); to = self.current_transform.transform_point(to);
let qbs = QuadraticBezierSegment { from, ctrl, to }; let qbs = QuadraticBezierSegment { from, ctrl, to };
self.bezier(qbs.to_cubic(), tolerance, feedrate)
self.current_position = qbs.to;
// See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints
self.previous_quadratic_control = Some(point(
2. * self.current_position.x - qbs.ctrl.x,
2. * self.current_position.y - qbs.ctrl.y,
));
self.previous_cubic_control = None;
self.turtle.quadratic_bezier(qbs);
} }
/// Draw an elliptical arc segment /// Draw an elliptical arc segment
@ -349,9 +276,7 @@ impl<'input> Turtle<'input> {
x_rotation: Angle<f64>, x_rotation: Angle<f64>,
flags: ArcFlags, flags: ArcFlags,
mut to: Point<f64>, mut to: Point<f64>,
feedrate: f64, ) {
tolerance: f64,
) -> Vec<Token<'input>> {
let from = self let from = self
.current_transform .current_transform
.inverse() .inverse()
@ -370,39 +295,11 @@ impl<'input> Turtle<'input> {
} }
.transformed(&self.current_transform); .transformed(&self.current_transform);
let arc_tokens = if svg_arc.is_straight_line() {
Self::linear_interpolation(svg_arc.to.x, svg_arc.to.y, feedrate)
} else if self
.machine
.supported_functionality()
.circular_interpolation
{
FlattenWithArcs::flattened(&svg_arc, tolerance)
.drain(..)
.flat_map(|segment| match segment {
ArcOrLineSegment::Arc(arc) => Self::circular_interpolation(arc, feedrate),
ArcOrLineSegment::Line(line) => {
Self::linear_interpolation(line.to.x, line.to.y, feedrate)
}
})
.collect()
} else {
svg_arc
.to_arc()
.flattened(tolerance)
.flat_map(|point| Self::linear_interpolation(point.x, point.y, feedrate))
.collect()
};
self.current_position = svg_arc.to; self.current_position = svg_arc.to;
self.previous_control = None; self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.machine
.tool_on() self.turtle.arc(svg_arc);
.drain(..)
.chain(self.machine.absolute())
.chain(arc_tokens)
.collect()
} }
/// Push a generic transform onto the stack /// Push a generic transform onto the stack
@ -435,7 +332,8 @@ impl<'input> Turtle<'input> {
self.current_position = self self.current_position = self
.current_transform .current_transform
.transform_point(self.current_position); .transform_point(self.current_position);
self.previous_control = None; self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.initial_position = self.current_position; self.initial_position = self.current_position;
} }
} }

@ -111,12 +111,11 @@ impl Component for App {
); );
let document = Document::parse(svg.content.as_str()).unwrap(); let document = Document::parse(svg.content.as_str()).unwrap();
let mut turtle = Turtle::new(machine);
let mut program = svg2program( let mut program = svg2program(
&document, &document,
&app_state.settings.conversion, &app_state.settings.conversion,
options, options,
&mut turtle, machine,
); );
set_origin(&mut program, app_state.settings.postprocess.origin); set_origin(&mut program, app_state.settings.postprocess.origin);

Loading…
Cancel
Save