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

#[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
}