Skip to content

Commit 225574d

Browse files
committed
svg2gcode 0.2.1: shape support + bump g-code
1 parent eb9a825 commit 225574d

File tree

7 files changed

+1124
-19
lines changed

7 files changed

+1124
-19
lines changed

Cargo.lock

+26-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[package]
22
name = "svg2gcode"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
authors = ["Sameer Puri <[email protected]>"]
55
edition = "2021"
66
description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine."
77
repository = "https://github.com/sameer/svg2gcode"
88
license = "MIT"
99

1010
[dependencies]
11-
g-code = { version = "0.3.6", features = ["serde"] }
11+
g-code = { version = "0.4.1", features = ["serde"] }
1212
lyon_geom = "1.0.5"
1313
euclid = "0.22"
1414
log = "0.4"

lib/src/converter/path.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ use super::Terrarium;
1212
/// Performs a [`Terrarium::reset`] on each call
1313
pub fn apply_path<T: Turtle>(
1414
terrarium: &mut Terrarium<T>,
15-
path: impl Iterator<Item = PathSegment>,
15+
path: impl IntoIterator<Item = PathSegment>,
1616
) {
1717
use PathSegment::*;
1818

1919
terrarium.reset();
20-
path.for_each(|segment| {
20+
path.into_iter().for_each(|segment| {
2121
debug!("Drawing {:?}", &segment);
2222
match segment {
2323
MoveTo { abs, x, y } => terrarium.move_to(abs, x, y),

lib/src/converter/visit.rs

+157-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ const CLIP_PATH_TAG_NAME: &str = "clipPath";
1818
const PATH_TAG_NAME: &str = "path";
1919
const POLYLINE_TAG_NAME: &str = "polyline";
2020
const POLYGON_TAG_NAME: &str = "polygon";
21+
const RECT_TAG_NAME: &str = "rect";
22+
const CIRCLE_TAG_NAME: &str = "circle";
23+
const ELLIPSE_TAG_NAME: &str = "ellipse";
24+
const LINE_TAG_NAME: &str = "line";
2125
const GROUP_TAG_NAME: &str = "g";
2226

2327
pub trait XmlVisitor {
@@ -57,8 +61,8 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
5761
}
5862

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

@@ -197,6 +201,157 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
197201
warn!("There is a {name} node containing no actual path: {node:?}");
198202
}
199203
}
204+
RECT_TAG_NAME => {
205+
let x = self.length_attr_to_user_units(&node, "x").unwrap_or(0.);
206+
let y = self.length_attr_to_user_units(&node, "y").unwrap_or(0.);
207+
let width = self.length_attr_to_user_units(&node, "width");
208+
let height = self.length_attr_to_user_units(&node, "height");
209+
let rx = self.length_attr_to_user_units(&node, "rx").unwrap_or(0.);
210+
let ry = self.length_attr_to_user_units(&node, "ry").unwrap_or(0.);
211+
let has_radius = rx > 0. && ry > 0.;
212+
213+
match (width, height) {
214+
(Some(width), Some(height)) => {
215+
self.comment(&node);
216+
apply_path(
217+
&mut self.terrarium,
218+
[
219+
MoveTo {
220+
abs: true,
221+
x: x + rx,
222+
y,
223+
},
224+
HorizontalLineTo {
225+
abs: true,
226+
x: x + width - rx,
227+
},
228+
EllipticalArc {
229+
abs: true,
230+
rx,
231+
ry,
232+
x_axis_rotation: 0.,
233+
large_arc: false,
234+
sweep: true,
235+
x: x + width,
236+
y: y + ry,
237+
},
238+
VerticalLineTo {
239+
abs: true,
240+
y: y + height - ry,
241+
},
242+
EllipticalArc {
243+
abs: true,
244+
rx,
245+
ry,
246+
x_axis_rotation: 0.,
247+
large_arc: false,
248+
sweep: true,
249+
x: x + width - rx,
250+
y: y + height,
251+
},
252+
HorizontalLineTo {
253+
abs: true,
254+
x: x + rx,
255+
},
256+
EllipticalArc {
257+
abs: true,
258+
rx,
259+
ry,
260+
x_axis_rotation: 0.,
261+
large_arc: false,
262+
sweep: true,
263+
x,
264+
y: y + height - ry,
265+
},
266+
VerticalLineTo {
267+
abs: true,
268+
y: y + ry,
269+
},
270+
EllipticalArc {
271+
abs: true,
272+
rx,
273+
ry,
274+
x_axis_rotation: 0.,
275+
large_arc: false,
276+
sweep: true,
277+
x: x + rx,
278+
y,
279+
},
280+
ClosePath { abs: true },
281+
]
282+
.into_iter()
283+
.filter(|p| has_radius || !matches!(p, EllipticalArc { .. })),
284+
)
285+
}
286+
_other => {
287+
warn!("Invalid rectangle node: {node:?}");
288+
}
289+
}
290+
}
291+
CIRCLE_TAG_NAME | ELLIPSE_TAG_NAME => {
292+
let cx = self.length_attr_to_user_units(&node, "cx").unwrap_or(0.);
293+
let cy = self.length_attr_to_user_units(&node, "cy").unwrap_or(0.);
294+
let r = self.length_attr_to_user_units(&node, "r").unwrap_or(0.);
295+
let rx = self.length_attr_to_user_units(&node, "rx").unwrap_or(r);
296+
let ry = self.length_attr_to_user_units(&node, "ry").unwrap_or(r);
297+
if rx > 0. && ry > 0. {
298+
self.comment(&node);
299+
apply_path(
300+
&mut self.terrarium,
301+
std::iter::once(MoveTo {
302+
abs: true,
303+
x: cx + rx,
304+
y: cy,
305+
})
306+
.chain(
307+
[(cx, cy + ry), (cx - rx, cy), (cx, cy - ry), (cx + rx, cy)].map(
308+
|(x, y)| EllipticalArc {
309+
abs: true,
310+
rx,
311+
ry,
312+
x_axis_rotation: 0.,
313+
large_arc: false,
314+
sweep: true,
315+
x,
316+
y,
317+
},
318+
),
319+
)
320+
.chain(std::iter::once(ClosePath { abs: true })),
321+
);
322+
} else {
323+
warn!("Invalid {} node: {node:?}", node.tag_name().name());
324+
}
325+
}
326+
LINE_TAG_NAME => {
327+
let x1 = self.length_attr_to_user_units(&node, "x1");
328+
let y1 = self.length_attr_to_user_units(&node, "y1");
329+
let x2 = self.length_attr_to_user_units(&node, "x2");
330+
let y2 = self.length_attr_to_user_units(&node, "y2");
331+
match (x1, y1, x2, y2) {
332+
(Some(x1), Some(y1), Some(x2), Some(y2)) => {
333+
self.comment(&node);
334+
apply_path(
335+
&mut self.terrarium,
336+
[
337+
MoveTo {
338+
abs: true,
339+
x: x1,
340+
y: y1,
341+
},
342+
LineTo {
343+
abs: true,
344+
x: x2,
345+
y: y2,
346+
},
347+
],
348+
);
349+
}
350+
_other => {
351+
warn!("Invalid line node: {node:?}");
352+
}
353+
}
354+
}
200355
// No-op tags
201356
SVG_TAG_NAME | GROUP_TAG_NAME => {}
202357
_ => {

lib/src/lib.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ mod test {
6969

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

214+
#[test]
215+
fn shapes_produces_expected_gcode() {
216+
let shapes = include_str!("../tests/shapes.svg");
217+
let expected = g_code::parse::file_parser(include_str!("../tests/shapes.gcode"))
218+
.unwrap()
219+
.iter_emit_tokens()
220+
.collect::<Vec<_>>();
221+
let actual = get_actual(shapes, false, [None; 2]);
222+
223+
assert_close(actual, expected)
224+
}
225+
214226
#[test]
215227
#[cfg(feature = "serde")]
216228
fn deserialize_v1_config_succeeds() {

0 commit comments

Comments
 (0)