migrate to roxmltree + svgtypes, closes #6

master
Sameer Puri 5 years ago
parent bd5d9d9eee
commit a9fe4f57ea

75
Cargo.lock generated

@ -144,27 +144,27 @@ dependencies = [
[[package]]
name = "paste"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"paste-impl 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
"paste-impl 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "paste-impl"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.15"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -206,18 +206,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roxmltree"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "simplecss"
version = "0.2.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"xmlparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -225,11 +217,6 @@ name = "siphasher"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.8.0"
@ -243,24 +230,11 @@ dependencies = [
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"lyon_geom 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)",
"paste 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"paste 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"svgdom 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uom 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "svgdom"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"roxmltree 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
"simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"roxmltree 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uom 0.28.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -274,7 +248,7 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.23"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
@ -364,12 +338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "xmlparser"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "xmlwriter"
version = "0.1.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
@ -392,22 +361,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lyon_geom 0.15.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9962a2ba81382716b87d7d358493cb71844c1f9165ddad763cd9f4d3f5474df2"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum paste 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "678f27e19361472a23717f11d229a7522ef64605baf0715c896a94b8b6b13a06"
"checksum paste-impl 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "149089128a45d8e377677b08873b4bad2a56618f80e4f28a83862ba250994a30"
"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
"checksum paste 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531"
"checksum paste-impl 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606"
"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
"checksum proc-macro2 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
"checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"checksum roxmltree 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "330d8f80a274bc3cb608908ee345970e7e24b96907f1ad69615a498bec57871c"
"checksum simplecss 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "596554e63596d556a0dbd681416342ca61c75f1a45203201e7e77d3fa2fa9014"
"checksum roxmltree 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5001f134077069d87f77c8b9452b690df2445f7a43f1c7ca4a1af8dd505789d"
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum svgdom 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4ae7d6df72b3a36c0f003f1f8919c96be43d20d7c015f8dcf2f82531a4fc33b"
"checksum svgtypes 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff"
"checksum syn 1.0.23 (registry+https://github.com/rust-lang/crates.io-index)" = "95b5f192649e48a5302a13f2feb224df883b98933222369e4b3b0fe2a5447269"
"checksum syn 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f"
"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
@ -420,5 +386,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum xmlparser 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee"
"checksum xmlwriter 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
"checksum xmlparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ccb4240203dadf40be2de9369e5c6dec1bf427528115b030baca3334c18362d7"

@ -6,7 +6,6 @@ edition = "2018"
description = "Convert paths in SVG files to GCode for a pen plotter or laser engraver"
[dependencies]
svgdom = "0"
lyon_geom = "0"
clap = "2"
log = "0"
@ -14,3 +13,5 @@ env_logger = "0"
uom = "0"
regex = "1"
paste = "0"
roxmltree = "0"
svgtypes = "0"

@ -1,5 +1,13 @@
use lyon_geom::{euclid, math};
use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, PathSegment};
use std::str::FromStr;
use lyon_geom::{
euclid::{default::Transform2D, Angle, Transform3D},
math,
};
use roxmltree::{Document, Node};
use svgtypes::{
LengthListParser, PathParser, PathSegment, TransformListParser, TransformListToken, ViewBox,
};
#[macro_use]
use crate::*;
@ -18,7 +26,7 @@ pub struct ProgramOptions {
pub dpi: f64,
}
pub fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machine) -> Vec<Command> {
pub fn svg2program(doc: &Document, options: ProgramOptions, mach: Machine) -> Vec<Command> {
let mut turtle = Turtle::new(mach);
let mut program = vec![
@ -29,109 +37,159 @@ pub fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machin
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
// Depth-first SVG DOM traversal
let mut node_stack = vec![(doc.root(), doc.root().children())];
let mut name_stack: Vec<String> = vec![];
for edge in doc.root().traverse() {
let (node, is_start) = match edge {
svgdom::NodeEdge::Start(node) => (node, true),
svgdom::NodeEdge::End(node) => (node, false),
while let Some((parent, mut children)) = node_stack.pop() {
let node: Node = match children.next() {
Some(child) => {
node_stack.push((parent, children));
child
}
None => {
if parent.has_attribute("viewBox")
|| parent.has_attribute("transform")
|| parent.has_attribute("width")
|| parent.has_attribute("height")
{
turtle.pop_transform();
}
name_stack.pop();
continue;
}
};
let id = if let svgdom::QName::Id(id) = *node.tag_name() {
id
} else {
if node.node_type() != roxmltree::NodeType::Element {
debug!("Encountered a non-element: {:?}", node);
continue;
};
}
let attributes = node.attributes();
if let (ElementId::Svg, true) = (id, is_start) {
if let Some(&AttributeValue::ViewBox(view_box)) =
attributes.get_value(AttributeId::ViewBox)
{
turtle.stack_scaling(
euclid::Transform2D::create_scale(1. / view_box.w, 1. / view_box.h)
if node.tag_name().name() == "clipPath" {
warn!("Clip paths are not supported: {:?}", node);
continue;
}
let mut transforms = vec![];
if let Some(view_box) = node.attribute("viewBox") {
let view_box = ViewBox::from_str(view_box).expect("could not parse viewBox");
transforms.push(
Transform2D::create_scale(1. / view_box.w, 1. / view_box.h)
.post_translate(math::vector(view_box.x, view_box.y)),
);
}
if let (Some(&AttributeValue::Length(width)), Some(&AttributeValue::Length(height))) = (
attributes.get_value(AttributeId::Width),
attributes.get_value(AttributeId::Height),
) {
let width_in_mm = length_to_mm(width, options.dpi);
let height_in_mm = length_to_mm(height, options.dpi);
turtle.stack_scaling(
euclid::Transform2D::create_scale(width_in_mm, -height_in_mm)
.post_translate(math::vector(0.0, height_in_mm)),
);
if let Some(transform) = width_and_height_into_transform(&options, &node) {
transforms.push(transform);
}
if let Some(transform) = node.attribute("transform") {
let parser = TransformListParser::from(transform);
transforms.append(
&mut parser
.map(|token| {
token.expect("could not parse a transform in a list of transforms")
})
.map(svg_transform_into_euclid_transform)
.collect(),
)
}
if transforms.len() != 0 {
let transform = transforms
.iter()
.fold(Transform2D::identity(), |acc, t| acc.post_transform(t));
turtle.push_transform(transform);
}
// Display named elements in GCode comments
if let ElementId::G = id {
if is_start {
name_stack.push(format!("{}#{}", node.tag_name(), node.id().to_string()));
if node.tag_name().name() == "path" {
if let Some(d) = node.attribute("d") {
turtle.reset();
let mut comment = String::new();
name_stack.iter().for_each(|name| {
comment += name;
comment += " > ";
});
comment += &node_name(&node);
program.push(command!(CommandWord::Comment(Box::new(comment)), {}));
program.append(&mut apply_path(&mut turtle, &options, d));
} else {
name_stack.pop();
warn!("There is a path node containing no actual path: {:?}", node);
}
}
if let Some(&AttributeValue::Transform(ref transform)) =
attributes.get_value(AttributeId::Transform)
{
if is_start {
turtle.push_transform(lyon_geom::euclid::Transform2D::row_major(
transform.a,
transform.b,
transform.c,
transform.d,
transform.e,
transform.f,
));
} else {
if node.has_children() {
node_stack.push((node, node.children()));
name_stack.push(node_name(&node));
} else if transforms.len() != 0 {
// Pop transform early, since this is the only element that has it
turtle.pop_transform();
}
}
let is_clip_path = node.ancestors().any(|ancestor| {
if let svgdom::QName::Id(ancestor_id) = *ancestor.tag_name() {
ancestor_id == ElementId::ClipPath
// Critical step for actually moving the machine back to the origin, just in case SVG is malformed
turtle.pop_all_transforms();
program.append(&mut turtle.machine.tool_off());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
program.append(&mut turtle.machine.program_end());
program.push(command!(CommandWord::ProgramEnd, {}));
program
}
fn node_name(node: &Node) -> String {
let mut name = node.tag_name().name().to_string();
if let Some(id) = node.attribute("id") {
name += "#";
name += id;
}
name
}
fn width_and_height_into_transform(
options: &ProgramOptions,
node: &Node,
) -> Option<Transform2D<f64>> {
if let (Some(mut width), Some(mut height)) = (
node.attribute("width").map(LengthListParser::from),
node.attribute("height").map(LengthListParser::from),
) {
let width = width
.next()
.expect("no width in width property")
.expect("cannot parse width");
let height = height
.next()
.expect("no height in height property")
.expect("cannot parse height");
let width_in_mm = length_to_mm(width, options.dpi);
let height_in_mm = length_to_mm(height, options.dpi);
Some(
Transform2D::create_scale(width_in_mm, -height_in_mm)
.post_translate(math::vector(0f64, height_in_mm)),
)
} else {
false
None
}
});
}
if node.is_graphic() && is_start && !is_clip_path {
match id {
ElementId::Path => {
if let Some(&AttributeValue::Path(ref path)) =
attributes.get_value(AttributeId::D)
{
let prefix: String =
name_stack.iter().fold(String::new(), |mut acc, name| {
acc += name;
acc += " => ";
acc
});
program.push(command!(
CommandWord::Comment(Box::new(prefix + &node.id())),
{}
));
turtle.reset();
for segment in path.iter() {
program.append(&mut match segment {
PathSegment::MoveTo { abs, x, y } => turtle.move_to(*abs, *x, *y),
PathSegment::ClosePath { abs: _ } => {
fn apply_path<'a>(turtle: &mut Turtle, options: &ProgramOptions, path: &'a str) -> Vec<Command> {
use PathSegment::*;
PathParser::from(path)
.map(|segment| segment.expect("could not parse path segment"))
.map(|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(None, options.feedrate)
}
PathSegment::LineTo { abs, x, y } => {
turtle.line(*abs, *x, *y, None, options.feedrate)
}
PathSegment::HorizontalLineTo { abs, x } => {
turtle.line(*abs, *x, None, None, options.feedrate)
}
PathSegment::VerticalLineTo { abs, y } => {
turtle.line(*abs, None, *y, None, options.feedrate)
}
PathSegment::CurveTo {
LineTo { abs, x, y } => turtle.line(abs, x, y, None, options.feedrate),
HorizontalLineTo { abs, x } => turtle.line(abs, x, None, None, options.feedrate),
VerticalLineTo { abs, y } => turtle.line(abs, None, y, None, options.feedrate),
CurveTo {
abs,
x1,
y1,
@ -140,49 +198,46 @@ pub fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machin
x,
y,
} => turtle.cubic_bezier(
*abs,
*x1,
*y1,
*x2,
*y2,
*x,
*y,
abs,
x1,
y1,
x2,
y2,
x,
y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => turtle
.smooth_cubic_bezier(
*abs,
*x2,
*y2,
*x,
*y,
SmoothCurveTo { abs, x2, y2, x, y } => turtle.smooth_cubic_bezier(
abs,
x2,
y2,
x,
y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::Quadratic { abs, x1, y1, x, y } => turtle
.quadratic_bezier(
*abs,
*x1,
*y1,
*x,
*y,
Quadratic { abs, x1, y1, x, y } => turtle.quadratic_bezier(
abs,
x1,
y1,
x,
y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothQuadratic { abs, x, y } => turtle
.smooth_quadratic_bezier(
*abs,
*x,
*y,
SmoothQuadratic { abs, x, y } => turtle.smooth_quadratic_bezier(
abs,
x,
y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::EllipticalArc {
EllipticalArc {
abs,
rx,
ry,
@ -192,45 +247,48 @@ pub fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machin
x,
y,
} => turtle.elliptical(
*abs,
*rx,
*ry,
*x_axis_rotation,
*large_arc,
*sweep,
*x,
*y,
abs,
rx,
ry,
x_axis_rotation,
large_arc,
sweep,
x,
y,
None,
options.feedrate,
options.tolerance,
),
});
}
}
}
_ => {
warn!("Node <{} id=\"{}\" .../> is not supported", id, node.id());
}
})
.flatten()
.collect()
}
fn svg_transform_into_euclid_transform(svg_transform: TransformListToken) -> Transform2D<f64> {
use TransformListToken::*;
match svg_transform {
Matrix { a, b, c, d, e, f } => Transform2D::row_major(a, b, c, d, e, f),
Translate { tx, ty } => Transform2D::create_translation(tx, ty),
Scale { sx, sy } => Transform2D::create_scale(sx, sy),
Rotate { angle } => Transform2D::create_rotation(Angle::degrees(angle)),
SkewX { angle } => {
warn!("Skew X might not be implemented correctly, please check the GCode output.");
Transform3D::create_skew(Angle::degrees(angle), Angle::degrees(0f64)).to_2d()
}
SkewY { angle } => {
warn!("Skew Y might not be implemented correctly, please check the GCode output.");
Transform3D::create_skew(Angle::degrees(0f64), Angle::degrees(angle)).to_2d()
}
}
// Critical step for actually move the machine back to the origin
turtle.pop_all_transforms();
program.append(&mut turtle.machine.tool_off());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
program.append(&mut turtle.machine.program_end());
program.push(command!(CommandWord::ProgramEnd, {}));
program
}
/// Convenience function for converting absolute lengths to millimeters
/// Absolute lengths are listed in [CSS 4 §6.2](https://www.w3.org/TR/css-values/#absolute-lengths)
/// Relative lengths in [CSS 4 §6.1](https://www.w3.org/TR/css-values/#relative-lengths) are not supported and will cause a panic.
/// A default DPI of 96 is used as per [CSS 4 §7.4](https://www.w3.org/TR/css-values/#resolution), which you can adjust with --dpi
fn length_to_mm(l: svgdom::Length, dpi: f64) -> f64 {
use svgdom::LengthUnit::*;
fn length_to_mm(l: svgtypes::Length, dpi: f64) -> f64 {
use svgtypes::LengthUnit::*;
use uom::si::f64::Length;
use uom::si::length::*;

@ -21,7 +21,7 @@ impl Into<f64> for &Value {
fn into(self) -> f64 {
match self {
Value::Float(f) => *f,
_ => panic!("Unwrapping a non-float")
_ => panic!("Unwrapping a non-float"),
}
}
}

@ -8,14 +8,14 @@ pub enum Direction {
}
/// Whether the tool is active (i.e. cutting)
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Tool {
Off,
On,
}
/// The distance mode for movement commands
#[derive(Copy, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Distance {
Absolute,
Relative,
@ -23,6 +23,7 @@ pub enum Distance {
/// Generic machine state simulation, assuming nothing is known about the machine when initialized.
/// This is used to reduce output GCode verbosity and run repetitive actions.
#[derive(Debug)]
pub struct Machine {
tool_state: Option<Tool>,
distance_mode: Option<Distance>,

@ -1,12 +1,7 @@
#[macro_use]
extern crate clap;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate lyon_geom;
extern crate regex;
extern crate svgdom;
extern crate uom;
use std::env;
use std::fs::File;
@ -19,12 +14,12 @@ mod converter;
mod gcode;
/// Emulates the state of an arbitrary machine that can run GCode
mod machine;
/// Provides an interface for drawing lines in GCode
/// This concept is referred to as [Turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics).
mod turtle;
/// Operations that are easier to implement after GCode is generated, or would
/// over-complicate SVG conversion
mod postprocess;
/// Provides an interface for drawing lines in GCode
/// This concept is referred to as [Turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics).
mod turtle;
fn main() -> io::Result<()> {
if let Err(_) = env::var("RUST_LOG") {
@ -98,7 +93,7 @@ fn main() -> io::Result<()> {
.unwrap_or_default(),
);
let document = svgdom::Document::from_str(&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);

@ -6,25 +6,24 @@ use lyon_geom::math::{point, vector, F64Point};
use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc};
/// Turtle graphics simulator for paths that outputs the gcode representation for each operation.
/// Handles trasforms, scaling, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html
/// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html
#[derive(Debug)]
pub struct Turtle {
current_position: F64Point,
initial_position: F64Point,
current_transform: Transform2D<f64>,
scaling: Option<Transform2D<f64>>,
transform_stack: Vec<Transform2D<f64>>,
pub machine: Machine,
previous_control: Option<F64Point>,
}
impl Turtle {
/// Create a turtle at the origin with no scaling or transform
/// Create a turtle at the origin with no transform
pub fn new(machine: Machine) -> Self {
Self {
current_position: point(0.0, 0.0),
initial_position: point(0.0, 0.0),
current_transform: Transform2D::identity(),
scaling: None,
transform_stack: vec![],
machine,
previous_control: None,
@ -439,33 +438,13 @@ impl Turtle {
.collect()
}
/// Push a new scaling-only transform onto the stack
/// This is useful for handling things like the viewBox
/// https://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
pub fn stack_scaling(&mut self, scaling: Transform2D<f64>) {
self.current_transform = self.current_transform.post_transform(&scaling);
if let Some(ref current_scaling) = self.scaling {
self.scaling = Some(current_scaling.post_transform(&scaling));
} else {
self.scaling = Some(scaling);
}
}
/// Push a generic transform onto the stack
/// Could be any valid CSS transform https://drafts.csswg.org/css-transforms-1/#typedef-transform-function
/// https://www.w3.org/TR/SVG/coords.html#InterfaceSVGTransform
pub fn push_transform(&mut self, trans: Transform2D<f64>) {
self.transform_stack.push(self.current_transform);
if let Some(ref scaling) = self.scaling {
self.current_transform = self
.current_transform
.post_transform(&scaling.inverse().unwrap())
.pre_transform(&trans)
.post_transform(&scaling);
} else {
self.current_transform = self.current_transform.post_transform(&trans);
}
}
/// Pop a generic transform off the stack, returning to the previous transform state
/// This means that most recent transform went out of scope
@ -485,6 +464,7 @@ impl Turtle {
}
/// Reset the position of the turtle to the origin in the current transform stack
/// Used for starting a new path
pub fn reset(&mut self) {
self.current_position = point(0.0, 0.0);
self.current_position = self

Loading…
Cancel
Save