Mild code cleanup, handle smooth curve variants, handle SVG transforms, name SVG groups, don't write names if empty

master
Sameer Puri 6 years ago
parent ef46ab8e1c
commit b849ede559

@ -3,6 +3,10 @@
Convert any SVG 1.1 path to gcode for a pen plotter, laser engraver, etc. Convert any SVG 1.1 path to gcode for a pen plotter, laser engraver, etc.
## TODO ## TODO
- [x] Support all path variants
- [ ] SVG transforms - [x] Support transforms
- [ ] Biarc interpolation (G2/G3 instead of many G1)
- [ ] Units - [ ] Units
## Known bugs & whether fixed
- [ ] Smooth curves should not use the control point when the previous curve is not of the same type (quadratic -> smooth cubic, cubic -> smooth quadratic)

@ -40,42 +40,64 @@ fn main() -> io::Result<()> {
} }
}; };
let tolerance = matches let mut opts = MachineOptions::default();
.value_of("tolerance")
.and_then(|x| x.parse().ok()) if let Some(tolerance) = matches.value_of("tolerance").and_then(|x| x.parse().ok()) {
.unwrap_or(0.1); opts.tolerance = tolerance;
let feedrate = matches }
.value_of("feedrate") if let Some(feedrate) = matches.value_of("feedrate").and_then(|x| x.parse().ok()) {
.and_then(|x| x.parse().ok()) opts.feedrate = feedrate;
.unwrap_or(3000.0); }
let doc = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file"); let doc = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file");
let tool_on_action = vec![MachineCode::StopSpindle, MachineCode::Dwell { p: 1.5 }]; let prog = svg2program(&doc, opts);
let tool_off_action = vec![ program2gcode(&prog, File::create("out.gcode")?)
MachineCode::Dwell { p: 0.1 }, }
MachineCode::StartSpindle {
d: Direction::Clockwise, struct MachineOptions {
s: 40.0, tolerance: f64,
}, feedrate: f64,
MachineCode::Dwell { p: 0.2 }, tool_on_action: Vec<MachineCode>,
]; tool_off_action: Vec<MachineCode>,
}
impl Default for MachineOptions {
fn default() -> Self {
Self {
tolerance: 0.1,
feedrate: 3000.0,
tool_on_action: vec![MachineCode::StopSpindle, MachineCode::Dwell { p: 1.5 }],
tool_off_action: vec![
MachineCode::Dwell { p: 0.1 },
MachineCode::StartSpindle {
d: Direction::Clockwise,
s: 40.0,
},
MachineCode::Dwell { p: 0.2 },
],
}
}
}
let tool = std::cell::Cell::new(Tool::Off); fn svg2program(doc: &svgdom::Document, opts: MachineOptions) -> Program {
let tool = std::cell::Cell::new(Tool::On);
let tool_on = |p: &mut Program| { let tool_on = |p: &mut Program| {
if tool.get() == Tool::Off { if tool.get() == Tool::Off {
tool_on_action.iter().for_each(|x| { opts.tool_on_action
p.push(x.clone()); .iter()
}); .map(Clone::clone)
.for_each(|x| p.push(x));
tool.set(Tool::On); tool.set(Tool::On);
} }
}; };
let tool_off = |p: &mut Program| { let tool_off = |p: &mut Program| {
if tool.get() == Tool::On { if tool.get() == Tool::On {
tool_off_action.iter().for_each(|x| { opts.tool_off_action
p.push(x.clone()); .iter()
}); .map(Clone::clone)
.for_each(|x| p.push(x));
tool.set(Tool::Off); tool.set(Tool::Off);
} }
}; };
@ -93,6 +115,16 @@ fn main() -> io::Result<()> {
is_absolute.set(true); is_absolute.set(true);
} }
}; };
let select_mode = |p: &mut Program, abs: bool| {
if abs {
absolute(p);
} else {
incremental(p);
}
};
let mut current_transform = lyon_geom::euclid::Transform2D::default();
let mut transform_stack = vec![];
let mut p = Program::new(); let mut p = Program::new();
p.push(MachineCode::UnitsMillimeters); p.push(MachineCode::UnitsMillimeters);
@ -103,97 +135,114 @@ fn main() -> io::Result<()> {
}); });
tool_on(&mut p); tool_on(&mut p);
for (id, node) in doc.root().descendants().svg() { for edge in doc.root().traverse() {
if node.is_graphic() { let (node, is_start) = match edge {
svgdom::NodeEdge::Start(node) => (node, true),
svgdom::NodeEdge::End(node) => (node, false),
};
let id = if let svgdom::QName::Id(id) = *node.tag_name() {
id
} else {
continue;
};
if let (ElementId::G, true) = (id, is_start) {
p.push(MachineCode::Named(Box::new(node.id().to_string())));
}
if let Some(&AttributeValue::Transform(ref t)) =
node.attributes().get_value(AttributeId::Transform)
{
if is_start {
transform_stack.push(current_transform);
current_transform = current_transform.post_mul(&lyon_geom::euclid::Transform2D::row_major(t.a, t.b, t.c, t.d, t.e, t.f));
} else {
current_transform = transform_stack.pop().unwrap();
}
}
if node.is_graphic() && is_start {
match id { match id {
ElementId::Path => { ElementId::Path => {
let attrs = node.attributes(); let attrs = node.attributes();
if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) { if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) {
p.push(MachineCode::Named(Box::new(node.id().to_string()))); p.push(MachineCode::Named(Box::new(node.id().to_string())));
let mut cx = 0.0; let mut curpos = math::point(0.0, 0.0);
let mut cy = 0.0; curpos = current_transform.transform_point(&curpos);
let mut prev_ctrl = curpos;
for segment in path.iter() { for segment in path.iter() {
match segment { match segment {
PathSegment::MoveTo { abs, x, y } => { PathSegment::MoveTo { abs, x, y } => {
tool_off(&mut p); tool_off(&mut p);
if *abs { select_mode(&mut p, *abs);
absolute(&mut p); let mut to = math::point(*x, *y);
} else { to = current_transform.transform_point(&to);
incremental(&mut p);
}
p.push(MachineCode::RapidPositioning { p.push(MachineCode::RapidPositioning {
x: (*x).into(), x: to.x.into(),
y: (*y).into(), y: to.y.into(),
}); });
if *abs { if *abs {
cx = *x; curpos = to;
cy = *y;
} else { } else {
cx += *x; curpos += to.to_vector();
cy += *y;
} }
prev_ctrl = curpos;
} }
PathSegment::ClosePath { abs } => { PathSegment::ClosePath { abs } => {
tool_off(&mut p); tool_off(&mut p);
} }
PathSegment::LineTo { abs, x, y } => { PathSegment::LineTo { abs, x, y } => {
tool_on(&mut p); tool_on(&mut p);
if *abs { select_mode(&mut p, *abs);
absolute(&mut p); let mut to = math::point(*x, *y);
} else { to = current_transform.transform_point(&to);
incremental(&mut p);
}
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: (*x).into(), x: to.x.into(),
y: (*y).into(), y: to.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
if *abs { if *abs {
cx = *x; curpos = to;
cy = *y;
} else { } else {
cx += *x; curpos += to.to_vector();
cy += *y;
} }
prev_ctrl = curpos;
} }
PathSegment::HorizontalLineTo { abs, x } => { PathSegment::HorizontalLineTo { abs, x } => {
tool_on(&mut p); tool_on(&mut p);
if *abs { select_mode(&mut p, *abs);
absolute(&mut p); let inv_transform = current_transform.inverse().expect("could not invert transform");
} else { let mut to = math::point(*x, inv_transform.transform_point(&curpos).y);
incremental(&mut p); to = current_transform.transform_point(&to);
}
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: (*x).into(), x: to.x.into(),
y: None, y: to.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
if *abs { if *abs {
cx = *x; curpos = to;
} else { } else {
cx += *x; curpos += to.to_vector();
} }
prev_ctrl = curpos;
} }
PathSegment::VerticalLineTo { abs, y } => { PathSegment::VerticalLineTo { abs, y } => {
tool_on(&mut p); tool_on(&mut p);
if *abs { select_mode(&mut p, *abs);
absolute(&mut p); let inv_transform = current_transform.inverse().expect("could not invert transform");
} else { let mut to = math::point(inv_transform.transform_point(&curpos).x, *y);
incremental(&mut p); to = current_transform.transform_point(&to);
}
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: None, x: to.x.into(),
y: (*y).into(), y: to.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
if *abs { if *abs {
cy = *y; curpos = to;
} else { } else {
cy += *y; curpos += to.to_vector();
} }
prev_ctrl = curpos;
} }
PathSegment::CurveTo { PathSegment::CurveTo {
abs, abs,
@ -204,71 +253,122 @@ fn main() -> io::Result<()> {
x, x,
y, y,
} => { } => {
println!("Curve {:?} starting at ({}, {})", segment, cx, cy);
tool_on(&mut p); tool_on(&mut p);
absolute(&mut p); absolute(&mut p);
let from = math::point(cx, cy); let from = curpos;
let ctrl1 = if *abs { let mut ctrl1 = math::point(*x1, *y1);
math::point(*x1, *y1) ctrl1 = current_transform.transform_point(&ctrl1);
} else { let mut ctrl2 = math::point(*x2, *y2);
math::point(cx + *x1, cy + *y1) ctrl2 = current_transform.transform_point(&ctrl2);
}; let mut to = math::point(*x, *y);
let ctrl2 = if *abs { to = current_transform.transform_point(&to);
math::point(*x2, *y2) if !*abs {
} else { ctrl1 += curpos.to_vector();
math::point(cx + *x2, cy + *y2) ctrl2 += curpos.to_vector();
}; to += curpos.to_vector();
let to = if *abs { }
math::point(*x, *y) let cbs = lyon_geom::CubicBezierSegment {
} else { from,
math::point(cx + *x, cy + *y) ctrl1,
ctrl2,
to,
}; };
let last_point = std::cell::Cell::new(curpos);
cbs.flattened(opts.tolerance).for_each(|point| {
p.push(MachineCode::LinearInterpolation {
x: point.x.into(),
y: point.y.into(),
z: None,
f: opts.feedrate.into(),
});
last_point.set(point);
});
curpos = last_point.get();
prev_ctrl = ctrl1;
}
PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => {
tool_on(&mut p);
absolute(&mut p);
let from = curpos;
let mut ctrl1 = prev_ctrl;
let mut ctrl2 = math::point(*x2, *y2);
ctrl2 = current_transform.transform_point(&ctrl2);
let mut to = math::point(*x, *y);
to = current_transform.transform_point(&to);
if !*abs {
ctrl1 += curpos.to_vector();
ctrl2 += curpos.to_vector();
to += curpos.to_vector();
}
let cbs = lyon_geom::CubicBezierSegment { let cbs = lyon_geom::CubicBezierSegment {
from, from,
ctrl1, ctrl1,
ctrl2, ctrl2,
to, to,
}; };
let last_point = std::cell::Cell::new(math::point(cx, cy)); let last_point = std::cell::Cell::new(curpos);
cbs.flattened(tolerance).for_each(|point| { cbs.flattened(opts.tolerance).for_each(|point| {
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: point.x.into(), x: point.x.into(),
y: point.y.into(), y: point.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
last_point.set(point); last_point.set(point);
}); });
cx = last_point.get().x; curpos = last_point.get();
cy = last_point.get().y; prev_ctrl = ctrl1;
} }
PathSegment::Quadratic { abs, x1, y1, x, y } => { PathSegment::Quadratic { abs, x1, y1, x, y } => {
tool_on(&mut p); tool_on(&mut p);
absolute(&mut p); absolute(&mut p);
let from = math::point(cx, cy); let from = curpos;
let ctrl = if *abs { let mut ctrl = math::point(*x1, *y1);
math::point(*x1, *y1) ctrl = current_transform.transform_point(&ctrl);
} else { let mut to = math::point(*x, *y);
math::point(cx + *x1, cy + *y1) to = current_transform.transform_point(&to);
}; if !*abs {
let to = if *abs { ctrl += curpos.to_vector();
math::point(*x, *y) to += curpos.to_vector();
} else { }
math::point(cx + *x, cy + *y)
};
let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to }; let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to };
let last_point = std::cell::Cell::new(math::point(cx, cy)); let last_point = std::cell::Cell::new(curpos);
qbs.flattened(tolerance).for_each(|point| { qbs.flattened(opts.tolerance).for_each(|point| {
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: point.x.into(), x: point.x.into(),
y: point.y.into(), y: point.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
last_point.set(point); last_point.set(point);
}); });
cx = last_point.get().x; curpos = last_point.get();
cy = last_point.get().y; prev_ctrl = ctrl;
}
PathSegment::SmoothQuadratic { abs, x, y } => {
tool_on(&mut p);
absolute(&mut p);
let from = curpos;
let mut ctrl = prev_ctrl;
let mut to = math::point(*x, *y);
to = current_transform.transform_point(&to);
if !*abs {
ctrl += curpos.to_vector();
to += curpos.to_vector();
}
let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to };
let last_point = std::cell::Cell::new(curpos);
qbs.flattened(opts.tolerance).for_each(|point| {
p.push(MachineCode::LinearInterpolation {
x: point.x.into(),
y: point.y.into(),
z: None,
f: opts.feedrate.into(),
});
last_point.set(point);
});
curpos = last_point.get();
prev_ctrl = ctrl;
} }
PathSegment::EllipticalArc { PathSegment::EllipticalArc {
abs, abs,
@ -282,16 +382,20 @@ fn main() -> io::Result<()> {
} => { } => {
tool_on(&mut p); tool_on(&mut p);
absolute(&mut p); absolute(&mut p);
let from = math::point(cx, cy); let from = curpos;
let to = if *abs { let mut to = math::point(*x, *y);
math::point(*x, *y) to = current_transform.transform_point(&to);
} else { if !*abs {
math::point(cx + *x, cy + *y) to += curpos.to_vector();
}; }
let mut radii = math::vector(*rx, *ry);
radii = current_transform.transform_vector(&radii);
let sarc = lyon_geom::SvgArc { let sarc = lyon_geom::SvgArc {
from, from,
to, to,
radii: math::vector(*rx, *ry), radii,
x_rotation: lyon_geom::euclid::Angle { x_rotation: lyon_geom::euclid::Angle {
radians: *x_axis_rotation, radians: *x_axis_rotation,
}, },
@ -300,30 +404,28 @@ fn main() -> io::Result<()> {
sweep: *sweep, sweep: *sweep,
}, },
}; };
let last_point = std::cell::Cell::new(math::point(cx, cy)); let last_point = std::cell::Cell::new(curpos);
sarc.for_each_flattened( sarc.for_each_flattened(
tolerance, opts.tolerance,
&mut |point: math::F64Point| { &mut |point: math::F64Point| {
p.push(MachineCode::LinearInterpolation { p.push(MachineCode::LinearInterpolation {
x: point.x.into(), x: point.x.into(),
y: point.y.into(), y: point.y.into(),
z: None, z: None,
f: feedrate.into(), f: opts.feedrate.into(),
}); });
last_point.set(point); last_point.set(point);
}, },
); );
cx = last_point.get().x; curpos = last_point.get();
cy = last_point.get().y; prev_ctrl = curpos;
} }
_ => panic!("Unsupported path segment type"),
} }
} }
} }
} }
_ => { _ => {
info!("Other {}", node); info!("{} node with id {} is unsupported", id, node.id());
} }
} }
} }
@ -337,7 +439,7 @@ fn main() -> io::Result<()> {
tool_on(&mut p); tool_on(&mut p);
p.push(MachineCode::ProgramEnd); p.push(MachineCode::ProgramEnd);
program2gcode(p, File::create("out.gcode")?) p
} }
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
@ -392,7 +494,7 @@ enum MachineCode {
type Program = Vec<MachineCode>; type Program = Vec<MachineCode>;
fn program2gcode<W: Write>(p: Program, mut w: W) -> io::Result<()> { fn program2gcode<W: Write>(p: &Program, mut w: W) -> io::Result<()> {
use MachineCode::*; use MachineCode::*;
for code in p.iter() { for code in p.iter() {
match code { match code {
@ -445,7 +547,9 @@ fn program2gcode<W: Write>(p: Program, mut w: W) -> io::Result<()> {
writeln!(w, "G91")?; writeln!(w, "G91")?;
} }
Named(name) => { Named(name) => {
writeln!(w, "({})", name)?; if name.len() > 0 {
writeln!(w, "({})", name)?;
}
} }
} }
} }

Loading…
Cancel
Save