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

@ -1,5 +1,5 @@
use std::borrow::Cow;
use std::str::FromStr;
use std::{borrow::Cow, fmt::Debug};
use g_code::{command, emit::Token};
use log::{debug, warn};
@ -15,7 +15,7 @@ use svgtypes::{
ViewBox,
};
use crate::turtle::*;
use crate::{turtle::*, Machine};
#[cfg(feature = "serde")]
mod length_serde;
@ -60,39 +60,29 @@ pub struct ConversionOptions {
pub dimensions: [Option<Length>; 2],
}
struct ConversionVisitor<'a, 'input: 'a> {
turtle: &'a mut Turtle<'input>,
#[derive(Debug)]
struct ConversionVisitor<'a, T: Turtle> {
terrarium: Terrarium<T>,
name_stack: Vec<String>,
program: Vec<Token<'input>>,
config: &'a ConversionConfig,
options: ConversionOptions,
}
impl<'a, 'input: 'a> ConversionVisitor<'a, 'input> {
impl<'a, 'input: 'a> ConversionVisitor<'a, GCodeTurtle<'input>> {
fn begin(&mut self) {
self.program = command!(UnitsMillimeters {})
.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());
self.terrarium.turtle.begin();
// 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) {
self.turtle.pop_all_transforms();
self.program.extend(self.turtle.machine.tool_off());
self.program.extend(self.turtle.machine.absolute());
self.program.extend(self.turtle.machine.program_end());
self.program
.append(&mut command!(ProgramEnd {}).into_token_vec());
self.terrarium.pop_all_transforms();
self.terrarium.turtle.end();
}
}
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) {
// 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
.iter()
.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 let Some(d) = node.attribute("d") {
self.turtle.reset();
self.terrarium.reset();
let mut comment = String::new();
self.name_stack.iter().for_each(|name| {
comment += name;
comment += " > ";
});
comment += &node_name(&node);
self.program.push(Token::Comment {
is_inline: false,
inner: Cow::Owned(comment),
});
self.program
.extend(apply_path(&mut self.turtle, &self.config, d));
self.terrarium.turtle.comment(comment);
apply_path(&mut self.terrarium, &self.config, d);
} else {
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));
} else {
// Pop transform since this is the only element that has it
self.turtle.pop_transform();
self.terrarium.pop_transform();
let mut parent = Some(node);
while let Some(p) = parent {
@ -230,7 +216,7 @@ impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
break;
}
// Pop the parent transform since this is the last child
self.turtle.pop_transform();
self.terrarium.pop_transform();
self.name_stack.pop();
parent = p.parent();
}
@ -242,20 +228,24 @@ pub fn svg2program<'a, 'input: 'a>(
doc: &'a Document,
config: &ConversionConfig,
options: ConversionOptions,
turtle: &'a mut Turtle<'input>,
machine: Machine<'input>,
) -> Vec<Token<'input>> {
let mut conversion_visitor = ConversionVisitor {
turtle,
terrarium: Terrarium::new(GCodeTurtle {
machine,
tolerance: config.tolerance,
feedrate: config.feedrate,
program: vec![],
}),
config,
options,
name_stack: vec![],
program: vec![],
};
conversion_visitor.begin();
visit::depth_first_visit(doc, &mut conversion_visitor);
conversion_visitor.end();
conversion_visitor.program
conversion_visitor.terrarium.turtle.program
}
fn node_name(node: &Node) -> String {
@ -267,25 +257,25 @@ fn node_name(node: &Node) -> String {
name
}
fn apply_path<'input>(
turtle: &'_ mut Turtle<'input>,
fn apply_path<'input, T: Turtle + Debug>(
turtle: &mut Terrarium<T>,
config: &ConversionConfig,
path: &str,
) -> Vec<Token<'input>> {
) {
use PathSegment::*;
PathParser::from(path)
.map(|segment| segment.expect("could not parse path segment"))
.flat_map(|segment| {
.for_each(|segment| {
debug!("Drawing {:?}", &segment);
match segment {
MoveTo { abs, x, y } => turtle.move_to(abs, x, y),
ClosePath { abs: _ } => {
// 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),
HorizontalLineTo { abs, x } => turtle.line(abs, x, None, config.feedrate),
VerticalLineTo { abs, y } => turtle.line(abs, None, y, config.feedrate),
LineTo { abs, x, y } => turtle.line(abs, x, y),
HorizontalLineTo { abs, x } => turtle.line(abs, x, None),
VerticalLineTo { abs, y } => turtle.line(abs, None, y),
CurveTo {
abs,
x1,
@ -294,34 +284,14 @@ fn apply_path<'input>(
y2,
x,
y,
} => turtle.cubic_bezier(
abs,
point(x1, y1),
point(x2, y2),
point(x, y),
config.tolerance,
config.feedrate,
),
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,
),
} => turtle.cubic_bezier(abs, point(x1, y1), point(x2, y2), point(x, y)),
SmoothCurveTo { abs, x2, y2, x, y } => {
turtle.smooth_cubic_bezier(abs, point(x2, y2), point(x, y))
}
Quadratic { abs, x1, y1, x, y } => {
turtle.quadratic_bezier(abs, point(x1, y1), point(x, y))
}
SmoothQuadratic { abs, x, y } => turtle.smooth_quadratic_bezier(abs, point(x, y)),
EllipticalArc {
abs,
rx,
@ -337,12 +307,9 @@ fn apply_path<'input>(
Angle::degrees(x_axis_rotation),
ArcFlags { large_arc, sweep },
point(x, y),
config.feedrate,
config.tolerance,
),
}
})
.collect()
});
}
fn svg_transform_into_euclid_transform(svg_transform: TransformListToken) -> Transform2D<f64> {

@ -42,7 +42,7 @@ mod test {
let options = ConversionOptions { dimensions };
let document = roxmltree::Document::parse(input).unwrap();
let mut turtle = Turtle::new(Machine::new(
let machine = Machine::new(
SupportedFunctionality {
circular_interpolation,
},
@ -50,8 +50,8 @@ mod test {
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.]);
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 crate::machine::Machine;
use g_code::{command, emit::Token};
use std::fmt::Debug;
use lyon_geom::euclid::{default::Transform2D, Angle};
use lyon_geom::{point, vector, Point, Vector};
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.
/// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html
pub trait Turtle: Debug {
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)]
pub struct Turtle<'input> {
current_position: F64Point,
initial_position: F64Point,
pub struct Terrarium<T: Turtle + std::fmt::Debug> {
pub turtle: T,
current_position: Point<f64>,
initial_position: Point<f64>,
current_transform: Transform2D<f64>,
transform_stack: Vec<Transform2D<f64>>,
pub machine: Machine<'input>,
previous_control: Option<F64Point>,
previous_quadratic_control: Option<Point<f64>>,
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
pub fn new(machine: Machine<'input>) -> Self {
pub fn new(turtle: T) -> Self {
Self {
turtle,
current_position: Point::zero(),
initial_position: Point::zero(),
current_transform: Transform2D::identity(),
transform_stack: vec![],
machine,
previous_control: None,
previous_quadratic_control: None,
previous_cubic_control: None,
}
}
/// Move the turtle to the given absolute/relative coordinates in the current transform
/// 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
X: 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));
self.current_position = to;
self.initial_position = to;
self.previous_control = None;
self.machine
.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
}
}
self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.turtle.move_to(to);
}
/// Close an SVG path, cutting back to its initial position
/// 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
// which could result in a G91 G1 X0 Y0
if (self.current_position - self.initial_position)
if !(self.current_position - self.initial_position)
.abs()
.lower_than(vector(std::f64::EPSILON, std::f64::EPSILON))
.all()
{
return vec![];
self.turtle.line_to(self.initial_position);
}
self.current_position = self.initial_position;
self.machine
.tool_on()
.drain(..)
.chain(self.machine.absolute())
.chain(Self::linear_interpolation(
self.initial_position.x,
self.initial_position.y,
feedrate,
))
.collect()
self.previous_quadratic_control = None;
self.previous_cubic_control = None;
}
/// Draw a line from the current position in the current transform to the specified position
/// 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
X: 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));
self.current_position = to;
self.previous_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
/// 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;
self.previous_quadratic_control = None;
self.previous_cubic_control = None;
// 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()
self.turtle.line_to(to);
}
/// 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 ctrl2: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
) {
let from = self.current_position;
if !abs {
let inverse_transform = self.current_transform.inverse().unwrap();
@ -261,21 +166,24 @@ impl<'input> Turtle<'input> {
ctrl2,
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
/// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
pub fn smooth_cubic_bezier(
&mut self,
abs: bool,
mut ctrl2: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
pub fn smooth_cubic_bezier(&mut self, abs: bool, mut ctrl2: Point<f64>, mut to: Point<f64>) {
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 {
let inverse_transform = self.current_transform.inverse().unwrap();
let original_current_position = inverse_transform.transform_point(from);
@ -291,20 +199,26 @@ impl<'input> Turtle<'input> {
ctrl2,
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
/// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
pub fn smooth_quadratic_bezier(
&mut self,
abs: bool,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
pub fn smooth_quadratic_bezier(&mut self, abs: bool, mut to: Point<f64>) {
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 {
let inverse_transform = self.current_transform.inverse().unwrap();
let original_current_position = inverse_transform.transform_point(from);
@ -313,19 +227,22 @@ impl<'input> Turtle<'input> {
to = self.current_transform.transform_point(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
/// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands
pub fn quadratic_bezier(
&mut self,
abs: bool,
mut ctrl: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
feedrate: f64,
) -> Vec<Token<'input>> {
pub fn quadratic_bezier(&mut self, abs: bool, mut ctrl: Point<f64>, mut to: Point<f64>) {
let from = self.current_position;
if !abs {
let inverse_transform = self.current_transform.inverse().unwrap();
@ -337,7 +254,17 @@ impl<'input> Turtle<'input> {
to = self.current_transform.transform_point(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
@ -349,9 +276,7 @@ impl<'input> Turtle<'input> {
x_rotation: Angle<f64>,
flags: ArcFlags,
mut to: Point<f64>,
feedrate: f64,
tolerance: f64,
) -> Vec<Token<'input>> {
) {
let from = self
.current_transform
.inverse()
@ -370,39 +295,11 @@ impl<'input> Turtle<'input> {
}
.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.previous_control = None;
self.machine
.tool_on()
.drain(..)
.chain(self.machine.absolute())
.chain(arc_tokens)
.collect()
self.previous_quadratic_control = None;
self.previous_cubic_control = None;
self.turtle.arc(svg_arc);
}
/// Push a generic transform onto the stack
@ -435,7 +332,8 @@ impl<'input> Turtle<'input> {
self.current_position = self
.current_transform
.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;
}
}

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

Loading…
Cancel
Save