Skip to content

Commit

Permalink
svg2gcode 0.2.1: shape support + bump g-code
Browse files Browse the repository at this point in the history
  • Loading branch information
sameer committed Apr 6, 2024
1 parent eb9a825 commit 225574d
Show file tree
Hide file tree
Showing 7 changed files with 1,124 additions and 19 deletions.
38 changes: 26 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "svg2gcode"
version = "0.2.0"
version = "0.2.1"
authors = ["Sameer Puri <crates@purisa.me>"]
edition = "2021"
description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine."
repository = "https://github.com/sameer/svg2gcode"
license = "MIT"

[dependencies]
g-code = { version = "0.3.6", features = ["serde"] }
g-code = { version = "0.4.1", features = ["serde"] }
lyon_geom = "1.0.5"
euclid = "0.22"
log = "0.4"
Expand Down
4 changes: 2 additions & 2 deletions lib/src/converter/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ use super::Terrarium;
/// Performs a [`Terrarium::reset`] on each call
pub fn apply_path<T: Turtle>(
terrarium: &mut Terrarium<T>,
path: impl Iterator<Item = PathSegment>,
path: impl IntoIterator<Item = PathSegment>,
) {
use PathSegment::*;

terrarium.reset();
path.for_each(|segment| {
path.into_iter().for_each(|segment| {
debug!("Drawing {:?}", &segment);
match segment {
MoveTo { abs, x, y } => terrarium.move_to(abs, x, y),
Expand Down
159 changes: 157 additions & 2 deletions lib/src/converter/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ const CLIP_PATH_TAG_NAME: &str = "clipPath";
const PATH_TAG_NAME: &str = "path";
const POLYLINE_TAG_NAME: &str = "polyline";
const POLYGON_TAG_NAME: &str = "polygon";
const RECT_TAG_NAME: &str = "rect";
const CIRCLE_TAG_NAME: &str = "circle";
const ELLIPSE_TAG_NAME: &str = "ellipse";
const LINE_TAG_NAME: &str = "line";
const GROUP_TAG_NAME: &str = "g";

pub trait XmlVisitor {
Expand Down Expand Up @@ -57,8 +61,8 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
}

// TODO: https://www.w3.org/TR/css-transforms-1/#transform-origin-property
if let Some(origin) = node.attribute("transform-origin").map(PointsParser::from) {
let _origin = PointsParser::from(origin).next();
if let Some(mut origin) = node.attribute("transform-origin").map(PointsParser::from) {
let _origin = origin.next();
warn!("transform-origin not supported yet");
}

Expand Down Expand Up @@ -197,6 +201,157 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
warn!("There is a {name} node containing no actual path: {node:?}");
}
}
RECT_TAG_NAME => {
let x = self.length_attr_to_user_units(&node, "x").unwrap_or(0.);
let y = self.length_attr_to_user_units(&node, "y").unwrap_or(0.);
let width = self.length_attr_to_user_units(&node, "width");
let height = self.length_attr_to_user_units(&node, "height");
let rx = self.length_attr_to_user_units(&node, "rx").unwrap_or(0.);
let ry = self.length_attr_to_user_units(&node, "ry").unwrap_or(0.);
let has_radius = rx > 0. && ry > 0.;

match (width, height) {
(Some(width), Some(height)) => {
self.comment(&node);
apply_path(
&mut self.terrarium,
[
MoveTo {
abs: true,
x: x + rx,
y,
},
HorizontalLineTo {
abs: true,
x: x + width - rx,
},
EllipticalArc {
abs: true,
rx,
ry,
x_axis_rotation: 0.,
large_arc: false,
sweep: true,
x: x + width,
y: y + ry,
},
VerticalLineTo {
abs: true,
y: y + height - ry,
},
EllipticalArc {
abs: true,
rx,
ry,
x_axis_rotation: 0.,
large_arc: false,
sweep: true,
x: x + width - rx,
y: y + height,
},
HorizontalLineTo {
abs: true,
x: x + rx,
},
EllipticalArc {
abs: true,
rx,
ry,
x_axis_rotation: 0.,
large_arc: false,
sweep: true,
x,
y: y + height - ry,
},
VerticalLineTo {
abs: true,
y: y + ry,
},
EllipticalArc {
abs: true,
rx,
ry,
x_axis_rotation: 0.,
large_arc: false,
sweep: true,
x: x + rx,
y,
},
ClosePath { abs: true },
]
.into_iter()
.filter(|p| has_radius || !matches!(p, EllipticalArc { .. })),
)
}
_other => {
warn!("Invalid rectangle node: {node:?}");
}
}
}
CIRCLE_TAG_NAME | ELLIPSE_TAG_NAME => {
let cx = self.length_attr_to_user_units(&node, "cx").unwrap_or(0.);
let cy = self.length_attr_to_user_units(&node, "cy").unwrap_or(0.);
let r = self.length_attr_to_user_units(&node, "r").unwrap_or(0.);
let rx = self.length_attr_to_user_units(&node, "rx").unwrap_or(r);
let ry = self.length_attr_to_user_units(&node, "ry").unwrap_or(r);
if rx > 0. && ry > 0. {
self.comment(&node);
apply_path(
&mut self.terrarium,
std::iter::once(MoveTo {
abs: true,
x: cx + rx,
y: cy,
})
.chain(
[(cx, cy + ry), (cx - rx, cy), (cx, cy - ry), (cx + rx, cy)].map(
|(x, y)| EllipticalArc {
abs: true,
rx,
ry,
x_axis_rotation: 0.,
large_arc: false,
sweep: true,
x,
y,
},
),
)
.chain(std::iter::once(ClosePath { abs: true })),
);
} else {
warn!("Invalid {} node: {node:?}", node.tag_name().name());
}
}
LINE_TAG_NAME => {
let x1 = self.length_attr_to_user_units(&node, "x1");
let y1 = self.length_attr_to_user_units(&node, "y1");
let x2 = self.length_attr_to_user_units(&node, "x2");
let y2 = self.length_attr_to_user_units(&node, "y2");
match (x1, y1, x2, y2) {
(Some(x1), Some(y1), Some(x2), Some(y2)) => {
self.comment(&node);
apply_path(
&mut self.terrarium,
[
MoveTo {
abs: true,
x: x1,
y: y1,
},
LineTo {
abs: true,
x: x2,
y: y2,
},
],
);
}
_other => {
warn!("Invalid line node: {node:?}");
}
}
}
// No-op tags
SVG_TAG_NAME | GROUP_TAG_NAME => {}
_ => {
Expand Down
14 changes: 13 additions & 1 deletion lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mod test {

fn assert_close(left: Vec<Token<'_>>, right: Vec<Token<'_>>) {
let mut code = String::new();
g_code::emit::format_gcode_fmt(&left, FormatOptions::default(), &mut code).unwrap();
g_code::emit::format_gcode_fmt(left.iter(), FormatOptions::default(), &mut code).unwrap();
assert_eq!(left.len(), right.len(), "{code}");
for (i, pair) in left.into_iter().zip(right.into_iter()).enumerate() {
match pair {
Expand Down Expand Up @@ -211,6 +211,18 @@ mod test {
);
}

#[test]
fn shapes_produces_expected_gcode() {
let shapes = include_str!("../tests/shapes.svg");
let expected = g_code::parse::file_parser(include_str!("../tests/shapes.gcode"))
.unwrap()
.iter_emit_tokens()
.collect::<Vec<_>>();
let actual = get_actual(shapes, false, [None; 2]);

assert_close(actual, expected)
}

#[test]
#[cfg(feature = "serde")]
fn deserialize_v1_config_succeeds() {
Expand Down
Loading

0 comments on commit 225574d

Please sign in to comment.