cleaning up shop: move large method out of main

master
Sameer Puri 5 years ago
parent 0e46c40f65
commit 90b83459e3

@ -0,0 +1,254 @@
use lyon_geom::{euclid, math};
use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, PathSegment};
#[macro_use]
use crate::*;
use crate::gcode::*;
use crate::machine::*;
use crate::turtle::*;
/// High-level output options
#[derive(Debug)]
pub struct ProgramOptions {
/// Curve interpolation tolerance in millimeters
pub tolerance: f64,
/// Feedrate in millimeters / minute
pub feedrate: f64,
/// Dots per inch for pixels, picas, points, etc.
pub dpi: f64,
pub origin: (f64, f64),
}
pub fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machine) -> Vec<Command> {
let mut turtle = Turtle::new(mach);
let mut program = vec![
command!(CommandWord::UnitsMillimeters, {}),
command!(CommandWord::FeedRateUnitsPerMinute, {}),
];
program.append(&mut turtle.machine.program_begin());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
let mut name_stack: Vec<String> = vec![];
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 attributes = node.attributes();
if let (ElementId::Svg, true) = (id, is_start) {
if let Some(&AttributeValue::ViewBox(view_box)) =
attributes.get_value(AttributeId::ViewBox)
{
turtle.stack_scaling(
euclid::Transform2D::create_scale(1. / view_box.w, 1. / view_box.h)
.post_translate(math::vector(view_box.x, view_box.y)),
);
}
if let (Some(&AttributeValue::Length(width)), Some(&AttributeValue::Length(height))) = (
attributes.get_value(AttributeId::Width),
attributes.get_value(AttributeId::Height),
) {
let width_in_mm = length_to_mm(width, options.dpi);
let height_in_mm = length_to_mm(height, options.dpi);
turtle.stack_scaling(
euclid::Transform2D::create_scale(width_in_mm, -height_in_mm)
.post_translate(math::vector(0.0, height_in_mm)),
);
}
}
// Display named elements in GCode comments
if let ElementId::G = id {
if is_start {
name_stack.push(format!("{}#{}", node.tag_name(), node.id().to_string()));
} else {
name_stack.pop();
}
}
if let Some(&AttributeValue::Transform(ref transform)) =
attributes.get_value(AttributeId::Transform)
{
if is_start {
turtle.push_transform(lyon_geom::euclid::Transform2D::row_major(
transform.a,
transform.b,
transform.c,
transform.d,
transform.e,
transform.f,
));
} else {
turtle.pop_transform();
}
}
let is_clip_path = node.ancestors().any(|ancestor| {
if let svgdom::QName::Id(ancestor_id) = *ancestor.tag_name() {
ancestor_id == ElementId::ClipPath
} else {
false
}
});
if node.is_graphic() && is_start && !is_clip_path {
match id {
ElementId::Path => {
if let Some(&AttributeValue::Path(ref path)) =
attributes.get_value(AttributeId::D)
{
let prefix: String =
name_stack.iter().fold(String::new(), |mut acc, name| {
acc += name;
acc += " => ";
acc
});
program.push(command!(
CommandWord::Comment(Box::new(prefix + &node.id())),
{}
));
turtle.reset();
for segment in path.iter() {
program.append(&mut match segment {
PathSegment::MoveTo { abs, x, y } => turtle.move_to(*abs, *x, *y),
PathSegment::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)
}
PathSegment::LineTo { abs, x, y } => {
turtle.line(*abs, *x, *y, None, options.feedrate)
}
PathSegment::HorizontalLineTo { abs, x } => {
turtle.line(*abs, *x, None, None, options.feedrate)
}
PathSegment::VerticalLineTo { abs, y } => {
turtle.line(*abs, None, *y, None, options.feedrate)
}
PathSegment::CurveTo {
abs,
x1,
y1,
x2,
y2,
x,
y,
} => turtle.cubic_bezier(
*abs,
*x1,
*y1,
*x2,
*y2,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => turtle
.smooth_cubic_bezier(
*abs,
*x2,
*y2,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::Quadratic { abs, x1, y1, x, y } => turtle
.quadratic_bezier(
*abs,
*x1,
*y1,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothQuadratic { abs, x, y } => turtle
.smooth_quadratic_bezier(
*abs,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::EllipticalArc {
abs,
rx,
ry,
x_axis_rotation,
large_arc,
sweep,
x,
y,
} => turtle.elliptical(
*abs,
*rx,
*ry,
*x_axis_rotation,
*large_arc,
*sweep,
*x,
*y,
None,
options.feedrate,
options.tolerance,
),
});
}
}
}
_ => {
warn!("Node <{} id=\"{}\" .../> is not supported", id, node.id());
}
}
}
}
program.append(&mut turtle.machine.tool_off());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
program.append(&mut turtle.machine.program_end());
program.push(command!(CommandWord::ProgramEnd, {}));
program
}
/// Convenience function for converting absolute lengths to millimeters
/// Absolute lengths are listed in [CSS 4 §6.2](https://www.w3.org/TR/css-values/#absolute-lengths)
/// Relative lengths in [CSS 4 §6.1](https://www.w3.org/TR/css-values/#relative-lengths) are not supported and will cause a panic.
/// A default DPI of 96 is used as per [CSS 4 §7.4](https://www.w3.org/TR/css-values/#resolution), which you can adjust with --dpi
fn length_to_mm(l: svgdom::Length, dpi: f64) -> f64 {
use svgdom::LengthUnit::*;
use uom::si::f64::Length;
use uom::si::length::*;
let length = match l.unit {
Cm => Length::new::<centimeter>(l.num),
Mm => Length::new::<millimeter>(l.num),
In => Length::new::<inch>(l.num),
Pc => Length::new::<pica_computer>(l.num) * dpi / 96.0,
Pt => Length::new::<point_computer>(l.num) * dpi / 96.0,
Px => Length::new::<inch>(l.num * dpi / 96.0),
other => {
warn!(
"Converting from '{:?}' to millimeters is not supported, treating as millimeters",
other
);
Length::new::<millimeter>(l.num)
}
};
length.get::<millimeter>()
}

@ -14,24 +14,16 @@ pub struct Word {
pub enum Value { pub enum Value {
Fractional(u32, Option<u32>), Fractional(u32, Option<u32>),
Float(f64), Float(f64),
String(Box<String>) String(Box<String>),
} }
impl std::fmt::Display for Value { impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Fractional(number, Some(fraction)) => { Self::Fractional(number, Some(fraction)) => write!(f, "{}.{}", number, fraction),
write!(f, "{}.{}", number, fraction) Self::Fractional(number, None) => write!(f, "{}", number),
}, Self::Float(float) => write!(f, "{}", float),
Self::Fractional(number, None) => { Self::String(string) => write!(f, "{}", string),
write!(f, "{}", number)
},
Self::Float(float) => {
write!(f, "{}", float)
},
Self::String(string) => {
write!(f, "{}", string)
}
} }
} }
} }

@ -1,4 +1,3 @@
#[macro_use]
use crate::gcode::*; use crate::gcode::*;
//// Direction of the machine spindle //// Direction of the machine spindle
@ -73,8 +72,12 @@ impl Machine {
} }
} }
pub fn program_begin(&self) -> Vec<Command> { self.program_begin_sequence.clone() } pub fn program_begin(&self) -> Vec<Command> {
pub fn program_end(&self) -> Vec<Command> { self.program_end_sequence.clone() } self.program_begin_sequence.clone()
}
pub fn program_end(&self) -> Vec<Command> {
self.program_end_sequence.clone()
}
/// Output relative distance field if mode was absolute or unknown. /// Output relative distance field if mode was absolute or unknown.
pub fn absolute(&mut self) -> Vec<Command> { pub fn absolute(&mut self) -> Vec<Command> {

@ -12,18 +12,17 @@ use std::env;
use std::fs::File; use std::fs::File;
use std::io::{self, Read}; use std::io::{self, Read};
use lyon_geom::{euclid, math}; /// Converts an SVG to GCode in an internal representation
use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, PathSegment}; mod converter;
/// Defines an internal GCode representation
#[macro_use] #[macro_use]
mod gcode; mod gcode;
/// Emulates the state of an arbitrary machine that can run GCode
mod machine; mod machine;
/// Provides an interface for drawing lines in GCode
/// This concept is referred to as [Turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics).
mod turtle; mod turtle;
use gcode::*;
use machine::*;
use turtle::*;
fn main() -> io::Result<()> { fn main() -> io::Result<()> {
if let Err(_) = env::var("RUST_LOG") { if let Err(_) = env::var("RUST_LOG") {
env::set_var("RUST_LOG", "svg2gcode=info") env::set_var("RUST_LOG", "svg2gcode=info")
@ -62,7 +61,7 @@ fn main() -> io::Result<()> {
} }
}; };
let options = ProgramOptions { let options = converter::ProgramOptions {
tolerance: matches tolerance: matches
.value_of("tolerance") .value_of("tolerance")
.map(|x| x.parse().expect("could not parse tolerance")) .map(|x| x.parse().expect("could not parse tolerance"))
@ -81,260 +80,34 @@ fn main() -> io::Result<()> {
.map(|coords| coords.map(|point| point.parse().expect("could not parse coordinate"))) .map(|coords| coords.map(|point| point.parse().expect("could not parse coordinate")))
.map(|coords| coords.collect::<Vec<f64>>()) .map(|coords| coords.collect::<Vec<f64>>())
.map(|coords| (coords[0], coords[1])) .map(|coords| (coords[0], coords[1]))
.unwrap_or((0.,0.)) .unwrap_or((0., 0.)),
}; };
let machine = Machine::new( let machine = machine::Machine::new(
matches.value_of("tool_on_sequence").map(parse_gcode).unwrap_or_default(), matches
matches.value_of("tool_off_sequence").map(parse_gcode).unwrap_or_default(), .value_of("tool_on_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
matches
.value_of("tool_off_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(),
matches matches
.value_of("begin_sequence") .value_of("begin_sequence")
.map(parse_gcode) .map(gcode::parse_gcode)
.unwrap_or_default(),
matches
.value_of("end_sequence")
.map(gcode::parse_gcode)
.unwrap_or_default(), .unwrap_or_default(),
matches.value_of("end_sequence").map(parse_gcode).unwrap_or_default(),
); );
let document = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file"); let document = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file");
let program = svg2program(&document, options, machine); let program = converter::svg2program(&document, options, machine);
if let Some(out_path) = matches.value_of("out") { if let Some(out_path) = matches.value_of("out") {
program2gcode(program, File::create(out_path)?) gcode::program2gcode(program, File::create(out_path)?)
} else { } else {
program2gcode(program, std::io::stdout()) gcode::program2gcode(program, std::io::stdout())
}
}
/// High-level output options
#[derive(Debug)]
struct ProgramOptions {
/// Curve interpolation tolerance in millimeters
tolerance: f64,
/// Feedrate in millimeters / minute
feedrate: f64,
/// Dots per inch for pixels, picas, points, etc.
dpi: f64,
origin: (f64, f64)
}
fn svg2program(doc: &svgdom::Document, options: ProgramOptions, mach: Machine) -> Vec<Command> {
let mut turtle = Turtle::new(mach);
let mut program = vec![
command!(CommandWord::UnitsMillimeters, {}),
command!(CommandWord::FeedRateUnitsPerMinute, {}),
];
program.append(&mut turtle.machine.program_begin());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
let mut name_stack: Vec<String> = vec![];
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 attributes = node.attributes();
if let (ElementId::Svg, true) = (id, is_start) {
if let Some(&AttributeValue::ViewBox(view_box)) = attributes.get_value(AttributeId::ViewBox) {
turtle.stack_scaling(
euclid::Transform2D::create_scale(1. / view_box.w, 1. / view_box.h)
.post_translate(math::vector(view_box.x, view_box.y)),
);
}
if let (Some(&AttributeValue::Length(width)), Some(&AttributeValue::Length(height))) = (
attributes.get_value(AttributeId::Width),
attributes.get_value(AttributeId::Height),
) {
let width_in_mm = length_to_mm(width, options.dpi);
let height_in_mm = length_to_mm(height, options.dpi);
turtle.stack_scaling(
euclid::Transform2D::create_scale(width_in_mm, -height_in_mm)
.post_translate(math::vector(0.0, height_in_mm)),
);
}
}
// Display named elements in GCode comments
if let ElementId::G = id {
if is_start {
name_stack.push(format!("{}#{}", node.tag_name(), node.id().to_string()));
} else {
name_stack.pop();
}
}
if let Some(&AttributeValue::Transform(ref transform)) = attributes.get_value(AttributeId::Transform)
{
if is_start {
turtle.push_transform(lyon_geom::euclid::Transform2D::row_major(
transform.a, transform.b, transform.c, transform.d, transform.e, transform.f,
));
} else {
turtle.pop_transform();
}
}
let is_clip_path = node.ancestors().any(|ancestor| {
if let svgdom::QName::Id(ancestor_id) = *ancestor.tag_name() {
ancestor_id == ElementId::ClipPath
} else {
false
}
});
if node.is_graphic() && is_start && !is_clip_path {
match id {
ElementId::Path => {
if let Some(&AttributeValue::Path(ref path)) = attributes.get_value(AttributeId::D) {
let prefix: String =
name_stack.iter().fold(String::new(), |mut acc, name| {
acc += name;
acc += " => ";
acc
});
program.push(command!(
CommandWord::Comment(Box::new(prefix + &node.id())),
{}
));
turtle.reset();
for segment in path.iter() {
program.append(&mut match segment {
PathSegment::MoveTo { abs, x, y } => turtle.move_to(*abs, *x, *y),
PathSegment::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)
}
PathSegment::LineTo { abs, x, y } => {
turtle.line(*abs, *x, *y, None, options.feedrate)
}
PathSegment::HorizontalLineTo { abs, x } => {
turtle.line(*abs, *x, None, None, options.feedrate)
}
PathSegment::VerticalLineTo { abs, y } => {
turtle.line(*abs, None, *y, None, options.feedrate)
}
PathSegment::CurveTo {
abs,
x1,
y1,
x2,
y2,
x,
y,
} => turtle.cubic_bezier(
*abs,
*x1,
*y1,
*x2,
*y2,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => turtle
.smooth_cubic_bezier(
*abs,
*x2,
*y2,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::Quadratic { abs, x1, y1, x, y } => turtle.quadratic_bezier(
*abs,
*x1,
*y1,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::SmoothQuadratic { abs, x, y } => turtle
.smooth_quadratic_bezier(
*abs,
*x,
*y,
options.tolerance,
None,
options.feedrate,
),
PathSegment::EllipticalArc {
abs,
rx,
ry,
x_axis_rotation,
large_arc,
sweep,
x,
y,
} => turtle.elliptical(
*abs,
*rx,
*ry,
*x_axis_rotation,
*large_arc,
*sweep,
*x,
*y,
None,
options.feedrate,
options.tolerance,
),
});
}
}
}
_ => {
warn!("Node <{} id=\"{}\" .../> is not supported", id, node.id());
}
}
}
} }
program.append(&mut turtle.machine.tool_off());
program.append(&mut turtle.machine.absolute());
program.append(&mut turtle.move_to(true, 0.0, 0.0));
program.append(&mut turtle.machine.program_end());
program.push(command!(CommandWord::ProgramEnd, {}));
program
}
/// Convenience function for converting absolute lengths to millimeters
/// Absolute lengths are listed in [CSS 4 §6.2](https://www.w3.org/TR/css-values/#absolute-lengths)
/// Relative lengths in [CSS 4 §6.1](https://www.w3.org/TR/css-values/#relative-lengths) are not supported and will cause a panic.
/// A default DPI of 96 is used as per [CSS 4 §7.4](https://www.w3.org/TR/css-values/#resolution), which you can adjust with --dpi
fn length_to_mm(l: svgdom::Length, dpi: f64) -> f64 {
use svgdom::LengthUnit::*;
use uom::si::f64::Length;
use uom::si::length::*;
let length = match l.unit {
Cm => Length::new::<centimeter>(l.num),
Mm => Length::new::<millimeter>(l.num),
In => Length::new::<inch>(l.num),
Pc => Length::new::<pica_computer>(l.num) * dpi / 96.0,
Pt => Length::new::<point_computer>(l.num) * dpi / 96.0,
Px => Length::new::<inch>(l.num * dpi / 96.0),
other => {
warn!(
"Converting from '{:?}' to millimeters is not supported, treating as millimeters",
other
);
Length::new::<millimeter>(l.num)
}
};
length.get::<millimeter>()
} }

@ -44,11 +44,23 @@ impl Turtle {
let original_current_position = inverse_transform.transform_point(self.current_position); let original_current_position = inverse_transform.transform_point(self.current_position);
let x = x let x = x
.into() .into()
.map(|x| if abs { x } else { original_current_position.x + x }) .map(|x| {
if abs {
x
} else {
original_current_position.x + x
}
})
.unwrap_or(original_current_position.x); .unwrap_or(original_current_position.x);
let y = y let y = y
.into() .into()
.map(|y| if abs { y } else { original_current_position.y + y }) .map(|y| {
if abs {
y
} else {
original_current_position.y + y
}
})
.unwrap_or(original_current_position.y); .unwrap_or(original_current_position.y);
let mut to = point(x, y); let mut to = point(x, y);
@ -134,11 +146,23 @@ impl Turtle {
let original_current_position = inverse_transform.transform_point(self.current_position); let original_current_position = inverse_transform.transform_point(self.current_position);
let x = x let x = x
.into() .into()
.map(|x| if abs { x } else { original_current_position.x + x }) .map(|x| {
if abs {
x
} else {
original_current_position.x + x
}
})
.unwrap_or(original_current_position.x); .unwrap_or(original_current_position.x);
let y = y let y = y
.into() .into()
.map(|y| if abs { y } else { original_current_position.y + y }) .map(|y| {
if abs {
y
} else {
original_current_position.y + y
}
})
.unwrap_or(original_current_position.y); .unwrap_or(original_current_position.y);
let mut to = point(x, y); let mut to = point(x, y);
@ -177,12 +201,7 @@ impl Turtle {
.flattened(tolerance) .flattened(tolerance)
.map(|point| { .map(|point| {
last_point.set(point); last_point.set(point);
Self::linear_interpolation( Self::linear_interpolation(point.x.into(), point.y.into(), z.into(), f.into())
point.x.into(),
point.y.into(),
z.into(),
f.into(),
)
}) })
.collect(); .collect();
self.current_position = last_point.get(); self.current_position = last_point.get();
@ -227,7 +246,8 @@ impl Turtle {
let mut to = point(x, y); let mut to = point(x, y);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); 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(self.current_position);
ctrl1 += original_current_position.to_vector(); ctrl1 += original_current_position.to_vector();
ctrl2 += original_current_position.to_vector(); ctrl2 += original_current_position.to_vector();
to += original_current_position.to_vector(); to += original_current_position.to_vector();
@ -268,7 +288,8 @@ impl Turtle {
let mut to = point(x, y); let mut to = point(x, y);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); 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(self.current_position);
ctrl2 += original_current_position.to_vector(); ctrl2 += original_current_position.to_vector();
to += original_current_position.to_vector(); to += original_current_position.to_vector();
} }
@ -304,7 +325,8 @@ impl Turtle {
let mut to = point(x, y); let mut to = point(x, y);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); 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(self.current_position);
to += original_current_position.to_vector(); to += original_current_position.to_vector();
} }
to = self.current_transform.transform_point(to); to = self.current_transform.transform_point(to);
@ -335,7 +357,8 @@ impl Turtle {
let mut to = point(x, y); let mut to = point(x, y);
if !abs { if !abs {
let inverse_transform = self.current_transform.inverse().unwrap(); 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(self.current_position);
to += original_current_position.to_vector(); to += original_current_position.to_vector();
ctrl += original_current_position.to_vector(); ctrl += original_current_position.to_vector();
} }
@ -456,7 +479,9 @@ impl Turtle {
/// Reset the position of the turtle to the origin in the current transform stack /// Reset the position of the turtle to the origin in the current transform stack
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.current_position = point(0.0, 0.0); self.current_position = point(0.0, 0.0);
self.current_position = self.current_transform.transform_point(self.current_position); self.current_position = self
.current_transform
.transform_point(self.current_position);
self.previous_control = None; self.previous_control = None;
self.initial_position = self.current_position; self.initial_position = self.current_position;
} }

Loading…
Cancel
Save