Merge pull request #17 from sameer/circular-interpolation

implement circular interpolation
master
Sameer Puri 4 years ago committed by GitHub
commit 97b76ca0d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

114
Cargo.lock generated

@ -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"

@ -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

@ -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"

@ -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<String>,
#[structopt(alias = "tool_off_sequence", long = "off")]
/// Tool off GCode sequence
/// Tool off "G-Code sequence
tool_off_sequence: Option<String>,
/// 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<String>,
/// 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<String>,
/// 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,

@ -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"] }

@ -0,0 +1,428 @@
use euclid::Angle;
use lyon_geom::{
Arc, ArcFlags, CubicBezierSegment, Line, LineSegment, Point, Scalar, SvgArc, Transform, Vector,
};
pub enum ArcOrLineSegment<S> {
Arc(SvgArc<S>),
Line(LineSegment<S>),
}
fn arc_from_endpoints_and_tangents<S: Scalar>(
from: Point<S>,
from_tangent: Vector<S>,
to: Point<S>,
to_tangent: Vector<S>,
) -> Option<SvgArc<S>> {
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<S> = 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<S> {
fn flattened(&self, tolerance: S) -> Vec<ArcOrLineSegment<S>>;
}
impl<S> FlattenWithArcs<S> for CubicBezierSegment<S>
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<ArcOrLineSegment<S>> {
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<S> FlattenWithArcs<S> for SvgArc<S>
where
S: Scalar,
{
fn flattened(&self, tolerance: S) -> Vec<ArcOrLineSegment<S>> {
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<S: Scalar>(arc: Arc<S>) -> SvgArc<S> {
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<S> {
fn transformed(&self, transform: &Transform<S>) -> Self;
}
impl<S: Scalar> Transformed<S> for SvgArc<S> {
/// 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<S>) -> 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),
}
}
}
}

@ -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<Token<'input>> {
@ -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,
),

@ -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")
)
}
}

@ -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<Tool>,
distance_mode: Option<Distance>,
pub tool_on_action: Option<Snippet<'input>>,
pub tool_off_action: Option<Snippet<'input>>,
pub program_begin_sequence: Option<Snippet<'input>>,
pub program_end_sequence: Option<Snippet<'input>>,
tool_on_action: Vec<Token<'input>>,
tool_off_action: Vec<Token<'input>>,
program_begin_sequence: Vec<Token<'input>>,
program_end_sequence: Vec<Token<'input>>,
}
#[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<Snippet<'input>>,
tool_off_action: Option<Snippet<'input>>,
program_begin_sequence: Option<Snippet<'input>>,
program_end_sequence: Option<Snippet<'input>>,
) -> 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<Token<'input>> {
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<Token<'input>> {
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<Token<'input>> {
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<Token<'input>> {
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.

@ -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};

@ -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<f64>;
/// 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<f64>, f: Option<f64>) -> Vec<Token<'static>> {
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<Token<'static>> {
command!(LinearInterpolation {
X: x,
Y: y,
F: feedrate,
})
.into_token_vec()
}
fn circular_interpolation(svg_arc: SvgArc<f64>, feedrate: f64) -> Vec<Token<'input>> {
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<Z, F>(&mut self, z: Z, f: F) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
pub fn close(&mut self, feedrate: f64) -> Vec<Token<'input>> {
// 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<X, Y, Z, F>(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Vec<Token<'input>>
pub fn line<X, Y>(&mut self, abs: bool, x: X, y: Y, feedrate: f64) -> Vec<Token<'input>>
where
X: Into<Option<f64>>,
Y: Into<Option<f64>>,
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
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<Z: Into<Option<f64>>, F: Into<Option<f64>>>(
fn bezier(
&mut self,
cbs: CubicBezierSegment<f64>,
tolerance: f64,
z: Z,
f: F,
feedrate: f64,
) -> Vec<Token<'input>> {
let z = z.into();
let f = f.into();
let last_point = std::cell::Cell::new(self.current_position);
let cubic: Vec<Token> = 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::<f64>::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<Z, F>(
pub fn cubic_bezier(
&mut self,
abs: bool,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
x: f64,
y: f64,
mut ctrl1: Point<f64>,
mut ctrl2: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
z: Z,
f: F,
) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
feedrate: f64,
) -> Vec<Token<'input>> {
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<Z, F>(
pub fn smooth_cubic_bezier(
&mut self,
abs: bool,
x2: f64,
y2: f64,
x: f64,
y: f64,
mut ctrl2: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
z: Z,
f: F,
) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
feedrate: f64,
) -> Vec<Token<'input>> {
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<Z, F>(
pub fn smooth_quadratic_bezier(
&mut self,
abs: bool,
x: f64,
y: f64,
mut to: Point<f64>,
tolerance: f64,
z: Z,
f: F,
) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
feedrate: f64,
) -> Vec<Token<'input>> {
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<Z, F>(
pub fn quadratic_bezier(
&mut self,
abs: bool,
x1: f64,
y1: f64,
x: f64,
y: f64,
mut ctrl: Point<f64>,
mut to: Point<f64>,
tolerance: f64,
z: Z,
f: F,
) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
feedrate: f64,
) -> Vec<Token<'input>> {
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<Z, F>(
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<f64>,
x_rotation: Angle<f64>,
flags: ArcFlags,
mut to: Point<f64>,
feedrate: f64,
tolerance: f64,
) -> Vec<Token<'input>>
where
Z: Into<Option<f64>>,
F: Into<Option<f64>>,
{
let z = z.into();
let f = f.into();
) -> Vec<Token<'input>> {
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);

@ -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

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
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_transformed.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
id="svg8"
version="1.1"
viewBox="0 0 10 10"
height="10mm"
width="10mm">
<defs
id="defs2" />
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="0"
inkscape:window-x="0"
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="34.247607"
inkscape:cx="7.5651517"
inkscape:zoom="9.1957985"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base">
<inkscape:grid
spacingy="1"
spacingx="1"
units="mm"
id="grid836"
type="xygrid" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
d="M 1,1 C 2,2 3,4 1,4"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 1,5 Q 2,6 4,2"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 7,1 A 2,2 0 0 1 4,3"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 1,6 A 4,1 0 0 0 4,4"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 1,8 A 1,1 0 1 0 2,7"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="M 7,8 A 1.5,1 1 1 0 5,8 A 1.5,1 1 0 0 7,8"
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -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

@ -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

@ -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

@ -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"

@ -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 {
<>
<ToleranceInput/>
<FeedrateInput/>
<DpiInput/>
<OriginXInput/>
<OriginYInput/>
<FormGroup success=true>
<Checkbox
label="Enable circular interpolation"
checked={circular_interpolation_checked}
onchange={on_circular_interpolation_change}
/>
</FormGroup>
<DpiInput/>
<ToolOnSequenceInput/>
<ToolOffSequenceInput/>
<BeginSequenceInput/>
@ -403,6 +422,7 @@ pub fn settings_form() -> Html {
title="Save"
onclick={save_onclick}
/>
{" "}
<HyperlinkButton
ref={close_ref.clone()}
style={ButtonStyle::Default}

@ -6,7 +6,9 @@ use g_code::{
};
use log::Level;
use roxmltree::Document;
use svg2gcode::{set_origin, svg2program, ConversionOptions, Machine, Turtle};
use svg2gcode::{
set_origin, svg2program, ConversionOptions, Machine, SupportedFunctionality, Turtle,
};
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;
use yew::{prelude::*, utils::window};
@ -76,6 +78,9 @@ impl Component for App {
dpi: app_state.dpi,
};
let machine = Machine::new(
SupportedFunctionality {
circular_interpolation: false,
},
app_state
.tool_on_sequence
.as_deref()

@ -113,6 +113,28 @@ where
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct CheckboxProps {
pub label: &'static str,
#[prop_or(false)]
pub checked: bool,
#[prop_or_default]
pub onchange: Callback<ChangeData>,
}
#[function_component(Checkbox)]
pub fn checkbox(props: &CheckboxProps) -> Html {
html! {
<>
<label class="form-checkbox">
<input type="checkbox" onchange={props.onchange.clone()} checked={props.checked} />
<Icon form={true} name={IconName::None} />
{ props.label }
</label>
</>
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct FileUploadProps<T, E>
where
@ -443,7 +465,8 @@ css_class_enum! {
Cross => "cross",
Stop => "stop",
Download => "download",
Edit => "edit"
Edit => "edit",
None => ""
}
}

@ -6,8 +6,9 @@ use yewdux::prelude::{BasicStore, Persistent, PersistentStore};
pub struct FormState {
pub tolerance: Result<f64, ParseFloatError>,
pub feedrate: Result<f64, ParseFloatError>,
pub dpi: Result<f64, ParseFloatError>,
pub origin: [Result<f64, ParseFloatError>; 2],
pub circular_interpolation: bool,
pub dpi: Result<f64, ParseFloatError>,
pub tool_on_sequence: Option<Result<String, String>>,
pub tool_off_sequence: Option<Result<String, String>>,
pub begin_sequence: Option<Result<String, String>>,
@ -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<String>,
pub tool_off_sequence: Option<String>,
pub begin_sequence: Option<String>,
pub end_sequence: Option<String>,
pub origin: [f64; 2],
#[serde(skip)]
pub svgs: Vec<Svg>,
}
@ -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![],
}
}

Loading…
Cancel
Save