parent
dbf46ef01b
commit
b8b3aa1f5d
@ -1,8 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lsys"
|
name = "svg2gcode"
|
||||||
version = "0.1.0"
|
version = "0.0.1"
|
||||||
authors = ["purisame"]
|
authors = ["Sameer Puri <purisame@spuri.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
description = "Convert paths in SVG files to GCode for a pen plotter"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cairo-rs = { version = "^0", features = ["svg"] }
|
svgdom = "^0"
|
||||||
|
lyon_geom = "^0"
|
||||||
|
clap = "^2"
|
||||||
|
log = "^0"
|
||||||
|
env_logger = "^0"
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Sameer Puri
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 28 KiB |
@ -1,270 +1,453 @@
|
|||||||
extern crate cairo;
|
#[macro_use]
|
||||||
|
extern crate clap;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate svgdom;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
extern crate lyon_geom;
|
||||||
|
|
||||||
use cairo::{svg, Context};
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
fn main() {
|
use lyon_geom::math;
|
||||||
koch();
|
use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, FilterSvg, PathSegment};
|
||||||
sierpinski();
|
|
||||||
arrowhead();
|
|
||||||
dragon();
|
|
||||||
plant();
|
|
||||||
moore();
|
|
||||||
hilbert();
|
|
||||||
sierpinski_carpet();
|
|
||||||
snowflake();
|
|
||||||
gosper();
|
|
||||||
kolam();
|
|
||||||
crystal();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn koch() {
|
fn main() -> io::Result<()> {
|
||||||
let mut out = File::create("out/koch.svg").unwrap();
|
env_logger::init();
|
||||||
run(
|
let matches = clap_app!(svg2gcode =>
|
||||||
"F",
|
(version: crate_version!())
|
||||||
&['F'],
|
(author: crate_authors!())
|
||||||
|c: char| match c {
|
(about: crate_description!())
|
||||||
'F' => "F+F-F-F+F".chars().collect(),
|
(@arg FILE: "Selects the input SVG file to use, else reading from stdin")
|
||||||
other => vec![other],
|
(@arg tolerance: "Sets the interpolation tolerance for curves")
|
||||||
},
|
(@arg feedrate: "Sets the machine feed rate")
|
||||||
std::f64::consts::PI / 2.,
|
)
|
||||||
4,
|
.get_matches();
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sierpinski() {
|
let input = match matches.value_of("FILE") {
|
||||||
let mut out = File::create("out/sierpinski.svg").unwrap();
|
Some(filename) => {
|
||||||
run(
|
let mut f = File::open(filename)?;
|
||||||
"F-G-G",
|
let len = f.metadata()?.len();
|
||||||
&['F', 'G'],
|
let mut input = String::with_capacity(len as usize + 1);
|
||||||
|c: char| match c {
|
f.read_to_string(&mut input)?;
|
||||||
'F' => "F-G+F+G-F".chars().collect(),
|
input
|
||||||
'G' => "GG".chars().collect(),
|
}
|
||||||
other => vec![other],
|
None => {
|
||||||
},
|
let mut input = String::new();
|
||||||
std::f64::consts::PI * 2. / 3.,
|
io::stdin().read_to_string(&mut input)?;
|
||||||
4,
|
input
|
||||||
&mut out,
|
}
|
||||||
);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
fn arrowhead() {
|
let tolerance = matches
|
||||||
let mut out = File::create("out/arrowhead.svg").unwrap();
|
.value_of("tolerance")
|
||||||
run(
|
.and_then(|x| x.parse().ok())
|
||||||
"A",
|
.unwrap_or(0.1);
|
||||||
&['A', 'B'],
|
let feedrate = matches
|
||||||
|c: char| match c {
|
.value_of("feedrate")
|
||||||
'A' => "B-A-B".chars().collect(),
|
.and_then(|x| x.parse().ok())
|
||||||
'B' => "A+B+A".chars().collect(),
|
.unwrap_or(3000.0);
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI * 1. / 3.,
|
|
||||||
6,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dragon() {
|
let doc = svgdom::Document::from_str(&input).expect("Invalid or unsupported SVG file");
|
||||||
let mut out = File::create("out/dragon.svg").unwrap();
|
|
||||||
run(
|
|
||||||
"FX",
|
|
||||||
&['F'],
|
|
||||||
|c: char| match c {
|
|
||||||
'X' => "X+YF+".chars().collect(),
|
|
||||||
'Y' => "-FX-Y".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI / 2.,
|
|
||||||
12,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn plant() {
|
let tool_on_action = vec![MachineCode::StopSpindle, MachineCode::Dwell { p: 1.5 }];
|
||||||
let mut out = File::create("out/plant.svg").unwrap();
|
let tool_off_action = vec![
|
||||||
run(
|
MachineCode::Dwell { p: 0.1 },
|
||||||
"X",
|
MachineCode::StartSpindle {
|
||||||
&['F'],
|
d: Direction::Clockwise,
|
||||||
|c: char| match c {
|
s: 40.0,
|
||||||
'X' => "F-[[X]+X]+F[+FX]-X".chars().collect(),
|
|
||||||
'F' => "FF".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
},
|
||||||
std::f64::consts::PI * 25.0 / 180.0,
|
MachineCode::Dwell { p: 0.2 },
|
||||||
5,
|
];
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moore() {
|
let tool = std::cell::Cell::new(Tool::Off);
|
||||||
let mut out = File::create("out/moore.svg").unwrap();
|
let tool_on = |p: &mut Program| {
|
||||||
run(
|
if tool.get() == Tool::Off {
|
||||||
"LFL+F+LFL",
|
tool_on_action.iter().for_each(|x| {
|
||||||
&['F'],
|
p.push(x.clone());
|
||||||
|c: char| match c {
|
});
|
||||||
'L' => "-RF+LFL+FR-".chars().collect(),
|
tool.set(Tool::On);
|
||||||
'R' => "+LF-RFR-FL+".chars().collect(),
|
}
|
||||||
other => vec![other],
|
};
|
||||||
},
|
|
||||||
std::f64::consts::PI * 90.0 / 180.0,
|
|
||||||
5,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hilbert() {
|
let tool_off = |p: &mut Program| {
|
||||||
let mut out = File::create("out/hilbert.svg").unwrap();
|
if tool.get() == Tool::On {
|
||||||
run(
|
tool_off_action.iter().for_each(|x| {
|
||||||
"A",
|
p.push(x.clone());
|
||||||
&['F'],
|
});
|
||||||
|c: char| match c {
|
tool.set(Tool::Off);
|
||||||
'A' => "-BF+AFA+FB-".chars().collect(),
|
}
|
||||||
'B' => "+AF-BFB-FA+".chars().collect(),
|
};
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI / 2.0,
|
|
||||||
6,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sierpinski_carpet() {
|
let is_absolute = std::cell::Cell::from(true);
|
||||||
let mut out = File::create("out/sierpinski_carpet.svg").unwrap();
|
let incremental = |p: &mut Program| {
|
||||||
run(
|
if is_absolute.get() {
|
||||||
"F+F+F+F",
|
p.push(MachineCode::IncrementalDistanceMode);
|
||||||
&['F'],
|
is_absolute.set(false);
|
||||||
|c: char| match c {
|
}
|
||||||
'F' => "FF+F+F+F+FF".chars().collect(),
|
};
|
||||||
other => vec![other],
|
let absolute = |p: &mut Program| {
|
||||||
|
if !is_absolute.get() {
|
||||||
|
p.push(MachineCode::AbsoluteDistanceMode);
|
||||||
|
is_absolute.set(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut p = Program::new();
|
||||||
|
p.push(MachineCode::UnitsMillimeters);
|
||||||
|
tool_off(&mut p);
|
||||||
|
p.push(MachineCode::RapidPositioning {
|
||||||
|
x: 0.0.into(),
|
||||||
|
y: 0.0.into(),
|
||||||
|
});
|
||||||
|
tool_on(&mut p);
|
||||||
|
|
||||||
|
for (id, node) in doc.root().descendants().svg() {
|
||||||
|
if node.is_graphic() {
|
||||||
|
match id {
|
||||||
|
ElementId::Path => {
|
||||||
|
let attrs = node.attributes();
|
||||||
|
if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) {
|
||||||
|
p.push(MachineCode::Named(Box::new(node.id().to_string())));
|
||||||
|
let mut cx = 0.0;
|
||||||
|
let mut cy = 0.0;
|
||||||
|
for segment in path.iter() {
|
||||||
|
match segment {
|
||||||
|
PathSegment::MoveTo { abs, x, y } => {
|
||||||
|
tool_off(&mut p);
|
||||||
|
if *abs {
|
||||||
|
absolute(&mut p);
|
||||||
|
} else {
|
||||||
|
incremental(&mut p);
|
||||||
|
}
|
||||||
|
p.push(MachineCode::RapidPositioning {
|
||||||
|
x: (*x).into(),
|
||||||
|
y: (*y).into(),
|
||||||
|
});
|
||||||
|
if *abs {
|
||||||
|
cx = *x;
|
||||||
|
cy = *y;
|
||||||
|
} else {
|
||||||
|
cx += *x;
|
||||||
|
cy += *y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathSegment::ClosePath { abs } => {
|
||||||
|
tool_off(&mut p);
|
||||||
|
}
|
||||||
|
PathSegment::LineTo { abs, x, y } => {
|
||||||
|
tool_on(&mut p);
|
||||||
|
if *abs {
|
||||||
|
absolute(&mut p);
|
||||||
|
} else {
|
||||||
|
incremental(&mut p);
|
||||||
|
}
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: (*x).into(),
|
||||||
|
y: (*y).into(),
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
if *abs {
|
||||||
|
cx = *x;
|
||||||
|
cy = *y;
|
||||||
|
} else {
|
||||||
|
cx += *x;
|
||||||
|
cy += *y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathSegment::HorizontalLineTo { abs, x } => {
|
||||||
|
tool_on(&mut p);
|
||||||
|
if *abs {
|
||||||
|
absolute(&mut p);
|
||||||
|
} else {
|
||||||
|
incremental(&mut p);
|
||||||
|
}
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: (*x).into(),
|
||||||
|
y: None,
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
if *abs {
|
||||||
|
cx = *x;
|
||||||
|
} else {
|
||||||
|
cx += *x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathSegment::VerticalLineTo { abs, y } => {
|
||||||
|
tool_on(&mut p);
|
||||||
|
if *abs {
|
||||||
|
absolute(&mut p);
|
||||||
|
} else {
|
||||||
|
incremental(&mut p);
|
||||||
|
}
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: None,
|
||||||
|
y: (*y).into(),
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
if *abs {
|
||||||
|
cy = *y;
|
||||||
|
} else {
|
||||||
|
cy += *y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PathSegment::CurveTo {
|
||||||
|
abs,
|
||||||
|
x1,
|
||||||
|
y1,
|
||||||
|
x2,
|
||||||
|
y2,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
} => {
|
||||||
|
println!("Curve {:?} starting at ({}, {})", segment, cx, cy);
|
||||||
|
tool_on(&mut p);
|
||||||
|
absolute(&mut p);
|
||||||
|
let from = math::point(cx, cy);
|
||||||
|
let ctrl1 = if *abs {
|
||||||
|
math::point(*x1, *y1)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x1, cy + *y1)
|
||||||
|
};
|
||||||
|
let ctrl2 = if *abs {
|
||||||
|
math::point(*x2, *y2)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x2, cy + *y2)
|
||||||
|
};
|
||||||
|
let to = if *abs {
|
||||||
|
math::point(*x, *y)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x, cy + *y)
|
||||||
|
};
|
||||||
|
let cbs = lyon_geom::CubicBezierSegment {
|
||||||
|
from,
|
||||||
|
ctrl1,
|
||||||
|
ctrl2,
|
||||||
|
to,
|
||||||
|
};
|
||||||
|
let last_point = std::cell::Cell::new(math::point(cx, cy));
|
||||||
|
cbs.flattened(tolerance).for_each(|point| {
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: point.x.into(),
|
||||||
|
y: point.y.into(),
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
last_point.set(point);
|
||||||
|
});
|
||||||
|
cx = last_point.get().x;
|
||||||
|
cy = last_point.get().y;
|
||||||
|
}
|
||||||
|
PathSegment::Quadratic { abs, x1, y1, x, y } => {
|
||||||
|
tool_on(&mut p);
|
||||||
|
absolute(&mut p);
|
||||||
|
let from = math::point(cx, cy);
|
||||||
|
let ctrl = if *abs {
|
||||||
|
math::point(*x1, *y1)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x1, cy + *y1)
|
||||||
|
};
|
||||||
|
let to = if *abs {
|
||||||
|
math::point(*x, *y)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x, cy + *y)
|
||||||
|
};
|
||||||
|
let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to };
|
||||||
|
let last_point = std::cell::Cell::new(math::point(cx, cy));
|
||||||
|
qbs.flattened(tolerance).for_each(|point| {
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: point.x.into(),
|
||||||
|
y: point.y.into(),
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
last_point.set(point);
|
||||||
|
});
|
||||||
|
cx = last_point.get().x;
|
||||||
|
cy = last_point.get().y;
|
||||||
|
}
|
||||||
|
PathSegment::EllipticalArc {
|
||||||
|
abs,
|
||||||
|
rx,
|
||||||
|
ry,
|
||||||
|
x_axis_rotation,
|
||||||
|
large_arc,
|
||||||
|
sweep,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
} => {
|
||||||
|
tool_on(&mut p);
|
||||||
|
absolute(&mut p);
|
||||||
|
let from = math::point(cx, cy);
|
||||||
|
let to = if *abs {
|
||||||
|
math::point(*x, *y)
|
||||||
|
} else {
|
||||||
|
math::point(cx + *x, cy + *y)
|
||||||
|
};
|
||||||
|
let sarc = lyon_geom::SvgArc {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
radii: math::vector(*rx, *ry),
|
||||||
|
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(math::point(cx, cy));
|
||||||
|
sarc.for_each_flattened(
|
||||||
|
tolerance,
|
||||||
|
&mut |point: math::F64Point| {
|
||||||
|
p.push(MachineCode::LinearInterpolation {
|
||||||
|
x: point.x.into(),
|
||||||
|
y: point.y.into(),
|
||||||
|
z: None,
|
||||||
|
f: feedrate.into(),
|
||||||
|
});
|
||||||
|
last_point.set(point);
|
||||||
},
|
},
|
||||||
std::f64::consts::PI / 2.,
|
|
||||||
4,
|
|
||||||
&mut out,
|
|
||||||
);
|
);
|
||||||
|
cx = last_point.get().x;
|
||||||
|
cy = last_point.get().y;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => panic!("Unsupported path segment type"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!("Other {}", node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_off(&mut p);
|
||||||
|
p.push(MachineCode::RapidPositioning {
|
||||||
|
x: 0.0.into(),
|
||||||
|
y: 0.0.into(),
|
||||||
|
});
|
||||||
|
tool_on(&mut p);
|
||||||
|
p.push(MachineCode::ProgramEnd);
|
||||||
|
|
||||||
|
program2gcode(p, File::create("out.gcode")?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snowflake() {
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
let mut out = File::create("out/snowflake.svg").unwrap();
|
enum Direction {
|
||||||
run(
|
Clockwise,
|
||||||
"F++F++F",
|
Anticlockwise,
|
||||||
&['F'],
|
|
||||||
|c: char| match c {
|
|
||||||
'F' => "F-F++F-F".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI / 3.,
|
|
||||||
4,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gosper() {
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
let mut out = File::create("out/gosper.svg").unwrap();
|
enum Tool {
|
||||||
run(
|
Off,
|
||||||
"XF",
|
On,
|
||||||
&['F'],
|
|
||||||
|c: char| match c {
|
|
||||||
'X' => "X+YF++YF-FX--FXFX-YF+".chars().collect(),
|
|
||||||
'Y' => "-FX+YFYF++YF+FX--FX-Y".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI / 3.,
|
|
||||||
4,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kolam() {
|
macro_rules! write_if_some {
|
||||||
let mut out = File::create("out/kolam.svg").unwrap();
|
($w:expr, $s:expr, $v:ident) => {
|
||||||
run(
|
if let Some(v) = $v {
|
||||||
"-D--D",
|
write!($w, $s, v)
|
||||||
&['F'],
|
} else {
|
||||||
|c: char| match c {
|
Ok(())
|
||||||
'A' => "F++FFFF--F--FFFF++F++FFFF--F".chars().collect(),
|
}
|
||||||
'B' => "F--FFFF++F++FFFF--F--FFFF++F".chars().collect(),
|
};
|
||||||
'C' => "BFA--BFA".chars().collect(),
|
|
||||||
'D' => "CFC--CFC".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
|
||||||
std::f64::consts::PI / 4.0,
|
|
||||||
6,
|
|
||||||
&mut out,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn crystal() {
|
#[derive(Clone, PartialEq)]
|
||||||
let mut out = File::create("out/crystal.svg").unwrap();
|
enum MachineCode {
|
||||||
run(
|
RapidPositioning {
|
||||||
"F+F+F+F",
|
x: Option<f64>,
|
||||||
&['F'],
|
y: Option<f64>,
|
||||||
|c: char| match c {
|
|
||||||
'F' => "FF+F++F+F".chars().collect(),
|
|
||||||
other => vec![other],
|
|
||||||
},
|
},
|
||||||
std::f64::consts::PI / 2.,
|
LinearInterpolation {
|
||||||
4,
|
x: Option<f64>,
|
||||||
&mut out,
|
y: Option<f64>,
|
||||||
);
|
z: Option<f64>,
|
||||||
|
f: Option<f64>,
|
||||||
|
},
|
||||||
|
Dwell {
|
||||||
|
p: f64,
|
||||||
|
},
|
||||||
|
UnitsInches,
|
||||||
|
UnitsMillimeters,
|
||||||
|
ProgramEnd,
|
||||||
|
StartSpindle {
|
||||||
|
d: Direction,
|
||||||
|
s: f64,
|
||||||
|
},
|
||||||
|
StopSpindle,
|
||||||
|
AbsoluteDistanceMode,
|
||||||
|
IncrementalDistanceMode,
|
||||||
|
Named(Box<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<F, W>(
|
type Program = Vec<MachineCode>;
|
||||||
axiom: &str,
|
|
||||||
variables_to_draw: &[char],
|
|
||||||
rules: F,
|
|
||||||
angle: f64,
|
|
||||||
iterations: usize,
|
|
||||||
writer: W,
|
|
||||||
) where
|
|
||||||
F: Fn(char) -> Vec<char> + Copy,
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
let surf = svg::Writer::new(1024.0, 1024.0, writer);
|
|
||||||
let ctx = Context::new(&surf);
|
|
||||||
ctx.scale(1024., 1024.);
|
|
||||||
|
|
||||||
ctx.set_line_width(0.001);
|
fn program2gcode<W: Write>(p: Program, mut w: W) -> io::Result<()> {
|
||||||
ctx.set_source_rgb(0., 0., 0.);
|
use MachineCode::*;
|
||||||
|
for code in p.iter() {
|
||||||
let mut state = axiom.to_string();
|
match code {
|
||||||
|
RapidPositioning { x, y } => {
|
||||||
for _ in 0..iterations {
|
if let (None, None) = (x, y) {
|
||||||
state = state.chars().map(rules).flatten().collect();
|
continue;
|
||||||
}
|
}
|
||||||
|
write!(w, "G0")?;
|
||||||
// let segment_count = state.chars().filter(|c| variables.contains(&c)).count();
|
write_if_some!(w, " X{}", x)?;
|
||||||
|
write_if_some!(w, " Y{}", y)?;
|
||||||
ctx.move_to(0.5, 0.5);
|
writeln!(w)?;
|
||||||
let mut stack: Vec<((f64, f64), f64)> = vec![];
|
}
|
||||||
let mut curangle = -std::f64::consts::PI / 2.0;
|
LinearInterpolation { x, y, z, f } => {
|
||||||
for c in state.chars() {
|
if let (None, None, None, None) = (x, y, z, f) {
|
||||||
match c {
|
continue;
|
||||||
'+' => {
|
}
|
||||||
// ctx.rotate(angle);
|
write!(w, "G1")?;
|
||||||
curangle += angle;
|
write_if_some!(w, " X{}", x)?;
|
||||||
|
write_if_some!(w, " Y{}", y)?;
|
||||||
|
write_if_some!(w, " Z{}", z)?;
|
||||||
|
write_if_some!(w, " F{}", f)?;
|
||||||
|
writeln!(w, "")?;
|
||||||
|
}
|
||||||
|
Dwell { p } => {
|
||||||
|
writeln!(w, "G4 P{}", p)?;
|
||||||
|
}
|
||||||
|
UnitsInches => {
|
||||||
|
writeln!(w, "G20")?;
|
||||||
|
}
|
||||||
|
UnitsMillimeters => {
|
||||||
|
writeln!(w, "G21")?;
|
||||||
}
|
}
|
||||||
'-' => {
|
ProgramEnd => {
|
||||||
// ctx.rotate(-angle);
|
writeln!(w, "M20")?;
|
||||||
curangle -= angle;
|
|
||||||
}
|
}
|
||||||
'|' => {
|
StartSpindle { d, s } => {
|
||||||
curangle = -curangle;
|
let d = match d {
|
||||||
|
Direction::Clockwise => 3,
|
||||||
|
Direction::Anticlockwise => 4,
|
||||||
|
};
|
||||||
|
writeln!(w, "M{} S{}", d, s)?;
|
||||||
}
|
}
|
||||||
'[' => {
|
StopSpindle => {
|
||||||
stack.push((ctx.get_current_point(), curangle));
|
writeln!(w, "M5")?;
|
||||||
}
|
}
|
||||||
']' => {
|
AbsoluteDistanceMode => {
|
||||||
let state = stack.pop().unwrap();
|
writeln!(w, "G90")?;
|
||||||
ctx.move_to((state.0).0, (state.0).1);
|
|
||||||
curangle = state.1;
|
|
||||||
}
|
}
|
||||||
other => {
|
IncrementalDistanceMode => {
|
||||||
if variables_to_draw.contains(&other) {
|
writeln!(w, "G91")?;
|
||||||
ctx.rel_line_to(0.005 * f64::cos(curangle), 0.005 * f64::sin(curangle));
|
|
||||||
}
|
}
|
||||||
|
Named(name) => {
|
||||||
|
writeln!(w, "({})", name)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue