Skip to content

Commit 10d19ac

Browse files
authored
Create coordinate system flag, use Box2D for image (#116)
1 parent 15f1fe7 commit 10d19ac

9 files changed

Lines changed: 81 additions & 32 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ roxmltree = "0.21"
1919
serde = { version = "1", default-features = false }
2020
serde_json = "1"
2121
svgtypes = "0.16"
22+
uom = "0.38"

g_code/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
use g_code::emit::Token;
66
use roxmltree::Document;
7-
use svg2star::lower::{ConversionOptions, svg_to_turtle};
7+
use svg2star::{
8+
lower::{ConversionOptions, svg_to_turtle},
9+
turtle::CoordinateSystem,
10+
};
811

912
pub use self::{machine::Machine, turtle::GCodeTurtle};
1013
use crate::config::GCodeConfig;
@@ -31,5 +34,12 @@ pub fn svg_to_gcode<'a, 'input: 'a>(
3134
feedrate: config.feedrate,
3235
program: vec![],
3336
};
34-
svg_to_turtle(doc, &config.inner, options, gcode_turtle).program
37+
svg_to_turtle(
38+
doc,
39+
&config.inner,
40+
options,
41+
gcode_turtle,
42+
CoordinateSystem::YUp,
43+
)
44+
.program
3545
}

star/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ rustc-hash = "2"
1818
lyon_geom.workspace = true
1919
euclid = "0.22"
2020
log.workspace = true
21-
uom = "0.38"
21+
uom.workspace = true
2222
roxmltree.workspace = true
2323
svgtypes.workspace = true
2424

star/src/lower/mod.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ use self::units::CSS_DEFAULT_DPI;
1414
use crate::{
1515
lower::selector::SelectorList,
1616
turtle::{
17-
DpiConvertingTurtle, PreprocessTurtle, StrokeCollectingTurtle, Terrarium, Turtle,
17+
CoordinateSystem, DpiConvertingTurtle, PreprocessTurtle, StrokeCollectingTurtle, Terrarium,
18+
Turtle,
1819
elements::{Stroke, minimize_travel_time},
1920
},
2021
};
@@ -83,6 +84,8 @@ pub struct ConversionOptions {
8384
#[derive(Debug)]
8485
struct ConversionVisitor<'a, T: Turtle> {
8586
terrarium: Terrarium<T>,
87+
/// Whether to flip the Y axis to convert from SVG (Y-down) to the output coordinate system.
88+
coordinate_system: CoordinateSystem,
8689
name_stack: Vec<String>,
8790
/// Used to convert percentage values
8891
viewport_dim_stack: Vec<[f64; 2]>,
@@ -111,14 +114,18 @@ impl<'a, T: Turtle> ConversionVisitor<'a, T> {
111114
}
112115

113116
fn begin(&mut self) {
114-
// Part 1 of converting from SVG to GCode coordinates
115-
self.terrarium.push_transform(Transform2D::scale(1., -1.));
117+
if self.coordinate_system == CoordinateSystem::YUp {
118+
// Part 1 of converting from SVG (Y-down) to output (Y-up) coordinates
119+
self.terrarium.push_transform(Transform2D::scale(1., -1.));
120+
}
116121
self.terrarium.turtle.begin();
117122
}
118123

119124
fn end(&mut self) {
120125
self.terrarium.turtle.end();
121-
self.terrarium.pop_transform();
126+
if self.coordinate_system == CoordinateSystem::YUp {
127+
self.terrarium.pop_transform();
128+
}
122129
}
123130
}
124131

@@ -137,6 +144,7 @@ pub fn svg_to_turtle<T: Turtle>(
137144
config: &ConversionConfig,
138145
options: ConversionOptions,
139146
turtle: T,
147+
coordinate_system: CoordinateSystem,
140148
) -> T {
141149
let selector_filter = config
142150
.selector_filter
@@ -149,6 +157,7 @@ pub fn svg_to_turtle<T: Turtle>(
149157
inner: PreprocessTurtle::default(),
150158
dpi: config.dpi,
151159
}),
160+
coordinate_system,
152161
_config: config,
153162
options: options.clone(),
154163
name_stack: vec![],
@@ -189,6 +198,7 @@ pub fn svg_to_turtle<T: Turtle>(
189198
inner: turtle,
190199
dpi: config.dpi,
191200
}),
201+
coordinate_system,
192202
_config: config,
193203
options: options.clone(),
194204
name_stack: vec![],
@@ -202,8 +212,14 @@ pub fn svg_to_turtle<T: Turtle>(
202212
conversion_visitor.begin();
203213

204214
if config.optimize_path_order {
205-
let strokes =
206-
svg_to_optimized_strokes(doc, config, options, origin_transform, selector_filter);
215+
let strokes = svg_to_optimized_strokes(
216+
doc,
217+
config,
218+
options,
219+
origin_transform,
220+
selector_filter,
221+
coordinate_system,
222+
);
207223
let turtle = &mut conversion_visitor.terrarium.turtle;
208224
for stroke in strokes {
209225
turtle.move_to(stroke.start_point());
@@ -227,9 +243,11 @@ fn svg_to_optimized_strokes(
227243
options: ConversionOptions,
228244
origin_transform: Transform2D<f64>,
229245
selector_filter: Option<SelectorList>,
246+
coordinate_system: CoordinateSystem,
230247
) -> Vec<Stroke> {
231248
let mut collect_visitor = ConversionVisitor {
232249
terrarium: Terrarium::new(StrokeCollectingTurtle::default()),
250+
coordinate_system,
233251
_config: config,
234252
options,
235253
name_stack: vec![],

star/src/lower/visit.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ use super::{
1414
transform::{get_viewport_transform, svg_transform_into_euclid_transform},
1515
units::DimensionHint,
1616
};
17-
use crate::{lower::node_name, turtle::Turtle};
17+
use crate::{
18+
lower::node_name,
19+
turtle::{CoordinateSystem, Turtle},
20+
};
1821

1922
const SVG_TAG_NAME: &str = "svg";
2023
const CLIP_PATH_TAG_NAME: &str = "clipPath";
@@ -168,10 +171,7 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
168171
.options
169172
.dimensions
170173
.map(|l| l.map(|l| self.length_to_user_units(l, DimensionHint::Horizontal)));
171-
for (original_dim, override_dim) in viewport_size
172-
.iter_mut()
173-
.zip(dimensions_override.into_iter())
174-
{
174+
for (original_dim, override_dim) in viewport_size.iter_mut().zip(dimensions_override) {
175175
*original_dim = override_dim.or(*original_dim);
176176
}
177177

@@ -219,11 +219,14 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
219219
);
220220
flattened_transform = flattened_transform.then(&viewport_transform);
221221
}
222-
// Part 2 of converting from SVG to GCode coordinates
223-
flattened_transform = flattened_transform.then(&Transform2D::translation(
224-
0.,
225-
-(viewport_size[1] + viewport_pos[1].unwrap_or(0.)),
226-
));
222+
if self.coordinate_system == CoordinateSystem::YUp {
223+
// Part 2 of converting from SVG (Y-down) to output (Y-up) coordinates:
224+
// shift the origin from the top-left to the bottom-left of the viewport.
225+
flattened_transform = flattened_transform.then(&Transform2D::translation(
226+
0.,
227+
-(viewport_size[1] + viewport_pos[1].unwrap_or(0.)),
228+
));
229+
}
227230
} else if node.has_tag_name(USE_TAG_NAME) {
228231
// Per SVG spec, <use> x/y translate is appended to the element's transform
229232
// https://www.w3.org/TR/SVG2/struct.html#UseLayout
@@ -468,8 +471,6 @@ impl<'a, T: Turtle> XmlVisitor for ConversionVisitor<'a, T> {
468471
"image" => {
469472
use base64::{Engine, engine::general_purpose::STANDARD};
470473

471-
use crate::turtle::elements::RasterImage;
472-
473474
let Some(href) = node
474475
.attribute("href")
475476
.or_else(|| node.attribute(("http://www.w3.org/1999/xlink", "href")))

star/src/turtle/dpi.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ impl<T: Turtle> DpiConvertingTurtle<T> {
2727
fn vector_to_mm(&self, v: Vector<f64>) -> Vector<f64> {
2828
vector(self.to_mm(v.x), self.to_mm(v.y))
2929
}
30+
31+
#[cfg(feature = "image")]
32+
fn box_to_mm(&self, b: lyon_geom::Box2D<f64>) -> lyon_geom::Box2D<f64> {
33+
lyon_geom::Box2D::new(self.point_to_mm(b.min), self.point_to_mm(b.max))
34+
}
3035
}
3136

3237
impl<T: Turtle> Turtle for DpiConvertingTurtle<T> {
@@ -100,10 +105,7 @@ impl<T: Turtle> Turtle for DpiConvertingTurtle<T> {
100105
#[cfg(feature = "image")]
101106
fn image(&mut self, img: super::elements::RasterImage) {
102107
self.inner.image(super::elements::RasterImage {
103-
x: self.to_mm(img.x),
104-
y: self.to_mm(img.y),
105-
width: self.to_mm(img.width),
106-
height: self.to_mm(img.height),
108+
dimensions: self.box_to_mm(img.dimensions),
107109
image: img.image,
108110
})
109111
}

star/src/turtle/elements/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ mod tsp;
2020
///
2121
/// <https://www.w3.org/TR/SVG/embedded.html#ImageElement>
2222
#[cfg(feature = "image")]
23+
#[derive(Debug, Clone)]
2324
pub struct RasterImage {
24-
pub position: Point<f64>,
25-
pub dimensions: Vector<f64>,
25+
pub dimensions: lyon_geom::Box2D<f64>,
2626
pub image: image::DynamicImage,
2727
}
2828

star/src/turtle/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ pub use self::{
2020
svg_preview::SvgPreviewTurtle,
2121
};
2222

23+
/// The coordinate system expected by a [`Turtle`] implementation.
24+
///
25+
/// Passed as a parameter to [`crate::lower::svg_to_turtle`] so each backend can declare
26+
/// whether it needs SVG's native Y-down space or Y-up (typical for machine tools / G-code).
27+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28+
pub enum CoordinateSystem {
29+
/// Y increases downward (SVG default). No extra transform is applied.
30+
#[default]
31+
YDown,
32+
/// Y increases upward (typical for machine tools / G-code).
33+
///
34+
/// [`crate::lower::svg_to_turtle`] will flip the Y axis so that coordinates delivered to
35+
/// the turtle have the origin at the bottom-left and Y increasing upward.
36+
YUp,
37+
}
38+
2339
/// Abstraction for drawing paths based on [Turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics)
2440
pub trait Turtle: Debug {
2541
fn begin(&mut self);
@@ -343,9 +359,10 @@ impl<T: Turtle + std::fmt::Debug> Terrarium<T> {
343359
.transform_point(point(x, y) + vector(width, height));
344360
self.turtle.image(crate::turtle::elements::RasterImage {
345361
// After transformation, the corners may be swapped resulting in a new x y.
346-
// Also need to pick the larger y because of the G-Code coordinate space swap (?).
347-
position: point(t0.x.min(t1.x), t0.y.max(t1.y)),
348-
dimensions: (t1 - t0).abs(),
362+
dimensions: lyon_geom::Box2D::new(
363+
point(t0.x.min(t1.x), t0.y.min(t1.y)),
364+
point(t0.x.max(t1.x), t0.y.max(t1.y)),
365+
),
349366
image,
350367
});
351368
}

web/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use roxmltree::{Document, ParsingOptions};
1717
use svg2gcode::{Machine, svg_to_gcode};
1818
use svg2star::{
1919
lower::{ConversionOptions, svg_to_turtle},
20-
turtle::SvgPreviewTurtle,
20+
turtle::{CoordinateSystem, SvgPreviewTurtle},
2121
};
2222
use yew::prelude::*;
2323

@@ -231,7 +231,7 @@ fn app() -> Html {
231231
.ok()
232232
.map(|doc| {
233233
let options = ConversionOptions { dimensions: svg.dimensions };
234-
svg_to_turtle(&doc, &app_store.settings.conversion.inner, options, SvgPreviewTurtle::default()).into_preview()
234+
svg_to_turtle(&doc, &app_store.settings.conversion.inner, options, SvgPreviewTurtle::default(), CoordinateSystem::YUp).into_preview()
235235
})
236236
.unwrap_or_default();
237237
let preview_svg_base64 = base64::engine::general_purpose::STANDARD_NO_PAD.encode(preview_svg.as_bytes());

0 commit comments

Comments
 (0)