From 505a07b52442650206e4c3fc4e3d38bfef416424 Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Wed, 18 Aug 2021 01:13:22 -0400 Subject: [PATCH] code check-in: circular interpolation (WIP) --- Cargo.lock | 114 ++++++- LICENSE | 3 +- cli/Cargo.toml | 2 +- cli/src/main.rs | 20 +- lib/Cargo.toml | 3 +- lib/src/arc.rs | 428 +++++++++++++++++++++++++ lib/src/converter.rs | 54 ++-- lib/src/lib.rs | 40 ++- lib/src/machine.rs | 64 ++-- lib/src/postprocess.rs | 3 +- lib/src/turtle.rs | 330 +++++++++---------- lib/tests/circular_interpolation.gcode | 145 +++++++++ lib/tests/circular_interpolation.svg | 81 +++++ lib/tests/square.gcode | 50 +-- lib/tests/square_transformed.gcode | 54 ++-- lib/tests/square_viewport.gcode | 34 +- web/Cargo.toml | 2 +- web/src/inputs.rs | 34 +- web/src/main.rs | 7 +- web/src/spectre/mod.rs | 25 +- web/src/state.rs | 12 +- 21 files changed, 1167 insertions(+), 338 deletions(-) create mode 100644 lib/src/arc.rs create mode 100644 lib/tests/circular_interpolation.gcode create mode 100644 lib/tests/circular_interpolation.svg diff --git a/Cargo.lock b/Cargo.lock index 60893c2..c66b409 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,6 +85,37 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "cairo-rs" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a408c13bbc04c3337b94194c1a4d04067097439b79dbc1dcbceba299d828b9ea" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c9c3928781e8a017ece15eace05230f04b647457d170d2d9641c94a444ff80" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "cfg-expr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -157,6 +188,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "env_logger" version = "0.9.0" @@ -207,9 +244,9 @@ checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" [[package]] name = "g-code" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844e6deaab51dbee3328a56f233f3d8124cd62d4962c0f8783651ccaa4d88305" +checksum = "2403e3a8d80208fa702f2bc4588d8ea28ed456c7e552891eea6e7e9c68582167" dependencies = [ "codespan", "codespan-reporting", @@ -335,6 +372,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -433,6 +479,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + [[package]] name = "pretty_assertions" version = "0.6.1" @@ -561,6 +613,12 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "strsim" version = "0.8.0" @@ -591,10 +649,29 @@ dependencies = [ "syn", ] +[[package]] +name = "strum" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" + +[[package]] +name = "strum_macros" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "svg2gcode" version = "0.0.4" dependencies = [ + "cairo-rs", "euclid", "g-code", "log", @@ -665,6 +742,24 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-deps" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" +dependencies = [ + "anyhow", + "cfg-expr", + "heck", + "itertools", + "pkg-config", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -703,6 +798,15 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "typenum" version = "1.13.0" @@ -743,6 +847,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + [[package]] name = "version_check" version = "0.9.3" diff --git a/LICENSE b/LICENSE index fff0561..de65fb0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2019 Sameer Puri +Copyright (c) 2019-2021 Sameer Puri +Copyright (C) 2013-2015 by Vitaly Puzrin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7fae572..d927ec5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" svg2gcode = { path = "../lib" } env_logger = { version = "0", default-features = false, features = ["atty", "termcolor", "humantime"] } log = "0" -g-code = "0.3" +g-code = "0" codespan-reporting = "0.11" structopt = "0.3" roxmltree = "0" diff --git a/cli/src/main.rs b/cli/src/main.rs index d70d69f..9ca9222 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,7 +11,9 @@ use std::{ }; use structopt::StructOpt; -use svg2gcode::{set_origin, svg2program, ConversionOptions, Machine, Turtle}; +use svg2gcode::{ + set_origin, svg2program, ConversionOptions, Machine, SupportedFunctionality, Turtle, +}; #[derive(Debug, StructOpt)] #[structopt(name = "svg2gcode", author, about)] @@ -26,15 +28,15 @@ struct Opt { #[structopt(long, default_value = "96")] dpi: f64, #[structopt(alias = "tool_on_sequence", long = "on")] - /// Tool on GCode sequence + /// Tool on "G-Code sequence tool_on_sequence: Option, #[structopt(alias = "tool_off_sequence", long = "off")] - /// Tool off GCode sequence + /// Tool off "G-Code sequence tool_off_sequence: Option, - /// Optional GCode begin sequence (i.e. change to a cutter tool) + /// Optional "G-Code begin sequence (i.e. change to a cutter tool) #[structopt(alias = "begin_sequence", long = "begin")] begin_sequence: Option, - /// Optional GCode end sequence, prior to program end (i.e. put away a cutter tool) + /// Optional "G-Code end sequence, prior to program end (i.e. put away a cutter tool) #[structopt(alias = "end_sequence", long = "end")] end_sequence: Option, /// A file path for an SVG, else reads from stdin @@ -46,6 +48,11 @@ struct Opt { /// on/off sequences. #[structopt(long, default_value = "0,0")] origin: String, + /// Whether to use circular arcs when generating g-code + /// + /// Please check if your machine supports G2/G3 commands before enabling this. + #[structopt(long)] + circular_interpolation: bool, } fn main() -> io::Result<()> { @@ -98,6 +105,9 @@ fn main() -> io::Result<()> { snippets { Machine::new( + SupportedFunctionality { + circular_interpolation: opt.circular_interpolation, + }, tool_on_action, tool_off_action, program_begin_sequence, diff --git a/lib/Cargo.toml b/lib/Cargo.toml index fbdd3d8..356e65f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/sameer/svg2gcode" license = "MIT" [dependencies] -g-code = "0.3" +g-code = "0.3.1" lyon_geom = ">= 0.17.2" euclid = "0.22" log = "0" @@ -25,3 +25,4 @@ wasmbind = [ "wasm-bindgen" ] [dev-dependencies] pretty_assertions = "0.6" +cairo-rs = { version = "^0", default-features = false, features = ["svg", "v1_16"] } diff --git a/lib/src/arc.rs b/lib/src/arc.rs new file mode 100644 index 0000000..5a61d93 --- /dev/null +++ b/lib/src/arc.rs @@ -0,0 +1,428 @@ +use euclid::Angle; +use lyon_geom::{ + Arc, ArcFlags, CubicBezierSegment, Line, LineSegment, Point, Scalar, SvgArc, Transform, Vector, +}; + +pub enum ArcOrLineSegment { + Arc(SvgArc), + Line(LineSegment), +} + +fn arc_from_endpoints_and_tangents( + from: Point, + from_tangent: Vector, + to: Point, + to_tangent: Vector, +) -> Option> { + let from_to = (from - to).length(); + let incenter = { + let from_tangent = Line { + point: from, + vector: from_tangent, + }; + let to_tangent = Line { + point: to, + vector: to_tangent, + }; + + let intersection = from_tangent.intersection(&to_tangent)?; + let from_intersection = (from - intersection).length(); + let to_intersection = (to - intersection).length(); + + (((from * to_intersection).to_vector() + + (to * from_intersection).to_vector() + + (intersection * from_to).to_vector()) + / (from_intersection + to_intersection + from_to)) + .to_point() + }; + + let get_perpendicular_bisector = |a, b| { + let vector: Vector = a - b; + let perpendicular_vector = Vector::from([-vector.y, vector.x]).normalize(); + Line { + point: LineSegment { from: a, to: b }.sample(S::HALF), + vector: perpendicular_vector, + } + }; + + let from_incenter_bisector = get_perpendicular_bisector(from, incenter); + let to_incenter_bisector = get_perpendicular_bisector(to, incenter); + let center = from_incenter_bisector.intersection(&to_incenter_bisector)?; + + let radius = (from - center).length(); + + // Use the 2D determinant + dot product to identify winding direction + // See https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands for + // a nice visual explanation of large arc and sweep + let flags = { + let from_center = (from - center).normalize(); + let to_center = (to - center).normalize(); + + let det = from_center.x * to_center.y - from_center.y * to_center.x; + let dot = from_center.dot(to_center); + let atan2 = det.atan2(dot); + ArcFlags { + large_arc: atan2.abs() >= S::PI(), + sweep: atan2.is_sign_positive(), + } + }; + + Some(SvgArc { + from, + to, + radii: Vector::splat(radius), + // This is a circular arc + x_rotation: Angle::zero(), + flags, + }) +} + +pub trait FlattenWithArcs { + fn flattened(&self, tolerance: S) -> Vec>; +} + +impl FlattenWithArcs for CubicBezierSegment +where + S: Scalar + Copy, +{ + /// Implementation of [Modeling of Bézier Curves Using a Combination of Linear and Circular Arc Approximations](https://sci-hub.st/https://doi.org/10.1109/CGIV.2012.20) + /// + /// There are some slight deviations like using monotonic ranges instead of bounding by inflection points. + /// + /// Kaewsaiha, P., & Dejdumrong, N. (2012). Modeling of Bézier Curves Using a Combination of Linear and Circular Arc Approximations. 2012 Ninth International Conference on Computer Graphics, Imaging and Visualization. doi:10.1109/cgiv.2012.20 + /// + fn flattened(&self, tolerance: S) -> Vec> { + if (self.to - self.from).square_length() < S::EPSILON { + return vec![]; + } else if self.is_linear(tolerance) { + return vec![ArcOrLineSegment::Line(self.baseline())]; + } + let mut acc = vec![]; + + self.for_each_monotonic_range(|range| { + let inner_bezier = self.split_range(range); + + if (inner_bezier.to - inner_bezier.from).square_length() < S::EPSILON { + return; + } else if inner_bezier.is_linear(tolerance) { + acc.push(ArcOrLineSegment::Line(inner_bezier.baseline())); + return; + } + + if let Some(svg_arc) = arc_from_endpoints_and_tangents( + inner_bezier.from, + inner_bezier.derivative(S::ZERO), + inner_bezier.to, + inner_bezier.derivative(S::ONE), + ) + .filter(|svg_arc| { + let arc = svg_arc.to_arc(); + let mut max_deviation = S::ZERO; + // TODO: find a better way to check tolerance + // Ideally: derivative of |f(x) - g(x)| and look at 0 crossings + for i in 1..20 { + let t = S::from(i).unwrap() / S::from(20).unwrap(); + max_deviation = + max_deviation.max((arc.sample(t) - inner_bezier.sample(t)).length()); + } + max_deviation < tolerance + }) { + acc.push(ArcOrLineSegment::Arc(svg_arc)); + } else { + let (left, right) = inner_bezier.split(S::HALF); + acc.append(&mut FlattenWithArcs::flattened(&left, tolerance)); + acc.append(&mut FlattenWithArcs::flattened(&right, tolerance)); + } + }); + acc + } +} + +impl FlattenWithArcs for SvgArc +where + S: Scalar, +{ + fn flattened(&self, tolerance: S) -> Vec> { + if (self.to - self.from).square_length() < S::EPSILON { + return vec![]; + } else if self.is_straight_line() { + return vec![ArcOrLineSegment::Line(LineSegment { + from: self.from, + to: self.to, + })]; + } else if (self.radii.x.abs() - self.radii.y.abs()).abs() < S::EPSILON { + return vec![ArcOrLineSegment::Arc(*self)]; + } + + let self_arc = self.to_arc(); + if let Some(svg_arc) = arc_from_endpoints_and_tangents( + self.from, + self_arc.sample_tangent(S::ZERO), + self.to, + self_arc.sample_tangent(S::ONE), + ) + .filter(|approx_svg_arc| { + let approx_arc = approx_svg_arc.to_arc(); + let mut max_deviation = S::ZERO; + // TODO: find a better way to check tolerance + // Ideally: derivative of |f(x) - g(x)| and look at 0 crossings + for i in 1..20 { + let t = S::from(i).unwrap() / S::from(20).unwrap(); + max_deviation = + max_deviation.max((approx_arc.sample(t) - self_arc.sample(t)).length()); + } + max_deviation < tolerance + }) { + vec![ArcOrLineSegment::Arc(svg_arc)] + } else { + let (left, right) = self_arc.split(S::HALF); + let mut acc = FlattenWithArcs::flattened(&to_svg_arc(left), tolerance); + acc.append(&mut FlattenWithArcs::flattened( + &to_svg_arc(right), + tolerance, + )); + acc + } + } +} + +/// Sanity holdover until https://github.com/nical/lyon/pull/693 is merged +pub fn to_svg_arc(arc: Arc) -> SvgArc { + let from = arc.sample(S::ZERO); + let to = arc.sample(S::ONE); + let flags = ArcFlags { + sweep: arc.sweep_angle.get() >= S::ZERO, + large_arc: S::abs(arc.sweep_angle.get()) >= S::PI(), + }; + SvgArc { + from, + to, + radii: arc.radii, + x_rotation: arc.x_rotation, + flags, + } +} + +pub trait Transformed { + fn transformed(&self, transform: &Transform) -> Self; +} + +impl Transformed for SvgArc { + /// A lot of the math here is heavily borrowed from [Vitaly Putzin's svgpath](https://github.com/fontello/svgpath). + /// + /// The code is Rust-ified with only one or two changes, but I plan to understand the math here and + /// merge changes upstream to lyon-geom. + fn transformed(&self, transform: &Transform) -> Self { + let from = transform.transform_point(self.from); + let to = transform.transform_point(self.to); + + // Translation does not affect rotation, radii, or flags + let [a, b, c, d, _tx, _ty] = transform.to_array(); + let (x_rotation, radii) = { + let (sin, cos) = self.x_rotation.sin_cos(); + + // Radii are axis-aligned -- rotate & transform + let ma = [ + self.radii.x * (a * cos + c * sin), + self.radii.x * (b * cos + d * sin), + self.radii.y * (-a * sin + c * cos), + self.radii.y * (-b * sin + d * cos), + ]; + + // ma * transpose(ma) = [ J L ] + // [ L K ] + // L is calculated later (if the image is not a circle) + let J = ma[0].powi(2) + ma[2].powi(2); + let K = ma[1].powi(2) + ma[3].powi(2); + + // the discriminant of the characteristic polynomial of ma * transpose(ma) + let D = ((ma[0] - ma[3]).powi(2) + (ma[2] + ma[1]).powi(2)) + * ((ma[0] + ma[3]).powi(2) + (ma[2] - ma[1]).powi(2)); + + // the "mean eigenvalue" + let JK = (J + K) / S::TWO; + + // check if the image is (almost) a circle + if D < S::EPSILON * JK { + // if it is + (Angle::zero(), Vector::splat(JK.sqrt())) + } else { + // if it is not a circle + let L = ma[0] * ma[1] + ma[2] * ma[3]; + + let D = D.sqrt(); + + // {l1,l2} = the two eigen values of ma * transpose(ma) + let l1 = JK + D / S::TWO; + let l2 = JK - D / S::TWO; + // the x - axis - rotation angle is the argument of the l1 - eigenvector + let ax = if L.abs() < S::EPSILON && (l1 - K).abs() < S::EPSILON { + Angle::frac_pi_2() + } else { + Angle::radians( + (if L.abs() > (l1 - K).abs() { + (l1 - J) / L + } else { + L / (l1 - K) + }) + .atan(), + ) + }; + (ax, Vector::from([l1.sqrt(), l2.sqrt()])) + } + }; + // A mirror transform causes this flag to be flipped + let invert_sweep = { (a * d) - (b * c) < S::ZERO }; + let flags = ArcFlags { + sweep: if invert_sweep { + !self.flags.sweep + } else { + self.flags.sweep + }, + large_arc: self.flags.large_arc, + }; + Self { + from, + to, + radii, + x_rotation, + flags, + } + } +} + +#[cfg(test)] +mod tests { + use cairo::{Context, SvgSurface}; + use lyon_geom::{point, vector, CubicBezierSegment, Point, Vector}; + use std::path::PathBuf; + use svgtypes::PathParser; + + use crate::arc::{ArcOrLineSegment, FlattenWithArcs}; + + #[test] + fn flatten_returns_expected_arcs() { + const PATH: &'static str = "M 8.0549,11.9023 + c + 0.13447,1.69916 8.85753,-5.917903 7.35159,-6.170957 + z"; + let mut surf = + SvgSurface::new(128., 128., Some(PathBuf::from("approx_circle.svg"))).unwrap(); + surf.set_document_unit(cairo::SvgUnit::Mm); + let ctx = Context::new(&surf).unwrap(); + ctx.set_line_width(0.2); + + let mut current_position = Point::zero(); + + let mut acc = 0; + + for path in PathParser::from(PATH) { + use svgtypes::PathSegment::*; + match path.unwrap() { + MoveTo { x, y, abs } => { + if abs { + ctx.move_to(x, y); + current_position = point(x, y); + } else { + ctx.rel_move_to(x, y); + current_position += vector(x, y); + } + } + LineTo { x, y, abs } => { + if abs { + ctx.line_to(x, y); + current_position = point(x, y); + } else { + ctx.rel_line_to(x, y); + current_position += vector(x, y); + } + } + ClosePath { .. } => ctx.close_path(), + CurveTo { + x1, + y1, + x2, + y2, + x, + y, + abs, + } => { + ctx.set_dash(&[], 0.); + match acc { + 0 => ctx.set_source_rgb(1., 0., 0.), + 1 => ctx.set_source_rgb(0., 1., 0.), + 2 => ctx.set_source_rgb(0., 0., 1.), + 3 => ctx.set_source_rgb(0., 0., 0.), + _ => unreachable!(), + } + let curve = CubicBezierSegment { + from: current_position, + ctrl1: (vector(x1, y1) + + if !abs { + current_position.to_vector() + } else { + Vector::zero() + }) + .to_point(), + ctrl2: (vector(x2, y2) + + if !abs { + current_position.to_vector() + } else { + Vector::zero() + }) + .to_point(), + to: (vector(x, y) + + if !abs { + current_position.to_vector() + } else { + Vector::zero() + }) + .to_point(), + }; + for segment in FlattenWithArcs::flattened(&curve, 0.02) { + match segment { + ArcOrLineSegment::Arc(svg_arc) => { + let arc = svg_arc.to_arc(); + if svg_arc.flags.sweep { + ctx.arc( + arc.center.x, + arc.center.y, + arc.radii.x, + arc.start_angle.radians, + (arc.start_angle + arc.sweep_angle).radians, + ) + } else { + ctx.arc_negative( + arc.center.x, + arc.center.y, + arc.radii.x, + arc.start_angle.radians, + (arc.start_angle + arc.sweep_angle).radians, + ) + } + } + ArcOrLineSegment::Line(line) => ctx.line_to(line.to.x, line.to.y), + } + } + + ctx.stroke().unwrap(); + + current_position = curve.to; + ctx.set_dash(&[0.1], 0.); + ctx.move_to(curve.from.x, curve.from.y); + ctx.curve_to( + curve.ctrl1.x, + curve.ctrl1.y, + curve.ctrl2.x, + curve.ctrl2.y, + curve.to.x, + curve.to.y, + ); + ctx.stroke().unwrap(); + acc += 1; + } + other => unimplemented!("{:?}", other), + } + } + } +} diff --git a/lib/src/converter.rs b/lib/src/converter.rs index 8b7b973..534d14e 100644 --- a/lib/src/converter.rs +++ b/lib/src/converter.rs @@ -5,7 +5,7 @@ use g_code::{command, emit::Token}; use log::{debug, warn}; use lyon_geom::{ euclid::{default::Transform2D, Angle, Transform3D}, - vector, + point, vector, ArcFlags, }; use roxmltree::{Document, Node}; use svgtypes::{ @@ -192,8 +192,8 @@ fn width_and_height_into_transform( } } -fn apply_path<'a, 'input>( - turtle: &'a mut Turtle<'input>, +fn apply_path<'input>( + turtle: &'_ mut Turtle<'input>, options: &ConversionOptions, path: &str, ) -> Vec> { @@ -206,11 +206,11 @@ fn apply_path<'a, 'input>( 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) + turtle.close(options.feedrate) } - 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), + LineTo { abs, x, y } => turtle.line(abs, x, y, options.feedrate), + HorizontalLineTo { abs, x } => turtle.line(abs, x, None, options.feedrate), + VerticalLineTo { abs, y } => turtle.line(abs, None, y, options.feedrate), CurveTo { abs, x1, @@ -221,42 +221,30 @@ fn apply_path<'a, 'input>( y, } => turtle.cubic_bezier( abs, - x1, - y1, - x2, - y2, - x, - y, + point(x1, y1), + point(x2, y2), + point(x, y), options.tolerance, - None, options.feedrate, ), SmoothCurveTo { abs, x2, y2, x, y } => turtle.smooth_cubic_bezier( abs, - x2, - y2, - x, - y, + point(x2, y2), + point(x, y), options.tolerance, - None, options.feedrate, ), Quadratic { abs, x1, y1, x, y } => turtle.quadratic_bezier( abs, - x1, - y1, - x, - y, + point(x1, y1), + point(x, y), options.tolerance, - None, options.feedrate, ), SmoothQuadratic { abs, x, y } => turtle.smooth_quadratic_bezier( abs, - x, - y, + point(x, y), options.tolerance, - None, options.feedrate, ), EllipticalArc { @@ -270,14 +258,10 @@ fn apply_path<'a, 'input>( y, } => turtle.elliptical( abs, - rx, - ry, - x_axis_rotation, - large_arc, - sweep, - x, - y, - None, + vector(rx, ry), + Angle::degrees(x_axis_rotation), + ArcFlags { large_arc, sweep }, + point(x, y), options.feedrate, options.tolerance, ), diff --git a/lib/src/lib.rs b/lib/src/lib.rs index fa5fbb5..2e18415 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1,16 +1,19 @@ -/// Converts an SVG to GCode in an internal representation +/// Approximate [Bézier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) with [Circular arcs](https://en.wikipedia.org/wiki/Circular_arc) +mod arc; +/// Converts an SVG to "G-Code in an internal representation mod converter; -/// Emulates the state of an arbitrary machine that can run GCode +/// Emulates the state of an arbitrary machine that can run "G-Code mod machine; -/// Operations that are easier to implement after GCode is generated, or would +/// Operations that are easier to implement after "G-Code is generated, or would /// otherwise over-complicate SVG conversion mod postprocess; -/// Provides an interface for drawing lines in GCode +/// Provides an interface for drawing lines in "G-Code /// This concept is referred to as [Turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics). mod turtle; pub use converter::{svg2program, ConversionOptions}; pub use machine::Machine; +pub use machine::SupportedFunctionality; pub use postprocess::set_origin; pub use turtle::Turtle; @@ -20,11 +23,19 @@ mod test { use g_code::emit::{format_gcode_fmt, FormatOptions}; use pretty_assertions::assert_eq; - fn get_actual(input: &str) -> String { + fn get_actual(input: &str, circular_interpolation: bool) -> String { let options = ConversionOptions::default(); let document = roxmltree::Document::parse(input).unwrap(); - let mut turtle = Turtle::new(Machine::default()); + let mut turtle = Turtle::new(Machine::new( + SupportedFunctionality { + circular_interpolation, + }, + None, + None, + None, + None, + )); let mut program = converter::svg2program(&document, options, &mut turtle); postprocess::set_origin(&mut program, [0., 0.]); @@ -36,7 +47,7 @@ mod test { #[test] fn square_produces_expected_gcode() { let square = include_str!("../tests/square.svg"); - let actual = get_actual(square); + let actual = get_actual(square, false); assert_eq!(actual, include_str!("../tests/square.gcode")) } @@ -44,7 +55,7 @@ mod test { #[test] fn square_transformed_produces_expected_gcode() { let square_transformed = include_str!("../tests/square_transformed.svg"); - let actual = get_actual(square_transformed); + let actual = get_actual(square_transformed, false); assert_eq!(actual, include_str!("../tests/square_transformed.gcode")) } @@ -52,8 +63,19 @@ mod test { #[test] fn square_viewport_produces_expected_gcode() { let square_transformed = include_str!("../tests/square_viewport.svg"); - let actual = get_actual(square_transformed); + let actual = get_actual(square_transformed, false); assert_eq!(actual, include_str!("../tests/square_viewport.gcode")) } + + #[test] + fn circular_interpolation_produces_expected_gcode() { + let circular_interpolation = include_str!("../tests/circular_interpolation.svg"); + let actual = get_actual(circular_interpolation, true); + + assert_eq!( + actual, + include_str!("../tests/circular_interpolation.gcode") + ) + } } diff --git a/lib/src/machine.rs b/lib/src/machine.rs index 6761730..9af337f 100644 --- a/lib/src/machine.rs +++ b/lib/src/machine.rs @@ -35,40 +35,61 @@ impl std::ops::Not for 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, Default)] +/// This is used to reduce output "G-Code verbosity and run repetitive actions. +#[derive(Debug, Default, Clone)] pub struct Machine<'input> { + supported_functionality: SupportedFunctionality, tool_state: Option, distance_mode: Option, - pub tool_on_action: Option>, - pub tool_off_action: Option>, - pub program_begin_sequence: Option>, - pub program_end_sequence: Option>, + tool_on_action: Vec>, + tool_off_action: Vec>, + program_begin_sequence: Vec>, + program_end_sequence: Vec>, +} + +#[derive(Debug, Default, Clone)] +pub struct SupportedFunctionality { + /// Indicates support for G2/G3 circular interpolation. + /// + /// Most modern machines support this. Old ones like early MakerBot 3D printers do not. + pub circular_interpolation: bool, } impl<'input> Machine<'input> { pub fn new( + supported_functionality: SupportedFunctionality, tool_on_action: Option>, tool_off_action: Option>, program_begin_sequence: Option>, program_end_sequence: Option>, ) -> Self { Self { - tool_on_action, - tool_off_action, - program_begin_sequence, - program_end_sequence, + supported_functionality, + tool_on_action: tool_on_action + .map(|s| s.iter_emit_tokens().collect()) + .unwrap_or_default(), + tool_off_action: tool_off_action + .map(|s| s.iter_emit_tokens().collect()) + .unwrap_or_default(), + program_begin_sequence: program_begin_sequence + .map(|s| s.iter_emit_tokens().collect()) + .unwrap_or_default(), + program_end_sequence: program_end_sequence + .map(|s| s.iter_emit_tokens().collect()) + .unwrap_or_default(), ..Default::default() } } + + pub fn supported_functionality(&self) -> &SupportedFunctionality { + &self.supported_functionality + } + /// Output gcode to turn the tool on. pub fn tool_on(&mut self) -> Vec> { if self.tool_state == Some(Tool::Off) || self.tool_state == None { self.tool_state = Some(Tool::On); - self.tool_on_action - .iter() - .flat_map(Snippet::iter_emit_tokens) - .collect() + self.tool_on_action.clone() } else { vec![] } @@ -78,10 +99,7 @@ impl<'input> Machine<'input> { pub fn tool_off(&mut self) -> Vec> { if self.tool_state == Some(Tool::On) || self.tool_state == None { self.tool_state = Some(Tool::Off); - self.tool_off_action - .iter() - .flat_map(Snippet::iter_emit_tokens) - .collect() + self.tool_off_action.clone() } else { vec![] } @@ -89,18 +107,12 @@ impl<'input> Machine<'input> { /// Output user-defined setup gcode pub fn program_begin(&self) -> Vec> { - self.program_begin_sequence - .iter() - .flat_map(Snippet::iter_emit_tokens) - .collect() + self.program_begin_sequence.clone() } /// Output user-defined teardown gcode pub fn program_end(&self) -> Vec> { - self.program_end_sequence - .iter() - .flat_map(Snippet::iter_emit_tokens) - .collect() + self.program_end_sequence.clone() } /// Output absolute distance field if mode was relative or unknown. diff --git a/lib/src/postprocess.rs b/lib/src/postprocess.rs index b6a9d2c..3bcfd6a 100644 --- a/lib/src/postprocess.rs +++ b/lib/src/postprocess.rs @@ -1,6 +1,7 @@ use euclid::default::Box2D; use g_code::emit::{ - Field, Token, Value, ABSOLUTE_DISTANCE_MODE_FIELD, RELATIVE_DISTANCE_MODE_FIELD, + command::{ABSOLUTE_DISTANCE_MODE_FIELD, RELATIVE_DISTANCE_MODE_FIELD}, + Field, Token, Value, }; use lyon_geom::{point, vector, Point}; diff --git a/lib/src/turtle.rs b/lib/src/turtle.rs index 47c7731..61094f8 100644 --- a/lib/src/turtle.rs +++ b/lib/src/turtle.rs @@ -1,16 +1,13 @@ +use crate::arc::{to_svg_arc, ArcOrLineSegment, FlattenWithArcs, Transformed}; use crate::machine::Machine; -use g_code::{ - command, - emit::{Field, Token, Value}, -}; +use g_code::{command, emit::Token}; use lyon_geom::euclid::{default::Transform2D, Angle}; -use lyon_geom::{point, vector, Point}; +use lyon_geom::{point, vector, Point, Vector}; use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; -use std::borrow::Cow; type F64Point = Point; -/// Turtle graphics simulator for paths that outputs the gcode representation for each operation. +/// Turtle graphics simulator for paths that outputs the g-code representation for each operation. /// Handles transforms, position, offsets, etc. See https://www.w3.org/TR/SVG/paths.html #[derive(Debug)] pub struct Turtle<'input> { @@ -26,8 +23,8 @@ impl<'input> Turtle<'input> { /// Create a turtle at the origin with no transform pub fn new(machine: Machine<'input>) -> Self { Self { - current_position: point(0.0, 0.0), - initial_position: point(0.0, 0.0), + current_position: Point::zero(), + initial_position: Point::zero(), current_transform: Transform2D::identity(), transform_stack: vec![], machine, @@ -65,8 +62,7 @@ impl<'input> Turtle<'input> { }) .unwrap_or(original_current_position.y); - let mut to = point(x, y); - to = self.current_transform.transform_point(to); + let to = self.current_transform.transform_point(point(x, y)); self.current_position = to; self.initial_position = to; self.previous_control = None; @@ -74,7 +70,7 @@ impl<'input> Turtle<'input> { self.machine .tool_off() .drain(..) - .chain(self.machine.absolute().drain(..)) + .chain(self.machine.absolute()) .chain( command!(RapidPositioning { X: to.x as f64, @@ -85,34 +81,47 @@ impl<'input> Turtle<'input> { .collect() } - fn linear_interpolation(x: f64, y: f64, z: Option, f: Option) -> Vec> { - let mut linear_interpolation = command! {LinearInterpolation { X: x, Y: y, }}; - if let Some(z) = z { - linear_interpolation - .push(Field { - letters: Cow::Borrowed("Z"), - value: Value::Float(z), - }) - .unwrap(); - } - if let Some(f) = f { - linear_interpolation - .push(Field { - letters: Cow::Borrowed("F"), - value: Value::Float(f), - }) - .unwrap(); + fn linear_interpolation(x: f64, y: f64, feedrate: f64) -> Vec> { + command!(LinearInterpolation { + X: x, + Y: y, + F: feedrate, + }) + .into_token_vec() + } + + fn circular_interpolation(svg_arc: SvgArc, feedrate: f64) -> Vec> { + debug_assert!((svg_arc.radii.x.abs() - svg_arc.radii.y.abs()).abs() < f64::EPSILON); + match (svg_arc.flags.large_arc, svg_arc.flags.sweep) { + (false, true) => command!(CounterclockwiseCircularInterpolation { + X: svg_arc.to.x, + Y: svg_arc.to.y, + R: svg_arc.radii.x, + F: feedrate, + }) + .into_token_vec(), + (false, false) => command!(ClockwiseCircularInterpolation { + X: svg_arc.to.x, + Y: svg_arc.to.y, + R: svg_arc.radii.x, + F: feedrate, + }) + .into_token_vec(), + (true, _) => { + let (left, right) = svg_arc.to_arc().split(0.5); + let mut token_vec = Self::circular_interpolation(to_svg_arc(left), feedrate); + token_vec.append(&mut Self::circular_interpolation( + to_svg_arc(right), + feedrate, + )); + token_vec + } } - linear_interpolation.into_token_vec() } /// Close an SVG path, cutting back to its initial position /// https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand - pub fn close(&mut self, z: Z, f: F) -> Vec> - where - Z: Into>, - F: Into>, - { + pub fn close(&mut self, feedrate: f64) -> Vec> { // See https://www.w3.org/TR/SVG/paths.html#Segment-CompletingClosePath // which could result in a G91 G1 X0 Y0 if (self.current_position - self.initial_position) @@ -131,20 +140,17 @@ impl<'input> Turtle<'input> { .chain(Self::linear_interpolation( self.initial_position.x, self.initial_position.y, - z.into(), - f.into(), + feedrate, )) .collect() } /// Draw a line from the current position in the current transform to the specified position /// https://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands - pub fn line(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec> + pub fn line(&mut self, abs: bool, x: X, y: Y, feedrate: f64) -> Vec> where X: Into>, Y: Into>, - Z: Into>, - F: Into>, { let inverse_transform = self.current_transform.inverse().unwrap(); let original_current_position = inverse_transform.transform_point(self.current_position); @@ -169,8 +175,7 @@ impl<'input> Turtle<'input> { }) .unwrap_or(original_current_position.y); - let mut to = point(x, y); - to = self.current_transform.transform_point(to); + let to = self.current_transform.transform_point(point(x, y)); self.current_position = to; self.previous_control = None; @@ -178,73 +183,72 @@ impl<'input> Turtle<'input> { .tool_on() .drain(..) .chain(self.machine.absolute()) - .chain(Self::linear_interpolation(to.x, to.y, z.into(), f.into())) + .chain(Self::linear_interpolation(to.x, to.y, feedrate)) .collect() } /// Draw a cubic bezier curve segment /// The public bezier functions call this command after converting to a cubic bezier segment /// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands - fn bezier>, F: Into>>( + fn bezier( &mut self, cbs: CubicBezierSegment, tolerance: f64, - z: Z, - f: F, + feedrate: f64, ) -> Vec> { - let z = z.into(); - let f = f.into(); - let last_point = std::cell::Cell::new(self.current_position); - let cubic: Vec = cbs - .flattened(tolerance) - .flat_map(|point| { - last_point.set(point); - Self::linear_interpolation(point.x, point.y, z, f) - }) - .collect(); - self.current_position = last_point.get(); + let tokens: Vec<_> = if self + .machine + .supported_functionality() + .circular_interpolation + { + FlattenWithArcs::::flattened(&cbs, tolerance) + .drain(..) + .flat_map(|segment| match segment { + ArcOrLineSegment::Arc(arc) => Self::circular_interpolation(arc, feedrate), + ArcOrLineSegment::Line(line) => { + Self::linear_interpolation(line.to.x, line.to.y, feedrate) + } + }) + .collect() + } else { + cbs.flattened(tolerance) + .flat_map(|point| Self::linear_interpolation(point.x, point.y, feedrate)) + .collect() + }; + + self.current_position = cbs.to; + // See https://www.w3.org/TR/SVG/paths.html#ReflectedControlPoints - self.previous_control = point( - 2.0 * self.current_position.x - cbs.ctrl2.x, - 2.0 * self.current_position.y - cbs.ctrl2.y, - ) - .into(); + self.previous_control = Some( + point( + self.current_position.x - cbs.ctrl2.x, + self.current_position.y - cbs.ctrl2.y, + ) * 2., + ); self.machine .tool_on() .drain(..) .chain(self.machine.absolute()) - .chain(cubic) + .chain(tokens) .collect() } /// Draw a cubic curve from the current point to (x, y) with specified control points (x1, y1) and (x2, y2) /// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands - pub fn cubic_bezier( + pub fn cubic_bezier( &mut self, abs: bool, - x1: f64, - y1: f64, - x2: f64, - y2: f64, - x: f64, - y: f64, + mut ctrl1: Point, + mut ctrl2: Point, + mut to: Point, tolerance: f64, - z: Z, - f: F, - ) -> Vec> - where - Z: Into>, - F: Into>, - { + feedrate: f64, + ) -> Vec> { let from = self.current_position; - let mut ctrl1 = point(x1, y1); - let mut ctrl2 = point(x2, y2); - let mut to = point(x, y); if !abs { let inverse_transform = self.current_transform.inverse().unwrap(); - let original_current_position = - inverse_transform.transform_point(self.current_position); + let original_current_position = inverse_transform.transform_point(from); ctrl1 += original_current_position.to_vector(); ctrl2 += original_current_position.to_vector(); to += original_current_position.to_vector(); @@ -252,176 +256,154 @@ impl<'input> Turtle<'input> { ctrl1 = self.current_transform.transform_point(ctrl1); ctrl2 = self.current_transform.transform_point(ctrl2); to = self.current_transform.transform_point(to); + let cbs = lyon_geom::CubicBezierSegment { from, ctrl1, ctrl2, to, }; - - self.bezier(cbs, tolerance, z, f) + self.bezier(cbs, tolerance, feedrate) } /// Draw a shorthand/smooth cubic bezier segment, where the first control point was already given /// https://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands - pub fn smooth_cubic_bezier( + pub fn smooth_cubic_bezier( &mut self, abs: bool, - x2: f64, - y2: f64, - x: f64, - y: f64, + mut ctrl2: Point, + mut to: Point, tolerance: f64, - z: Z, - f: F, - ) -> Vec> - where - Z: Into>, - F: Into>, - { + feedrate: f64, + ) -> Vec> { let from = self.current_position; let ctrl1 = self.previous_control.unwrap_or(self.current_position); - let mut ctrl2 = point(x2, y2); - let mut to = point(x, y); if !abs { let inverse_transform = self.current_transform.inverse().unwrap(); - let original_current_position = - inverse_transform.transform_point(self.current_position); + let original_current_position = inverse_transform.transform_point(from); ctrl2 += original_current_position.to_vector(); to += original_current_position.to_vector(); } ctrl2 = self.current_transform.transform_point(ctrl2); to = self.current_transform.transform_point(to); + let cbs = lyon_geom::CubicBezierSegment { from, ctrl1, ctrl2, to, }; - - self.bezier(cbs, tolerance, z, f) + self.bezier(cbs, tolerance, feedrate) } /// Draw a shorthand/smooth cubic bezier segment, where the control point was already given /// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands - pub fn smooth_quadratic_bezier( + pub fn smooth_quadratic_bezier( &mut self, abs: bool, - x: f64, - y: f64, + mut to: Point, tolerance: f64, - z: Z, - f: F, - ) -> Vec> - where - Z: Into>, - F: Into>, - { + feedrate: f64, + ) -> Vec> { let from = self.current_position; let ctrl = self.previous_control.unwrap_or(self.current_position); - let mut to = point(x, y); if !abs { let inverse_transform = self.current_transform.inverse().unwrap(); - let original_current_position = - inverse_transform.transform_point(self.current_position); + let original_current_position = inverse_transform.transform_point(from); to += original_current_position.to_vector(); } to = self.current_transform.transform_point(to); - let qbs = QuadraticBezierSegment { from, ctrl, to }; - self.bezier(qbs.to_cubic(), tolerance, z, f) + let qbs = QuadraticBezierSegment { from, ctrl, to }; + self.bezier(qbs.to_cubic(), tolerance, feedrate) } /// Draw a quadratic bezier segment /// https://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands - pub fn quadratic_bezier( + pub fn quadratic_bezier( &mut self, abs: bool, - x1: f64, - y1: f64, - x: f64, - y: f64, + mut ctrl: Point, + mut to: Point, tolerance: f64, - z: Z, - f: F, - ) -> Vec> - where - Z: Into>, - F: Into>, - { + feedrate: f64, + ) -> Vec> { let from = self.current_position; - let mut ctrl = point(x1, y1); - let mut to = point(x, y); if !abs { let inverse_transform = self.current_transform.inverse().unwrap(); - let original_current_position = - inverse_transform.transform_point(self.current_position); + let original_current_position = inverse_transform.transform_point(from); to += original_current_position.to_vector(); ctrl += original_current_position.to_vector(); } ctrl = self.current_transform.transform_point(ctrl); to = self.current_transform.transform_point(to); - let qbs = QuadraticBezierSegment { from, ctrl, to }; - self.bezier(qbs.to_cubic(), tolerance, z, f) + let qbs = QuadraticBezierSegment { from, ctrl, to }; + self.bezier(qbs.to_cubic(), tolerance, feedrate) } - /// Draw an elliptical arc curve + /// Draw an elliptical arc segment /// https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands - pub fn elliptical( + pub fn elliptical( &mut self, abs: bool, - rx: f64, - ry: f64, - x_axis_rotation: f64, - large_arc: bool, - sweep: bool, - x: f64, - y: f64, - z: Z, - f: F, + radii: Vector, + x_rotation: Angle, + flags: ArcFlags, + mut to: Point, + feedrate: f64, tolerance: f64, - ) -> Vec> - where - Z: Into>, - F: Into>, - { - let z = z.into(); - let f = f.into(); + ) -> Vec> { + let from = self + .current_transform + .inverse() + .unwrap() + .transform_point(self.current_position); - let inverse_transform = self.current_transform.inverse().unwrap(); - let original_current_position = inverse_transform.transform_point(self.current_position); - let mut to: F64Point = point(x, y); if !abs { - to += original_current_position.to_vector(); + to += from.to_vector() } - let radii = vector(rx, ry); - let svg_arc = SvgArc { - from: original_current_position, + from, to, radii, - x_rotation: Angle::degrees(x_axis_rotation), - flags: ArcFlags { large_arc, sweep }, + x_rotation, + flags, + } + .transformed(&self.current_transform); + + let arc_tokens = if svg_arc.is_straight_line() { + Self::linear_interpolation(svg_arc.to.x, svg_arc.to.y, feedrate) + } else if self + .machine + .supported_functionality() + .circular_interpolation + { + FlattenWithArcs::flattened(&svg_arc, tolerance) + .drain(..) + .flat_map(|segment| match segment { + ArcOrLineSegment::Arc(arc) => Self::circular_interpolation(arc, feedrate), + ArcOrLineSegment::Line(line) => { + Self::linear_interpolation(line.to.x, line.to.y, feedrate) + } + }) + .collect() + } else { + svg_arc + .to_arc() + .flattened(tolerance) + .flat_map(|point| Self::linear_interpolation(point.x, point.y, feedrate)) + .collect() }; - let arc = svg_arc.to_arc(); - let last_point = std::cell::Cell::new(self.current_position); - - let mut ellipse = vec![]; - arc.flattened(tolerance).for_each(|point| { - let point = self.current_transform.transform_point(point); - ellipse.append(&mut Self::linear_interpolation(point.x, point.y, z, f)); - last_point.set(point); - }); - self.current_position = last_point.get(); + self.current_position = svg_arc.to; self.previous_control = None; self.machine .tool_on() .drain(..) .chain(self.machine.absolute()) - .chain(ellipse) + .chain(arc_tokens) .collect() } @@ -451,7 +433,7 @@ impl<'input> Turtle<'input> { /// 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 = Point::zero(); self.current_position = self .current_transform .transform_point(self.current_position); diff --git a/lib/tests/circular_interpolation.gcode b/lib/tests/circular_interpolation.gcode new file mode 100644 index 0000000..5a3488f --- /dev/null +++ b/lib/tests/circular_interpolation.gcode @@ -0,0 +1,145 @@ +G21 +G90;svg#svg8 > g#layer1 > path +G0 X1 Y9 +G1 X1.108112220817491 Y8.887981529182511 F300 +G1 X1.2153787137015988 Y8.768996286298401 F300 +G2 X1.4239916039361313 Y8.51350839606387 R5.11981430218831 F300 +G2 X1.7938566201357353 Y7.956143379864265 R4.835393156461376 F300 +G2 X1.9415770991666748 Y7.6677979008333255 R3.8134637231185913 F300 +G1 X2.0031742836480873 Y7.524169466351913 F300 +G1 X2.0554684608622846 Y7.382031539137715 F300 +G1 X2.0976139028758833 Y7.242229847124117 F300 +G1 X2.128764881755499 Y7.105610118244501 F300 +G1 X2.148075669567749 Y6.973018080432251 F300 +G2 X2.1547005383792515 Y6.8452994616207485 R1.2759926937417487 F300 +G2 X2.1397549853513067 Y6.668858900493001 R1.0008154063555685 F300 +G2 X2.0931488160479113 Y6.50733213111376 R0.7841189700439164 F300 +G2 X2.0122277651397247 Y6.363373418812364 R0.6809578083609312 F300 +G2 X1.8943375672974065 Y6.239637028918155 R0.6907022975872513 F300 +G1 X1.8206995803605288 Y6.186181662789164 F300 +G1 X1.7368239571916155 Y6.138777226760473 F300 +G1 X1.6423789146244991 Y6.097755503998249 F300 +G1 X1.5370326694930114 Y6.06344827766866 F300 +G1 X1.4204534386309855 Y6.036187330937873 F300 +G1 X1.2923094388722531 Y6.016304446972055 F300 +G1 X1.1522688870506472 Y6.004131408937375 F300 +G1 X1 Y6 F300;svg#svg8 > g#layer1 > path +G0 X1 Y5 +G3 X1.1025 Y4.9125 R0.81697048500483 F300 +G3 X1.2100000000000002 Y4.8500000000000005 R0.6432262761687367 F300 +G3 X1.4400000000000004 Y4.800000000000001 R0.5559102124679794 F300 +G3 X1.5625000000000004 Y4.8125 R0.6248177401751354 F300 +G3 X1.6900000000000004 Y4.8500000000000005 R0.7837286053113949 F300 +G3 X1.8225000000000007 Y4.9125 R1.0478526379133146 F300 +G1 X1.8906250000000004 Y4.953125 F300 +G1 X1.9600000000000004 Y5 F300 +G1 X2.0306250000000006 Y5.053125 F300 +G1 X2.1025000000000005 Y5.1125 F300 +G1 X2.175625 Y5.178125 F300 +G1 X2.2500000000000004 Y5.25 F300 +G1 X2.3256250000000005 Y5.328125 F300 +G1 X2.4025000000000007 Y5.4125 F300 +G1 X2.4806250000000007 Y5.503125 F300 +G1 X2.5600000000000005 Y5.6000000000000005 F300 +G1 X2.7225 Y5.8125 F300 +G1 X2.89 Y6.050000000000001 F300 +G1 X3.0625 Y6.3125 F300 +G1 X3.24 Y6.6 F300 +G1 X3.4225000000000003 Y6.9125 F300 +G1 X3.61 Y7.25 F300 +G1 X3.8025 Y7.612500000000001 F300 +G1 X4 Y8 F300;svg#svg8 > g#layer1 > path +G0 X7 Y9 +G2 X4 Y7 R2 F300;svg#svg8 > g#layer1 > path +G0 X1 Y4 +G3 X1.3978250325985413 Y3.968156676904629 R14.876279155824578 F300 +G3 X1.8079854783033578 Y3.946104968199289 R16.01024971452823 F300 +G3 X2.6441213528766796 Y3.932607474577383 R16.909229019806858 F300 +G3 X3.0621524614580276 Y3.9412866185390385 R16.902307637846288 F300 +G3 X3.474762735928376 Y3.9601731711554446 R16.28877876962794 F300 +G3 X3.8778904316608083 Y3.9890778449897057 R15.268674575836199 F300 +G3 X4.267739066726786 Y4.0277250178448885 R13.912776714019424 F300 +G3 X4.456063577509985 Y4.050532552971807 R12.739268728330957 F300 +G3 X4.63949362667036 Y4.075589152050236 R11.886079594871317 F300 +G3 X4.9912723595593835 Y4.132404125106506 R10.514058134441601 F300 +G3 X5.158404895076147 Y4.163979552996096 R9.124219221791998 F300 +G3 X5.319615850363981 Y4.197668785821917 R8.191555678247077 F300 +G3 X5.473047754155922 Y4.233066714906146 R7.337969988742633 F300 +G3 X5.61937862477731 Y4.270296440546117 R6.491816535421121 F300 +G3 X5.7594343548684455 Y4.309615721254547 R5.617604058887768 F300 +G3 X5.8915689090734364 Y4.350612794689171 R4.822901450495766 F300 +G3 X6.015159340298373 Y4.3930787556347495 R4.103817344924035 F300 +G3 X6.130250150760052 Y4.437012246178796 R3.44384482377765 F300 +G3 X6.236127588090238 Y4.48211064322029 R2.846139959892035 F300 +G3 X6.333048170507615 Y4.528450609158775 R2.3152352933048315 F300 +G3 X6.4208476002165105 Y4.575960377763483 R1.852483263205878 F300 +G3 X6.499231927533419 Y4.624488389580274 R1.4581571431397116 F300 +G3 X6.5681780722511505 Y4.674046907498437 R1.1299709145518102 F300 +G3 X6.6273047901063284 Y4.724392183641678 R0.8643192961566583 F300 +G3 X6.676477557058046 Y4.775412218763866 R0.6564208388485623 F300 +G3 X6.71556575202882 Y4.826974546197227 R0.5001673711308741 F300 +G3 X6.763123778129433 Y4.931181762950229 R0.350386491482661 F300 +G3 X6.769570039513327 Y5.036030574790918 R0.2731563882430039 F300 +G3 X6.734853500522512 Y5.140538119506337 R0.3127589306728062 F300 +G3 X6.702124660145641 Y5.192377872230199 R0.42576793104995136 F300 +G3 X6.659260252238352 Y5.243740866196475 R0.5529909118803078 F300 +G3 X6.606369810412423 Y5.294504520034969 R0.7277547047047377 F300 +G3 X6.543572649892903 Y5.344557674458401 R0.9564889710367651 F300 +G3 X6.471071971365293 Y5.393746987681996 R1.2449378228703627 F300 +G3 X6.389003780317571 Y5.441987645437967 R1.597798863130081 F300 +G3 X6.297869780912585 Y5.489015899982382 R2.0177297473684925 F300 +G3 X6.197643386983442 Y5.534873412599875 R2.505678774355954 F300 +G3 X6.088496927021365 Y5.579476569875775 R3.0612941796334256 F300 +G3 X5.970749082451981 Y5.622690761009283 R3.6808379091910886 F300 +G3 X5.844081790059699 Y5.664601230818244 R4.360700929904796 F300 +G3 X5.709324860866246 Y5.70490397007849 R5.102368612887797 F300 +G3 X5.567733146370779 Y5.743261946530389 R5.934854156765002 F300 +G3 X5.418814245543702 Y5.779844664709513 R6.798940676792501 F300 +G3 X5.261537124667706 Y5.814858597710807 R7.631496009070628 F300 +G3 X5.097548832796083 Y5.847892236765922 R8.528687532226238 F300 +G3 X4.9276849905723505 Y5.878789845134821 R9.47189940399368 F300 +G3 X4.751951029840501 Y5.9075625483307235 R10.4133131491631 F300 +G3 X4.386110365917731 Y5.958272783471742 R11.758269645230168 F300 +G3 X4.196158507416505 Y5.980211912487405 R13.046962723496428 F300 +G3 X4.002046461235404 Y5.999807994943473 R13.831352786873643 F300;svg#svg8 > g#layer1 > path +G0 X1 Y2 +G3 X2.7071067811865475 Y1.2928932188134523 R1 F300 +G3 X2.0000000000000004 Y3 R1 F300;svg#svg8 > g#layer1 > path +G0 X7 Y2 +G3 X7.154762363949493 Y2.109124437803701 R1.3064226822292095 F300 +G3 X7.282116680361381 Y2.2321077959060944 R1.0856892593761847 F300 +G3 X7.380081728935076 Y2.366714137608437 R0.9044562895491561 F300 +G3 X7.446435873469992 Y2.5098930035038536 R0.7704193428710345 F300 +G3 X7.479017753360532 Y2.809177694882109 R0.6803427665568468 F300 +G3 X7.444418384862246 Y2.9583778658834623 R0.7026557460446882 F300 +G3 X7.3766799299740935 Y3.1027417096732983 R0.7922831187696943 F300 +G3 X7.277389351167718 Y3.2389398933696354 R0.9367339275418858 F300 +G3 X7.148774650157433 Y3.3639502152420433 R1.125271732619803 F300 +G3 X6.993673148291746 Y3.474987144669766 R1.3540462899654855 F300 +G3 X6.815657407470125 Y3.5694700860997925 R1.5910816869891395 F300 +G3 X6.619051181089784 Y3.6451637975031086 R1.8311381180551767 F300 +G3 X6.408070496157389 Y3.700498981631148 R2.0295147528189914 F300 +G3 X5.961879008015639 Y3.7455746745355167 R2.20667175175377 F300 +G3 X5.51774327794617 Y3.7004924716052043 R2.177719386997694 F300 +G3 X5.309067761171235 Y3.6451184265986116 R1.9659448824172234 F300 +G3 X5.115776986833164 Y3.5694123856790956 R1.7512568735632301 F300 +G3 X4.941808710313139 Y3.4748620651716124 R1.509194160282913 F300 +G3 X4.791506394813442 Y3.3637428366487176 R1.2687625765933719 F300 +G3 X4.668454022834454 Y3.2387839626056327 R1.0557113303482242 F300 +G3 X4.575183183400513 Y3.1026532892406093 R0.8805630550146627 F300 +G3 X4.513751798468469 Y2.9582561971686174 R0.7547083052304115 F300 +G3 X4.4856847179921875 Y2.80902758022418 R0.6846718778044363 F300 +G3 X4.531388123426751 Y2.509739067236653 R0.6913082436962433 F300 +G3 X4.604045213168647 Y2.3665275739817453 R0.8110589553538909 F300 +G3 X4.707904549977265 Y2.231952183082983 R0.9631939268966888 F300 +G3 X4.840756715708391 Y2.108903718054256 R1.15692448047627 F300 +G3 X4.999519011965418 Y2.0002834466817996 R1.3906772528175693 F300 +G3 X5.221523434813825 Y1.8914584411737705 R1.6541860008828217 F300 +G3 X5.468658098289327 Y1.8111179864415283 R1.929021932467884 F300 +G3 X5.732969365528165 Y1.7618093869229279 R2.132487933789956 F300 +G3 X6.005616291542055 Y1.7451939792884783 R2.236659915629062 F300 +G3 X6.277559043718761 Y1.761826851152393 R2.218645912284593 F300 +G3 X6.539734110392067 Y1.811157851163688 R2.082787785187509 F300 +G3 X6.7834314749579985 Y1.891549416416874 R1.8563026877496185 F300 +G3 X7.00054219152339 Y2.000327963727709 R1.5688254782733724 F300 +M2 diff --git a/lib/tests/circular_interpolation.svg b/lib/tests/circular_interpolation.svg new file mode 100644 index 0000000..834aefe --- /dev/null +++ b/lib/tests/circular_interpolation.svg @@ -0,0 +1,81 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/lib/tests/square.gcode b/lib/tests/square.gcode index 1cd41d7..960863a 100644 --- a/lib/tests/square.gcode +++ b/lib/tests/square.gcode @@ -7,39 +7,39 @@ G1 X1 Y1 F300 G1 X1 Y9 F300;svg#svg8 > g#layer1 > path#path832 G0 X8 Y2.5 G1 X7.992016 Y2.4110041813117045 F300 -G1 X7.968318977024 Y2.3248505330890383 F300 -G1 X7.929665719197762 Y2.2442904582416405 F300 +G1 X7.968318977024 Y2.324850533089038 F300 +G1 X7.929665719197762 Y2.24429045824164 F300 G1 X7.877290656963224 Y2.171896723319837 F300 G1 X7.812866440307907 Y2.1099812946420924 F300 -G1 X7.738450521014919 Y2.0605215033386575 F300 +G1 X7.738450521014919 Y2.060521503338657 F300 G1 X7.656419445882797 Y2.025096897304599 F300 G1 X7.569392959326962 Y2.0048387967582206 F300 G1 X7.5 Y2 F300 -G1 X7.4110041813117045 Y2.0079840000000004 F300 -G1 X7.324850533089038 Y2.0316810229759996 F300 +G1 X7.4110041813117045 Y2.007984 F300 +G1 X7.324850533089038 Y2.031681022976 F300 G1 X7.2442904582416405 Y2.0703342808022382 F300 -G1 X7.171896723319837 Y2.1227093430367763 F300 -G1 X7.109981294642092 Y2.187133559692093 F300 -G1 X7.0605215033386575 Y2.2615494789850814 F300 +G1 X7.171896723319837 Y2.1227093430367767 F300 +G1 X7.109981294642092 Y2.1871335596920924 F300 +G1 X7.0605215033386575 Y2.261549478985082 F300 G1 X7.025096897304599 Y2.343580554117203 F300 -G1 X7.004838796758221 Y2.4306070406730376 F300 -G1 X7 Y2.5 F300 -G1 X7.007984 Y2.5889958186882955 F300 -G1 X7.0316810229760005 Y2.6751494669109617 F300 -G1 X7.070334280802238 Y2.7557095417583604 F300 +G1 X7.004838796758221 Y2.430607040673038 F300 +G1 X7 Y2.5000000000000004 F300 +G1 X7.007984 Y2.588995818688296 F300 +G1 X7.0316810229760005 Y2.675149466910962 F300 +G1 X7.070334280802238 Y2.75570954175836 F300 G1 X7.122709343036777 Y2.828103276680163 F300 -G1 X7.187133559692093 Y2.8900187053579076 F300 -G1 X7.261549478985081 Y2.9394784966613425 F300 -G1 X7.343580554117203 Y2.974903102695401 F300 +G1 X7.187133559692093 Y2.890018705357908 F300 +G1 X7.261549478985082 Y2.939478496661343 F300 +G1 X7.343580554117203 Y2.9749031026954014 F300 G1 X7.430607040673038 Y2.9951612032417794 F300 -G1 X7.499999999999999 Y3 F300 -G1 X7.588995818688295 Y2.9920160000000005 F300 -G1 X7.675149466910961 Y2.9683189770240004 F300 -G1 X7.755709541758359 Y2.9296657191977618 F300 -G1 X7.828103276680162 Y2.8772906569632237 F300 -G1 X7.890018705357908 Y2.812866440307908 F300 -G1 X7.9394784966613425 Y2.7384505210149195 F300 -G1 X7.974903102695401 Y2.656419445882798 F300 -G1 X7.995161203241779 Y2.5693929593269633 F300 +G1 X7.500000000000001 Y3 F300 +G1 X7.5889958186882955 Y2.992016 F300 +G1 X7.675149466910962 Y2.968318977024 F300 +G1 X7.7557095417583595 Y2.9296657191977618 F300 +G1 X7.828103276680163 Y2.8772906569632233 F300 +G1 X7.890018705357908 Y2.8128664403079076 F300 +G1 X7.9394784966613425 Y2.7384505210149186 F300 +G1 X7.974903102695401 Y2.656419445882797 F300 +G1 X7.995161203241779 Y2.5693929593269624 F300 G1 X8 Y2.5 F300 M2 diff --git a/lib/tests/square_transformed.gcode b/lib/tests/square_transformed.gcode index fec714f..1824a87 100644 --- a/lib/tests/square_transformed.gcode +++ b/lib/tests/square_transformed.gcode @@ -7,39 +7,39 @@ G1 X9 Y0.9999999999999994 F300 G1 X1 Y0.9999999999999999 F300;svg#svg8 > g#layer1 > path#path832 G0 X7.500000000000001 Y7.999999999999999 G1 X7.588995818688296 Y7.992015999999999 F300 -G1 X7.675149466910963 Y7.9683189770239995 F300 -G1 X7.75570954175836 Y7.929665719197761 F300 -G1 X7.828103276680164 Y7.877290656963223 F300 +G1 X7.675149466910963 Y7.968318977023999 F300 +G1 X7.75570954175836 Y7.92966571919776 F300 +G1 X7.828103276680164 Y7.877290656963222 F300 G1 X7.8900187053579085 Y7.812866440307906 F300 -G1 X7.939478496661343 Y7.738450521014918 F300 -G1 X7.974903102695402 Y7.656419445882796 F300 -G1 X7.99516120324178 Y7.5693929593269615 F300 +G1 X7.939478496661343 Y7.738450521014917 F300 +G1 X7.974903102695402 Y7.656419445882795 F300 +G1 X7.995161203241779 Y7.569392959326961 F300 G1 X8 Y7.499999999999999 F300 -G1 X7.9920160000000005 Y7.411004181311704 F300 -G1 X7.968318977024001 Y7.324850533089037 F300 +G1 X7.992016 Y7.411004181311704 F300 +G1 X7.968318977024 Y7.324850533089037 F300 G1 X7.929665719197762 Y7.24429045824164 F300 G1 X7.877290656963224 Y7.171896723319836 F300 G1 X7.812866440307907 Y7.1099812946420915 F300 G1 X7.738450521014919 Y7.060521503338657 F300 G1 X7.656419445882797 Y7.025096897304598 F300 G1 X7.569392959326962 Y7.00483879675822 F300 -G1 X7.500000000000001 Y6.999999999999999 F300 -G1 X7.411004181311705 Y7.007983999999999 F300 -G1 X7.324850533089038 Y7.031681022975999 F300 -G1 X7.2442904582416405 Y7.070334280802238 F300 -G1 X7.171896723319838 Y7.122709343036776 F300 -G1 X7.109981294642092 Y7.187133559692092 F300 -G1 X7.060521503338658 Y7.2615494789850805 F300 -G1 X7.0250968973046 Y7.343580554117202 F300 -G1 X7.0048387967582215 Y7.430607040673037 F300 -G1 X7.000000000000001 Y7.499999999999999 F300 -G1 X7.007984 Y7.588995818688295 F300 -G1 X7.0316810229760005 Y7.675149466910961 F300 -G1 X7.070334280802239 Y7.755709541758359 F300 -G1 X7.122709343036777 Y7.828103276680162 F300 -G1 X7.187133559692093 Y7.890018705357908 F300 -G1 X7.261549478985081 Y7.939478496661342 F300 -G1 X7.343580554117203 Y7.9749031026954 F300 -G1 X7.430607040673038 Y7.9951612032417785 F300 -G1 X7.500000000000001 Y7.999999999999999 F300 +G1 X7.5 Y6.999999999999999 F300 +G1 X7.411004181311704 Y7.007984 F300 +G1 X7.324850533089037 Y7.0316810229760005 F300 +G1 X7.24429045824164 Y7.070334280802239 F300 +G1 X7.171896723319836 Y7.122709343036777 F300 +G1 X7.1099812946420915 Y7.187133559692093 F300 +G1 X7.060521503338657 Y7.261549478985082 F300 +G1 X7.025096897304598 Y7.343580554117204 F300 +G1 X7.004838796758221 Y7.4306070406730385 F300 +G1 X7 Y7.500000000000001 F300 +G1 X7.007984000000001 Y7.588995818688296 F300 +G1 X7.031681022976001 Y7.6751494669109634 F300 +G1 X7.07033428080224 Y7.755709541758361 F300 +G1 X7.122709343036778 Y7.828103276680164 F300 +G1 X7.187133559692095 Y7.8900187053579085 F300 +G1 X7.261549478985083 Y7.939478496661343 F300 +G1 X7.343580554117205 Y7.974903102695402 F300 +G1 X7.430607040673039 Y7.995161203241779 F300 +G1 X7.500000000000002 Y8 F300 M2 diff --git a/lib/tests/square_viewport.gcode b/lib/tests/square_viewport.gcode index e915e88..960863a 100644 --- a/lib/tests/square_viewport.gcode +++ b/lib/tests/square_viewport.gcode @@ -7,8 +7,8 @@ G1 X1 Y1 F300 G1 X1 Y9 F300;svg#svg8 > g#layer1 > path#path832 G0 X8 Y2.5 G1 X7.992016 Y2.4110041813117045 F300 -G1 X7.9683189770239995 Y2.324850533089038 F300 -G1 X7.929665719197761 Y2.24429045824164 F300 +G1 X7.968318977024 Y2.324850533089038 F300 +G1 X7.929665719197762 Y2.24429045824164 F300 G1 X7.877290656963224 Y2.171896723319837 F300 G1 X7.812866440307907 Y2.1099812946420924 F300 G1 X7.738450521014919 Y2.060521503338657 F300 @@ -16,26 +16,26 @@ G1 X7.656419445882797 Y2.025096897304599 F300 G1 X7.569392959326962 Y2.0048387967582206 F300 G1 X7.5 Y2 F300 G1 X7.4110041813117045 Y2.007984 F300 -G1 X7.324850533089037 Y2.031681022976 F300 -G1 X7.24429045824164 Y2.0703342808022382 F300 +G1 X7.324850533089038 Y2.031681022976 F300 +G1 X7.2442904582416405 Y2.0703342808022382 F300 G1 X7.171896723319837 Y2.1227093430367767 F300 -G1 X7.1099812946420915 Y2.1871335596920924 F300 +G1 X7.109981294642092 Y2.1871335596920924 F300 G1 X7.0605215033386575 Y2.261549478985082 F300 -G1 X7.025096897304598 Y2.343580554117203 F300 +G1 X7.025096897304599 Y2.343580554117203 F300 G1 X7.004838796758221 Y2.430607040673038 F300 G1 X7 Y2.5000000000000004 F300 -G1 X7.007984 Y2.5889958186882964 F300 -G1 X7.0316810229760005 Y2.6751494669109626 F300 -G1 X7.070334280802239 Y2.7557095417583604 F300 -G1 X7.122709343036777 Y2.8281032766801633 F300 -G1 X7.187133559692093 Y2.8900187053579085 F300 -G1 X7.261549478985082 Y2.9394784966613434 F300 -G1 X7.343580554117204 Y2.9749031026954014 F300 -G1 X7.4306070406730385 Y2.9951612032417794 F300 -G1 X7.5 Y3 F300 +G1 X7.007984 Y2.588995818688296 F300 +G1 X7.0316810229760005 Y2.675149466910962 F300 +G1 X7.070334280802238 Y2.75570954175836 F300 +G1 X7.122709343036777 Y2.828103276680163 F300 +G1 X7.187133559692093 Y2.890018705357908 F300 +G1 X7.261549478985082 Y2.939478496661343 F300 +G1 X7.343580554117203 Y2.9749031026954014 F300 +G1 X7.430607040673038 Y2.9951612032417794 F300 +G1 X7.500000000000001 Y3 F300 G1 X7.5889958186882955 Y2.992016 F300 -G1 X7.675149466910963 Y2.968318977024 F300 -G1 X7.75570954175836 Y2.9296657191977618 F300 +G1 X7.675149466910962 Y2.968318977024 F300 +G1 X7.7557095417583595 Y2.9296657191977618 F300 G1 X7.828103276680163 Y2.8772906569632233 F300 G1 X7.890018705357908 Y2.8128664403079076 F300 G1 X7.9394784966613425 Y2.7384505210149186 F300 diff --git a/web/Cargo.toml b/web/Cargo.toml index 76b127a..4b14f44 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -12,7 +12,7 @@ license = "MIT" wasm-bindgen = "0.2" svg2gcode = { path = "../lib" } roxmltree = "0" -g-code = "0.3" +g-code = "0" codespan-reporting = "0.11" codespan = "0.11" serde = "1" diff --git a/web/src/inputs.rs b/web/src/inputs.rs index 6ad79ee..ef14e58 100644 --- a/web/src/inputs.rs +++ b/web/src/inputs.rs @@ -4,7 +4,7 @@ use gloo_file::futures::read_as_text; use gloo_timers::callback::Timeout; use paste::paste; use roxmltree::Document; -use std::{num::ParseFloatError}; +use std::num::ParseFloatError; use web_sys::{FileList, HtmlElement}; use yew::prelude::*; use yewdux::prelude::{BasicStore, Dispatcher}; @@ -225,22 +225,22 @@ macro_rules! gcode_input { gcode_input! { ToolOnSequence { "Tool On Sequence", - "GCode for turning on the tool", + "G-Code for turning on the tool", tool_on_sequence, } ToolOffSequence { "Tool Off Sequence", - "GCode for turning off the tool", + "G-Code for turning off the tool", tool_off_sequence, } BeginSequence { "Program Begin Sequence", - "GCode for initializing the machine at the beginning of the program", + "G-Code for initializing the machine at the beginning of the program", begin_sequence, } EndSequence { "Program End Sequence", - "GCode for stopping/idling the machine at the end of the program", + "G-Code for stopping/idling the machine at the end of the program", end_sequence, } } @@ -339,17 +339,29 @@ pub fn settings_form() -> Html { let close_ref = NodeRef::default(); + let on_circular_interpolation_change = + form.dispatch().reduce_callback_with(|form, change_data| { + if let ChangeData::Value(_) = change_data { + form.circular_interpolation = !form.circular_interpolation; + } + }); + let circular_interpolation_checked = form + .state() + .map(|state| state.circular_interpolation) + .unwrap_or(false); + let save_onclick = { let close_ref = close_ref.clone(); app.dispatch().reduce_callback(move |app| { if let (false, Some(form)) = (disabled, form.state()) { app.tolerance = *form.tolerance.as_ref().unwrap(); app.feedrate = *form.feedrate.as_ref().unwrap(); - app.dpi = *form.dpi.as_ref().unwrap(); app.origin = [ *form.origin[0].as_ref().unwrap(), *form.origin[1].as_ref().unwrap(), ]; + app.circular_interpolation = form.circular_interpolation; + app.dpi = *form.dpi.as_ref().unwrap(); app.tool_on_sequence = form.tool_on_sequence.clone().and_then(Result::ok); app.tool_off_sequence = form.tool_off_sequence.clone().and_then(Result::ok); app.begin_sequence = form.begin_sequence.clone().and_then(Result::ok); @@ -376,9 +388,16 @@ pub fn settings_form() -> Html { <> - + + + + @@ -403,6 +422,7 @@ pub fn settings_form() -> Html { title="Save" onclick={save_onclick} /> + {" "} , +} + +#[function_component(Checkbox)] +pub fn checkbox(props: &CheckboxProps) -> Html { + html! { + <> + + + } +} + #[derive(Properties, PartialEq, Clone)] pub struct FileUploadProps where @@ -443,7 +465,8 @@ css_class_enum! { Cross => "cross", Stop => "stop", Download => "download", - Edit => "edit" + Edit => "edit", + None => "" } } diff --git a/web/src/state.rs b/web/src/state.rs index afb7fce..eed8d07 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -6,8 +6,9 @@ use yewdux::prelude::{BasicStore, Persistent, PersistentStore}; pub struct FormState { pub tolerance: Result, pub feedrate: Result, - pub dpi: Result, pub origin: [Result; 2], + pub circular_interpolation: bool, + pub dpi: Result, pub tool_on_sequence: Option>, pub tool_off_sequence: Option>, pub begin_sequence: Option>, @@ -26,8 +27,9 @@ impl From<&AppState> for FormState { Self { tolerance: Ok(app_state.tolerance), feedrate: Ok(app_state.feedrate), - dpi: Ok(app_state.dpi), + circular_interpolation: app_state.circular_interpolation, origin: [Ok(app_state.origin[0]), Ok(app_state.origin[1])], + dpi: Ok(app_state.dpi), tool_on_sequence: app_state.tool_on_sequence.clone().map(Result::Ok), tool_off_sequence: app_state.tool_off_sequence.clone().map(Result::Ok), begin_sequence: app_state.begin_sequence.clone().map(Result::Ok), @@ -43,12 +45,13 @@ pub struct AppState { pub first_visit: bool, pub tolerance: f64, pub feedrate: f64, + pub origin: [f64; 2], + pub circular_interpolation: bool, pub dpi: f64, pub tool_on_sequence: Option, pub tool_off_sequence: Option, pub begin_sequence: Option, pub end_sequence: Option, - pub origin: [f64; 2], #[serde(skip)] pub svgs: Vec, } @@ -65,12 +68,13 @@ impl Default for AppState { first_visit: true, tolerance: 0.002, feedrate: 300., + origin: [0., 0.], + circular_interpolation: false, dpi: 96., tool_on_sequence: None, tool_off_sequence: None, begin_sequence: None, end_sequence: None, - origin: [0., 0.], svgs: vec![], } }