diff --git a/Cargo.lock b/Cargo.lock index b46c46c..4c1aacc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "num" @@ -368,9 +368,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.4.6" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "roxmltree" @@ -458,9 +458,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.70" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9505f307c872bab8eb46f77ae357c8eba1fdacead58ee5a850116b1d7f82883" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", @@ -505,9 +505,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "uom" diff --git a/README.md b/README.md index 32ab6c5..e093580 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,10 @@ Convert any SVG 1.1 path to gcode for a pen plotter, laser engraver, etc. ## TODO -- [x] Support all path variants -- [x] Support group and path transforms - [ ] Biarc interpolation (G2/G3 instead of many G1) -- [x] Px, pc, in to mm -- [x] Configurable DPI for px/pc to mm - [ ] Sort paths by distance to reduce G0 distances -- [x] Configurable start/end sequence - [ ] Comments in GCode input - [ ] Rustdocs -- [ ] End-to-end tests ## Known bugs/issues - [ ] Ellipse paths are dubious -- large_arc, sweep may need to be inverted diff --git a/src/converter.rs b/src/converter.rs index 279c6a6..fb1e289 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -96,13 +96,15 @@ pub fn svg2program<'input>( if let Some(transform) = node.attribute("transform") { let parser = TransformListParser::from(transform); - transforms.append( - &mut parser + transforms.extend( + parser .map(|token| { token.expect("could not parse a transform in a list of transforms") }) .map(svg_transform_into_euclid_transform) - .collect(), + .collect::>() + .iter() + .rev(), ) } @@ -180,6 +182,8 @@ fn width_and_height_into_transform( let width_in_mm = length_to_mm(width, options.dpi); let height_in_mm = length_to_mm(height, options.dpi); + // SVGs have 0,0 in upper left + // g-code has 0,0 in lower left Some( Transform2D::scale(width_in_mm, -height_in_mm) .then_translate(vector(0f64, height_in_mm)), diff --git a/src/turtle.rs b/src/turtle.rs index 4925c3b..d7e7285 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -431,7 +431,7 @@ impl<'input> Turtle<'input> { /// https://www.w3.org/TR/SVG/coords.html#InterfaceSVGTransform pub fn push_transform(&mut self, trans: Transform2D) { self.transform_stack.push(self.current_transform); - self.current_transform = self.current_transform.then(&trans); + self.current_transform = trans.then(&self.current_transform); } /// Pop a generic transform off the stack, returning to the previous transform state diff --git a/tests/square.gcode b/tests/square.gcode index 557ecfa..bf9870e 100644 --- a/tests/square.gcode +++ b/tests/square.gcode @@ -1,11 +1,120 @@ G21 -G94 G90 G0 X0 Y0;svg#svg8 > g#layer1 > path#path838 G0 X1 Y9 G1 X9 Y9 F300 G1 X9 Y1 F300 G1 X1 Y1 F300 -G1 X1 Y9 F300 +G1 X1 Y9 F300;svg#svg8 > g#layer1 > path#path832 +G0 X8 Y2.5 +G1 X7.992016 Y2.5889958186882955 F300 +G1 X7.968318977024 Y2.675149466910962 F300 +G1 X7.929665719197762 Y2.75570954175836 F300 +G1 X7.877290656963224 Y2.828103276680163 F300 +G1 X7.812866440307907 Y2.8900187053579076 F300 +G1 X7.738450521014919 Y2.939478496661343 F300 +G1 X7.656419445882797 Y2.974903102695401 F300 +G1 X7.569392959326962 Y2.9951612032417794 F300 +G1 X7.480150339222062 Y2.999605835601428 F300 +G1 X7.391541637883766 Y2.9880950559953092 F300 +G1 X7.306396662798014 Y2.9609964726809244 F300 +G1 X7.227434603889145 Y2.9191755060150015 F300 +G1 X7.1571771934704715 Y2.8639677503889835 F300 +G1 X7.097868172201125 Y2.7971363206865427 F300 +G1 X7.051401632984364 Y2.7208155454466567 F300 +G1 X7.019261531216614 Y2.637442804947386 F300 +G1 X7.00247429318793 Y2.549680691029316 F300 +G1 X7.001576036131997 Y2.460331974562534 F300 +G1 X7.016595446786152 Y2.372250096156122 F300 +G1 X7.047052865251745 Y2.288248038678869 F300 +G1 X7.091975603412658 Y2.211008491838367 F300 +G1 X7.149929008702984 Y2.142998177802515 F300 +G1 X7.2190622811713725 Y2.086389073960362 F300 +G1 X7.297167580628271 Y2.042989048652211 F300 +G1 X7.381750536230225 Y2.0141841250863033 F300 +G1 X7.4701099067071315 Y2.000894217301639 F300 +G1 X7.5 Y2 F300 +G1 X7.5889958186882955 Y2.007984 F300 +G1 X7.675149466910962 Y2.031681022976 F300 +G1 X7.7557095417583595 Y2.0703342808022382 F300 +G1 X7.828103276680163 Y2.1227093430367767 F300 +G1 X7.890018705357908 Y2.1871335596920924 F300 +G1 X7.9394784966613425 Y2.2615494789850814 F300 +G1 X7.974903102695401 Y2.343580554117203 F300 +G1 X7.995161203241779 Y2.4306070406730376 F300 +G1 X7.999605835601428 Y2.519849660777938 F300 +G1 X7.988095055995309 Y2.608458362116234 F300 +G1 X7.960996472680924 Y2.6936033372019863 F300 +G1 X7.9191755060150015 Y2.7725653961108554 F300 +G1 X7.8639677503889835 Y2.842822806529529 F300 +G1 X7.797136320686543 Y2.902131827798875 F300 +G1 X7.720815545446657 Y2.948598367015636 F300 +G1 X7.637442804947386 Y2.980738468783386 F300 +G1 X7.549680691029316 Y2.9975257068120698 F300 +G1 X7.460331974562534 Y2.998423963868003 F300 +G1 X7.372250096156122 Y2.9834045532138482 F300 +G1 X7.288248038678869 Y2.952947134748255 F300 +G1 X7.211008491838367 Y2.9080243965873427 F300 +G1 X7.142998177802515 Y2.850070991297016 F300 +G1 X7.086389073960362 Y2.7809377188286284 F300 +G1 X7.0429890486522115 Y2.7028324193717292 F300 +G1 X7.014184125086303 Y2.6182494637697746 F300 +G1 X7.000894217301639 Y2.5298900932928685 F300 +G1 X7 Y2.5 F300 +G1 X7.007984 Y2.4110041813117045 F300 +G1 X7.0316810229760005 Y2.324850533089038 F300 +G1 X7.070334280802238 Y2.24429045824164 F300 +G1 X7.122709343036777 Y2.171896723319837 F300 +G1 X7.187133559692093 Y2.109981294642092 F300 +G1 X7.261549478985081 Y2.0605215033386575 F300 +G1 X7.343580554117203 Y2.025096897304599 F300 +G1 X7.430607040673038 Y2.004838796758221 F300 +G1 X7.519849660777937 Y2.000394164398572 F300 +G1 X7.608458362116234 Y2.0119049440046908 F300 +G1 X7.693603337201985 Y2.0390035273190756 F300 +G1 X7.7725653961108545 Y2.080824493984998 F300 +G1 X7.842822806529528 Y2.136032249611016 F300 +G1 X7.902131827798875 Y2.2028636793134564 F300 +G1 X7.948598367015635 Y2.279184454553342 F300 +G1 X7.980738468783386 Y2.362557195052612 F300 +G1 X7.99752570681207 Y2.4503193089706823 F300 +G1 X7.998423963868003 Y2.5396680254374644 F300 +G1 X7.983404553213848 Y2.627749903843876 F300 +G1 X7.952947134748256 Y2.711751961321129 F300 +G1 X7.908024396587344 Y2.7889915081616308 F300 +G1 X7.850070991297018 Y2.857001822197483 F300 +G1 X7.78093771882863 Y2.9136109260396363 F300 +G1 X7.702832419371732 Y2.9570109513477876 F300 +G1 X7.618249463769778 Y2.9858158749136963 F300 +G1 X7.529890093292872 Y2.999105782698361 F300 +G1 X7.5000000000000036 Y3 F300 +G1 X7.411004181311708 Y2.9920160000000005 F300 +G1 X7.324850533089041 Y2.9683189770240013 F300 +G1 X7.244290458241643 Y2.9296657191977635 F300 +G1 X7.17189672331984 Y2.8772906569632255 F300 +G1 X7.109981294642094 Y2.81286644030791 F300 +G1 X7.060521503338659 Y2.7384505210149213 F300 +G1 X7.0250968973046 Y2.6564194458828 F300 +G1 X7.0048387967582215 Y2.5693929593269655 F300 +G1 X7.000394164398572 Y2.480150339222065 F300 +G1 X7.011904944004691 Y2.391541637883769 F300 +G1 X7.039003527319075 Y2.306396662798017 F300 +G1 X7.080824493984997 Y2.227434603889147 F300 +G1 X7.136032249611015 Y2.1571771934704733 F300 +G1 X7.202863679313455 Y2.0978681722011268 F300 +G1 X7.279184454553341 Y2.051401632984365 F300 +G1 X7.36255719505261 Y2.019261531216615 F300 +G1 X7.4503193089706805 Y2.0024742931879307 F300 +G1 X7.539668025437463 Y2.0015760361319965 F300 +G1 X7.627749903843874 Y2.016595446786151 F300 +G1 X7.711751961321127 Y2.047052865251743 F300 +G1 X7.788991508161629 Y2.091975603412655 F300 +G1 X7.857001822197482 Y2.1499290087029808 F300 +G1 X7.9136109260396355 Y2.219062281171368 F300 +G1 X7.957010951347787 Y2.2971675806282663 F300 +G1 X7.985815874913696 Y2.3817505362302205 F300 +G1 X7.9991057826983605 Y2.470109906707126 F300 +G1 X8 Y2.4999999999999987 F300 +G1 X8 Y2.5 F300 G0 X0 Y0 M2 diff --git a/tests/square.svg b/tests/square.svg index 55bfc25..f0f8d90 100644 --- a/tests/square.svg +++ b/tests/square.svg @@ -7,8 +7,8 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - sodipodi:docname="square.svg" - inkscape:version="1.0 (4035a4fb49, 2020-05-01)" + sodipodi:docname="square_transformed.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)" id="svg8" version="1.1" viewBox="0 0 10 10" @@ -20,16 +20,16 @@ inkscape:window-maximized="1" inkscape:window-y="0" inkscape:window-x="0" - inkscape:window-height="2109" - inkscape:window-width="3836" + inkscape:window-height="768" + inkscape:window-width="1366" units="mm" showgrid="true" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" - inkscape:cy="35.055997" - inkscape:cx="21.930942" - inkscape:zoom="22.4" + inkscape:cy="34.247607" + inkscape:cx="7.5651517" + inkscape:zoom="9.1957985" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" @@ -51,7 +51,7 @@ image/svg+xml - + @@ -63,5 +63,9 @@ id="path838" d="M 1,1 H 9 V 9 H 1 Z" style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + diff --git a/tests/square_transformed.gcode b/tests/square_transformed.gcode index 165af53..eb62628 100644 --- a/tests/square_transformed.gcode +++ b/tests/square_transformed.gcode @@ -1,11 +1,120 @@ G21 -G94 G90 -G0 X0 Y19;svg#svg8 > g#layer1 > path#path838 -G0 X9 Y8 -G1 X9 Y0 F300 -G1 X1 Y0 F300 -G1 X0.9999999999999982 Y8 F300 -G1 X9 Y8 F300 -G0 X0 Y19 +G0 X0 Y0;svg#svg8 > g#layer1 > path#path838 +G0 X1 Y0.9999999999999999 +G1 X1.0000000000000004 Y9 F300 +G1 X9 Y9 F300 +G1 X9 Y0.9999999999999994 F300 +G1 X1 Y0.9999999999999999 F300;svg#svg8 > g#layer1 > path#path832 +G0 X7.500000000000001 Y7.999999999999999 +G1 X7.411004181311705 Y7.992016 F300 +G1 X7.324850533089039 Y7.9683189770239995 F300 +G1 X7.244290458241641 Y7.929665719197761 F300 +G1 X7.171896723319838 Y7.877290656963223 F300 +G1 X7.109981294642092 Y7.812866440307907 F300 +G1 X7.0605215033386575 Y7.738450521014919 F300 +G1 X7.025096897304599 Y7.656419445882797 F300 +G1 X7.004838796758221 Y7.569392959326962 F300 +G1 X7.000394164398572 Y7.480150339222062 F300 +G1 X7.011904944004691 Y7.3915416378837655 F300 +G1 X7.039003527319076 Y7.306396662798013 F300 +G1 X7.0808244939849985 Y7.227434603889144 F300 +G1 X7.1360322496110165 Y7.157177193470471 F300 +G1 X7.202863679313457 Y7.0978681722011245 F300 +G1 X7.279184454553342 Y7.051401632984363 F300 +G1 X7.362557195052613 Y7.019261531216613 F300 +G1 X7.450319308970683 Y7.002474293187929 F300 +G1 X7.539668025437465 Y7.001576036131996 F300 +G1 X7.627749903843877 Y7.016595446786151 F300 +G1 X7.71175196132113 Y7.047052865251743 F300 +G1 X7.788991508161631 Y7.0919756034126555 F300 +G1 X7.857001822197484 Y7.149929008702982 F300 +G1 X7.913610926039636 Y7.21906228117137 F300 +G1 X7.957010951347788 Y7.297167580628268 F300 +G1 X7.985815874913697 Y7.381750536230222 F300 +G1 X7.999105782698361 Y7.470109906707128 F300 +G1 X8 Y7.499999999999997 F300 +G1 X7.9920160000000005 Y7.588995818688293 F300 +G1 X7.968318977024 Y7.67514946691096 F300 +G1 X7.929665719197763 Y7.755709541758358 F300 +G1 X7.877290656963225 Y7.82810327668016 F300 +G1 X7.812866440307909 Y7.890018705357906 F300 +G1 X7.73845052101492 Y7.939478496661341 F300 +G1 X7.656419445882799 Y7.9749031026954 F300 +G1 X7.569392959326964 Y7.995161203241778 F300 +G1 X7.480150339222064 Y7.999605835601427 F300 +G1 X7.391541637883768 Y7.988095055995308 F300 +G1 X7.306396662798016 Y7.9609964726809235 F300 +G1 X7.2274346038891455 Y7.9191755060150015 F300 +G1 X7.157177193470472 Y7.8639677503889835 F300 +G1 X7.097868172201126 Y7.797136320686543 F300 +G1 X7.051401632984365 Y7.720815545446658 F300 +G1 X7.019261531216614 Y7.637442804947387 F300 +G1 X7.00247429318793 Y7.549680691029317 F300 +G1 X7.001576036131997 Y7.460331974562535 F300 +G1 X7.016595446786152 Y7.372250096156122 F300 +G1 X7.047052865251744 Y7.288248038678869 F300 +G1 X7.091975603412656 Y7.211008491838367 F300 +G1 X7.149929008702983 Y7.142998177802515 F300 +G1 X7.219062281171371 Y7.086389073960362 F300 +G1 X7.297167580628269 Y7.042989048652211 F300 +G1 X7.381750536230223 Y7.014184125086302 F300 +G1 X7.47010990670713 Y7.000894217301639 F300 +G1 X7.5 Y6.999999999999999 F300 +G1 X7.588995818688295 Y7.007983999999999 F300 +G1 X7.675149466910961 Y7.031681022975999 F300 +G1 X7.755709541758359 Y7.070334280802237 F300 +G1 X7.828103276680162 Y7.122709343036775 F300 +G1 X7.890018705357908 Y7.187133559692091 F300 +G1 X7.9394784966613425 Y7.26154947898508 F300 +G1 X7.974903102695401 Y7.343580554117201 F300 +G1 X7.995161203241779 Y7.430607040673036 F300 +G1 X7.999605835601428 Y7.519849660777936 F300 +G1 X7.988095055995309 Y7.608458362116233 F300 +G1 X7.960996472680924 Y7.693603337201984 F300 +G1 X7.9191755060150015 Y7.772565396110854 F300 +G1 X7.8639677503889835 Y7.842822806529528 F300 +G1 X7.797136320686543 Y7.902131827798874 F300 +G1 X7.720815545446658 Y7.948598367015635 F300 +G1 X7.637442804947387 Y7.980738468783385 F300 +G1 X7.549680691029317 Y7.997525706812069 F300 +G1 X7.460331974562535 Y7.998423963868002 F300 +G1 X7.372250096156123 Y7.983404553213847 F300 +G1 X7.28824803867887 Y7.952947134748255 F300 +G1 X7.211008491838368 Y7.908024396587342 F300 +G1 X7.142998177802516 Y7.850070991297016 F300 +G1 X7.086389073960363 Y7.780937718828628 F300 +G1 X7.0429890486522115 Y7.702832419371729 F300 +G1 X7.014184125086303 Y7.618249463769775 F300 +G1 X7.000894217301639 Y7.5298900932928685 F300 +G1 X7 Y7.5 F300 +G1 X7.0079839999999995 Y7.411004181311705 F300 +G1 X7.031681022976 Y7.324850533089039 F300 +G1 X7.070334280802238 Y7.2442904582416405 F300 +G1 X7.122709343036776 Y7.171896723319838 F300 +G1 X7.187133559692092 Y7.109981294642092 F300 +G1 X7.2615494789850805 Y7.0605215033386575 F300 +G1 X7.343580554117202 Y7.025096897304599 F300 +G1 X7.430607040673037 Y7.0048387967582215 F300 +G1 X7.519849660777936 Y7.000394164398572 F300 +G1 X7.608458362116233 Y7.011904944004691 F300 +G1 X7.693603337201985 Y7.039003527319076 F300 +G1 X7.7725653961108545 Y7.080824493984998 F300 +G1 X7.842822806529528 Y7.136032249611016 F300 +G1 X7.902131827798874 Y7.202863679313456 F300 +G1 X7.948598367015635 Y7.2791844545533415 F300 +G1 X7.980738468783385 Y7.362557195052611 F300 +G1 X7.99752570681207 Y7.450319308970681 F300 +G1 X7.998423963868003 Y7.5396680254374635 F300 +G1 X7.983404553213849 Y7.627749903843875 F300 +G1 X7.952947134748257 Y7.711751961321128 F300 +G1 X7.9080243965873445 Y7.78899150816163 F300 +G1 X7.850070991297018 Y7.857001822197482 F300 +G1 X7.780937718828631 Y7.9136109260396355 F300 +G1 X7.702832419371733 Y7.957010951347787 F300 +G1 X7.618249463769779 Y7.985815874913696 F300 +G1 X7.529890093292873 Y7.9991057826983605 F300 +G1 X7.500000000000004 Y8 F300 +G1 X7.500000000000001 Y7.999999999999999 F300 +G0 X0 Y0 M2 diff --git a/tests/square_transformed.svg b/tests/square_transformed.svg index 02f19c7..329cb24 100644 --- a/tests/square_transformed.svg +++ b/tests/square_transformed.svg @@ -7,8 +7,8 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - sodipodi:docname="square_translated.svg" - inkscape:version="1.0 (4035a4fb49, 2020-05-01)" + sodipodi:docname="square_transformed.svg" + inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)" id="svg8" version="1.1" viewBox="0 0 10 10" @@ -20,16 +20,16 @@ inkscape:window-maximized="1" inkscape:window-y="0" inkscape:window-x="0" - inkscape:window-height="2109" - inkscape:window-width="3836" + inkscape:window-height="768" + inkscape:window-width="1366" units="mm" showgrid="true" inkscape:document-rotation="0" inkscape:current-layer="layer1" inkscape:document-units="mm" - inkscape:cy="35.055997" - inkscape:cx="21.930942" - inkscape:zoom="22.4" + inkscape:cy="34.247607" + inkscape:cx="7.5651517" + inkscape:zoom="9.1957985" inkscape:pageshadow="2" inkscape:pageopacity="0.0" borderopacity="1.0" @@ -51,18 +51,22 @@ image/svg+xml - + + inkscape:label="Layer 1" + transform="rotate(-90, 5, 5)"> +