You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
487 lines
22 KiB
487 lines
22 KiB
#[macro_use]
|
|
extern crate clap;
|
|
extern crate env_logger;
|
|
extern crate svgdom;
|
|
#[macro_use]
|
|
extern crate log;
|
|
extern crate lyon_geom;
|
|
|
|
use std::env;
|
|
use std::fs::File;
|
|
use std::io::{self, Read};
|
|
|
|
use lyon_geom::math;
|
|
use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, PathSegment};
|
|
|
|
mod code;
|
|
mod machine;
|
|
|
|
use code::*;
|
|
use machine::*;
|
|
|
|
fn main() -> io::Result<()> {
|
|
if let Err(_) = env::var("RUST_LOG") {
|
|
env::set_var("RUST_LOG", "svg2gcode=info")
|
|
}
|
|
env_logger::init();
|
|
let matches = clap_app!(svg2gcode =>
|
|
(version: crate_version!())
|
|
(author: crate_authors!())
|
|
(about: crate_description!())
|
|
(@arg FILE: "Selects the input SVG file to use, else reading from stdin")
|
|
(@arg tolerance: "Sets the interpolation tolerance for curves")
|
|
(@arg feedrate: "Sets the machine feed rate")
|
|
(@arg dpi: "Sets the DPI for SVGs with units in pt, pc, etc.")
|
|
)
|
|
.get_matches();
|
|
|
|
let input = match matches.value_of("FILE") {
|
|
Some(filename) => {
|
|
let mut f = File::open(filename)?;
|
|
let len = f.metadata()?.len();
|
|
let mut input = String::with_capacity(len as usize + 1);
|
|
f.read_to_string(&mut input)?;
|
|
input
|
|
}
|
|
None => {
|
|
let mut input = String::new();
|
|
io::stdin().read_to_string(&mut input)?;
|
|
input
|
|
}
|
|
};
|
|
|
|
let mut opts = ProgramOptions::default();
|
|
let mut mach = Machine::default();
|
|
|
|
if let Some(tolerance) = matches.value_of("tolerance").and_then(|x| x.parse().ok()) {
|
|
opts.tolerance = tolerance;
|
|
}
|
|
if let Some(feedrate) = matches.value_of("feedrate").and_then(|x| x.parse().ok()) {
|
|
opts.feedrate = feedrate;
|
|
}
|
|
if let Some(dpi) = matches.value_of("dpi").and_then(|x| x.parse().ok()) {
|
|
opts.dpi = dpi;
|
|
}
|
|
|
|
if true {
|
|
mach.tool_on_action = vec![GCode::StopSpindle, GCode::Dwell { p: 1.5 }];
|
|
}
|
|
if true {
|
|
mach.tool_off_action = vec![
|
|
GCode::Dwell { p: 0.1 },
|
|
GCode::StartSpindle {
|
|
d: Direction::Clockwise,
|
|
s: 40.0,
|
|
},
|
|
GCode::Dwell { p: 0.2 },
|
|
];
|
|
}
|
|
|
|
let doc = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file");
|
|
|
|
let prog = svg2program(&doc, opts, mach);
|
|
program2gcode(&prog, File::create("out.gcode")?)
|
|
}
|
|
|
|
struct ProgramOptions {
|
|
tolerance: f64,
|
|
feedrate: f64,
|
|
dpi: f64,
|
|
}
|
|
|
|
impl Default for ProgramOptions {
|
|
fn default() -> Self {
|
|
ProgramOptions {
|
|
tolerance: 0.1,
|
|
feedrate: 3000.0,
|
|
dpi: 72.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) -> Program {
|
|
let mut current_transform = lyon_geom::euclid::Transform2D::create_scale(1.0, -1.0)
|
|
.post_translate(math::vector(0.0, 2.0));
|
|
let mut transform_stack = vec![];
|
|
|
|
let mut p = Program::new();
|
|
p.push(GCode::UnitsMillimeters);
|
|
mach.tool_off(&mut p);
|
|
p.push(GCode::RapidPositioning {
|
|
x: 0.0.into(),
|
|
y: 0.0.into(),
|
|
});
|
|
mach.tool_on(&mut p);
|
|
|
|
for edge in doc.root().traverse() {
|
|
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;
|
|
};
|
|
let attrs = node.attributes();
|
|
if let (ElementId::Svg, true) = (id, is_start) {
|
|
if let (Some(&AttributeValue::Length(width)), Some(&AttributeValue::Length(height))) = (
|
|
attrs.get_value(AttributeId::Width),
|
|
attrs.get_value(AttributeId::Height),
|
|
) {
|
|
let width_in_mm = length_to_mm(width, opts.dpi);
|
|
let height_in_mm = length_to_mm(height, opts.dpi);
|
|
current_transform =
|
|
current_transform.post_mul(&lyon_geom::euclid::Transform2D::create_scale(
|
|
width_in_mm / width.num,
|
|
height_in_mm / height.num,
|
|
));
|
|
}
|
|
}
|
|
if let (ElementId::G, true) = (id, is_start) {
|
|
p.push(GCode::Named(Box::new(node.id().to_string())));
|
|
}
|
|
if let Some(&AttributeValue::Transform(ref t)) = attrs.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 {
|
|
ElementId::Path => {
|
|
if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) {
|
|
p.push(GCode::Named(Box::new(node.id().to_string())));
|
|
let mut curpos = math::point(0.0, 0.0);
|
|
curpos = current_transform.transform_point(&curpos);
|
|
let mut prev_ctrl = curpos;
|
|
for segment in path.iter() {
|
|
match segment {
|
|
PathSegment::MoveTo { abs, x, y } => {
|
|
mach.tool_off(&mut p);
|
|
mach.distance(&mut p, *abs);
|
|
let mut to = math::point(*x, *y);
|
|
to = current_transform.transform_point(&to);
|
|
if !*abs {
|
|
to -= math::vector(
|
|
current_transform.m31,
|
|
current_transform.m32,
|
|
);
|
|
}
|
|
p.push(GCode::RapidPositioning {
|
|
x: to.x.into(),
|
|
y: to.y.into(),
|
|
});
|
|
if *abs {
|
|
curpos = to;
|
|
} else {
|
|
curpos += to.to_vector();
|
|
}
|
|
prev_ctrl = curpos;
|
|
}
|
|
PathSegment::ClosePath { abs } => {
|
|
mach.tool_off(&mut p);
|
|
}
|
|
PathSegment::LineTo { abs, x, y } => {
|
|
mach.tool_on(&mut p);
|
|
mach.distance(&mut p, *abs);
|
|
let mut to = math::point(*x, *y);
|
|
to = current_transform.transform_point(&to);
|
|
if !*abs {
|
|
to -= math::vector(
|
|
current_transform.m31,
|
|
current_transform.m32,
|
|
);
|
|
}
|
|
p.push(GCode::LinearInterpolation {
|
|
x: to.x.into(),
|
|
y: to.y.into(),
|
|
z: None,
|
|
f: opts.feedrate.into(),
|
|
});
|
|
if *abs {
|
|
curpos = to;
|
|
} else {
|
|
curpos += to.to_vector();
|
|
}
|
|
prev_ctrl = curpos;
|
|
}
|
|
PathSegment::HorizontalLineTo { abs, x } => {
|
|
mach.tool_on(&mut p);
|
|
mach.distance(&mut p, *abs);
|
|
let mut to = if *abs {
|
|
let inv_transform = current_transform
|
|
.inverse()
|
|
.expect("could not invert transform");
|
|
math::point(*x, inv_transform.transform_point(&curpos).y)
|
|
} else {
|
|
math::point(*x, 0.0)
|
|
};
|
|
to = current_transform.transform_point(&to);
|
|
if !*abs {
|
|
to -= math::vector(
|
|
current_transform.m31,
|
|
current_transform.m32,
|
|
);
|
|
}
|
|
p.push(GCode::LinearInterpolation {
|
|
x: to.x.into(),
|
|
y: to.y.into(),
|
|
z: None,
|
|
f: opts.feedrate.into(),
|
|
});
|
|
if *abs {
|
|
curpos = to;
|
|
} else {
|
|
curpos += to.to_vector();
|
|
}
|
|
prev_ctrl = curpos;
|
|
}
|
|
PathSegment::VerticalLineTo { abs, y } => {
|
|
mach.tool_on(&mut p);
|
|
mach.distance(&mut p, *abs);
|
|
let mut to = if *abs {
|
|
let inv_transform = current_transform
|
|
.inverse()
|
|
.expect("could not invert transform");
|
|
math::point(inv_transform.transform_point(&curpos).x, *y)
|
|
} else {
|
|
math::point(0.0, *y)
|
|
};
|
|
to = current_transform.transform_point(&to);
|
|
if !*abs {
|
|
to -= math::vector(
|
|
current_transform.m31,
|
|
current_transform.m32,
|
|
);
|
|
}
|
|
p.push(GCode::LinearInterpolation {
|
|
x: to.x.into(),
|
|
y: to.y.into(),
|
|
z: None,
|
|
f: opts.feedrate.into(),
|
|
});
|
|
if *abs {
|
|
curpos = to;
|
|
} else {
|
|
curpos += to.to_vector();
|
|
}
|
|
prev_ctrl = curpos;
|
|
}
|
|
PathSegment::CurveTo {
|
|
abs,
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
x,
|
|
y,
|
|
} => {
|
|
mach.tool_on(&mut p);
|
|
mach.absolute(&mut p);
|
|
let from = curpos;
|
|
let mut ctrl1 = math::point(*x1, *y1);
|
|
ctrl1 = current_transform.transform_point(&ctrl1);
|
|
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 {
|
|
from,
|
|
ctrl1,
|
|
ctrl2,
|
|
to,
|
|
};
|
|
let last_point = std::cell::Cell::new(curpos);
|
|
cbs.flattened(opts.tolerance).for_each(|point| {
|
|
p.push(GCode::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 } => {
|
|
mach.tool_on(&mut p);
|
|
mach.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 {
|
|
from,
|
|
ctrl1,
|
|
ctrl2,
|
|
to,
|
|
};
|
|
let last_point = std::cell::Cell::new(curpos);
|
|
cbs.flattened(opts.tolerance).for_each(|point| {
|
|
p.push(GCode::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::Quadratic { abs, x1, y1, x, y } => {
|
|
mach.tool_on(&mut p);
|
|
mach.absolute(&mut p);
|
|
let from = curpos;
|
|
let mut ctrl = math::point(*x1, *y1);
|
|
ctrl = current_transform.transform_point(&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(GCode::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::SmoothQuadratic { abs, x, y } => {
|
|
mach.tool_on(&mut p);
|
|
mach.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(GCode::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 {
|
|
abs,
|
|
rx,
|
|
ry,
|
|
x_axis_rotation,
|
|
large_arc,
|
|
sweep,
|
|
x,
|
|
y,
|
|
} => {
|
|
mach.tool_on(&mut p);
|
|
mach.absolute(&mut p);
|
|
let from = curpos;
|
|
let mut to = math::point(*x, *y);
|
|
to = current_transform.transform_point(&to);
|
|
if !*abs {
|
|
to += curpos.to_vector();
|
|
}
|
|
|
|
let mut radii = math::vector(*rx, *ry);
|
|
radii = current_transform.transform_vector(&radii);
|
|
|
|
let sarc = lyon_geom::SvgArc {
|
|
from,
|
|
to,
|
|
radii,
|
|
x_rotation: lyon_geom::euclid::Angle {
|
|
radians: *x_axis_rotation,
|
|
},
|
|
flags: lyon_geom::ArcFlags {
|
|
large_arc: *large_arc,
|
|
sweep: *sweep,
|
|
},
|
|
};
|
|
let last_point = std::cell::Cell::new(curpos);
|
|
sarc.for_each_flattened(
|
|
opts.tolerance,
|
|
&mut |point: math::F64Point| {
|
|
p.push(GCode::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 = curpos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
info!("Node <{} id=\"{}\" .../> is not supported", id, node.id());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mach.tool_off(&mut p);
|
|
mach.absolute(&mut p);
|
|
p.push(GCode::RapidPositioning {
|
|
x: 0.0.into(),
|
|
y: 0.0.into(),
|
|
});
|
|
mach.tool_on(&mut p);
|
|
p.push(GCode::ProgramEnd);
|
|
|
|
p
|
|
}
|
|
|
|
fn length_to_mm(l: svgdom::Length, dpi: f64) -> f64 {
|
|
use svgdom::LengthUnit::*;
|
|
let scale = match l.unit {
|
|
Cm => 0.1,
|
|
Mm => 1.0,
|
|
In => 25.4,
|
|
Pt => 25.4 / dpi,
|
|
Pc => 25.4 / (6.0 * (dpi / 72.0)),
|
|
_ => 1.0,
|
|
};
|
|
|
|
l.num * scale
|
|
}
|