Basic functionality is there :)

master
Samuel Bryner 2019-01-24 22:46:30 +01:00
commit b06d4876f0
7 zmienionych plików z 597 dodań i 0 usunięć

4
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

18
Cargo.toml 100644
Wyświetl plik

@ -0,0 +1,18 @@
[package]
name = "svg2gerber"
version = "0.1.0"
authors = ["Samuel Bryner <samuelbryner@gmx.ch>"]
[dependencies]
#gerber-types = { git = "https://github.com/dbrgn/gerber-types-rs" }
gerber-types = "0.1"
usvg = "0.5"
svgdom = "0.16"
lyon = "0.11"
conv = "0.3" # this is the version used by gerber-types
gnuplot = "0.0.26" # just for debugging
log = "0.4"
env_logger = "0.6"
[patch.crates-io]
gerber-types = { path = "../gerber-types-rs" }

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,153 @@
extern crate lyon;
use sort_polygons::Polygon;
use gerber_types::*;
use std::io::Write;
use conv::TryFrom;
#[derive(Clone, Debug)]
pub struct GerberBuilder {
cf: gerber_types::CoordinateFormat,
commands: Vec<gerber_types::Command>,
}
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
pub trait IntoCommand {
fn into_command(self) -> gerber_types::Command;
}
impl IntoCommand for FunctionCode {
fn into_command(self) -> gerber_types::Command {
self.into()
}
}
impl IntoCommand for gerber_types::GCode {
fn into_command(self) -> gerber_types::Command {
gerber_types::FunctionCode::GCode(self).into()
}
}
impl IntoCommand for gerber_types::ExtendedCode {
fn into_command(self) -> gerber_types::Command {
self.into()
}
}
impl IntoCommand for gerber_types::Operation {
fn into_command(self) -> gerber_types::Command {
gerber_types::FunctionCode::DCode(
gerber_types::DCode::Operation(self)
).into()
}
}
impl GerberBuilder {
pub fn new(cf: gerber_types::CoordinateFormat, layer_type: Part, layer_func: FileFunction, file_polarity: bool) -> Self { GerberBuilder {
cf: cf,
commands: vec![
// gerbv doesn't seem to parse these:
ExtendedCode::FileAttribute(
FileAttribute::GenerationSoftware(
GenerationSoftware::new("SAM", "svg2gerber", Some(VERSION))
)
).into(),
ExtendedCode::FileAttribute(
FileAttribute::Part(layer_type)
).into(),
ExtendedCode::FileAttribute(
FileAttribute::FileFunction(layer_func)
).into(),
ExtendedCode::FileAttribute(
FileAttribute::FilePolarity(if file_polarity {FilePolarity::Positive} else {FilePolarity::Negative})
).into(),
//FunctionCode::GCode( GCode::Comment("Ucamco ex. 1: Two square boxes".to_string())).into(),
ExtendedCode::CoordinateFormat(cf).into(),
ExtendedCode::Unit(Unit::Millimeters).into(),
// gerbv complains if there are no apertures defined
ExtendedCode::ApertureDefinition(
ApertureDefinition {
code: 10,
aperture: Aperture::Circle(Circle { diameter: 0.01, hole_diameter: None }),
}
).into(),
ExtendedCode::LoadPolarity(Polarity::Dark).into(), // this is the default, this makes our intentions explicit
FunctionCode::GCode(
GCode::InterpolationMode(InterpolationMode::Linear)
).into(),
],
} }
// this function should not require a mutable self, but then how can we call it in
// a function that mutates self?
pub fn vertex_to_coords(&self, vertex: &lyon::math::Point) -> Coordinates {
// seriously?! this gerber library seems unnecessarily complicated
Coordinates::new(
CoordinateNumber::try_from( vertex.x as f64).unwrap(),
// mirror vertical axis, as SVG and gerber use different conventions
// TODO: this is a bit hackish, no? where shall we put this instead?
CoordinateNumber::try_from(-vertex.y as f64).unwrap(),
self.cf
)
}
pub fn push<F: IntoCommand>(&mut self, cmd: F) {
self.commands.push(cmd.into_command());
}
pub fn publish<W: Write>(&mut self, writer: &mut W) {
// append EOF marker
self.push(FunctionCode::MCode(MCode::EndOfFile));
self.commands.serialize(writer).unwrap();
}
pub fn start_region(&mut self) {
// start a new region
self.push(GCode::RegionMode(true));
}
pub fn end_region(&mut self) {
// end region
self.push(GCode::RegionMode(false));
}
pub fn set_polarity(&mut self, polarity: bool) {
if polarity { // true = positive = add
self.push(ExtendedCode::LoadPolarity(Polarity::Dark));
} else { // false = negative = clear
self.push(ExtendedCode::LoadPolarity(Polarity::Clear));
}
}
pub fn add_polygon(&mut self, poly: &Polygon) {
self.start_region();
// goto last point
let pt = self.vertex_to_coords(poly.vertices.last().unwrap());
self.push(gerber_types::Operation::Move(pt));
// now convert the full polygon
for ref v in &poly.vertices {
let pt = self.vertex_to_coords(&v);
self.push(gerber_types::Operation::Interpolate(pt, None));
}
self.end_region();
}
}

111
src/main.rs 100644
Wyświetl plik

@ -0,0 +1,111 @@
#[macro_use] extern crate log;
extern crate env_logger;
extern crate usvg;
extern crate gerber_types;
extern crate lyon;
extern crate conv;
mod path_convert;
mod gerber_builder;
mod sort_polygons;
use std::path::Path;
use std::fs::File;
use std::io::stdout;
use std::env;
use gerber_types::*;
//use lyon::tessellation as tess;
use lyon::path::iterator::PathIterator;
fn main() {
env_logger::init();
let args: Vec<String> = env::args().collect();
if args.len() != 2 && args.len() != 3 && args.len() != 4 {
println!("Usage:\n\tsvg2gerber input.svg [output.gerb [layer_type]]");
return;
}
// load svg from disk and simplify it considerably with usvg
let svg = usvg::Tree::from_file(&args[1], &usvg::Options::default()).expect("failed to load SVG");
// TODO: for a proper script we probably want to add separate parameters for all of these:
let part_type = if args.len() < 4 {
(gerber_types::Part::Other("Unknown".to_string()), gerber_types::FileFunction::Other("Unkown".to_string()), true)
} else {
match args[3].to_ascii_lowercase().as_ref() {
"f.cu" => (gerber_types::Part::Single, gerber_types::FileFunction::Copper{layer: 1, pos: gerber_types::ExtendedPosition::Top, copper_type: None}, true),
"b.cu" => (gerber_types::Part::Single, gerber_types::FileFunction::Copper{layer: 2, pos: gerber_types::ExtendedPosition::Bottom, copper_type: None}, true),
"f.mask" => (gerber_types::Part::Single, gerber_types::FileFunction::Soldermask{index: None, pos: gerber_types::Position::Top}, false),
"b.mask" => (gerber_types::Part::Single, gerber_types::FileFunction::Soldermask{index: None, pos: gerber_types::Position::Bottom}, false),
&_ => panic!("unknown layer type {}", args[3]),
}
};
let mut gerb = gerber_builder::GerberBuilder::new(
CoordinateFormat::new(5, 6),
part_type.0, // part type (single PCB or 'other')
part_type.1, // file function (copper, solder mask, ...)
part_type.2, // polarity (true = positive = add stuff where gerber has stuff)
);
//let mut fill_tess = lyon::tessellation::FillTessellator::new();
let mut polys = Vec::new();
for node in svg.root().descendants() {
if let usvg::NodeKind::Path(ref p) = *node.borrow() {
// TODO: do we have to handle transformations here? usvg should already have removed
// those for us, no?
let path = path_convert::convert_path(p).path_iter();
// convert path containing bezier curves and other complicated things into something
// piece-wise linear
let flattened = path.flattened(0.01);
polys.extend(sort_polygons::Polygon::from_path(flattened));
/*
fill_tess.tessellate_path(
path_convert::convert_path(p).path_iter(),
&lyon::tessellation::FillOptions::tolerance(0.01),
&mut data
).expect("Error during tesselation!");
*/
}
// TODO: extract layer information from Inkscape-SVG
}
let mut parents = sort_polygons::create_parent_list(&polys);
info!("got {} polygons", polys.len());
parents.sort_unstable_by_key(|pi| pi.level);
for (idx, p) in parents.iter().enumerate() {
info!(" - poly {} has parent {:?} and level {}", idx, p.parent_idx, p.level);
gerb.set_polarity(p.level % 2 == 0);
gerb.add_polygon(p.polygon);
}
if args.len() > 2 {
if args[2] == "-" {
// publish to stdout
gerb.publish(&mut stdout());
} else {
let mut outfile = File::create(&args[2]).expect("Could not create output file");
gerb.publish(&mut outfile);
}
} else {
let path = Path::new(&args[1]);
let path = Path::new(path.file_stem().unwrap()).with_extension("gerb");
let mut outfile = File::create(path).expect("Could not create output file");
gerb.publish(&mut outfile);
}
}

Wyświetl plik

@ -0,0 +1,46 @@
// from https://raw.githubusercontent.com/nical/lyon/master/examples/svg_render/src/path_convert.rs
use std::iter;
use std::slice;
use lyon::math::Point;
use lyon::path::PathEvent;
use lyon::path::iterator::PathIter;
use usvg::{Path, PathSegment};
fn point(x: f64, y: f64) -> Point {
Point::new(x as f32, y as f32)
}
// Map usvg::PathSegment to lyon::path::PathEvent
fn as_event(ps: &PathSegment) -> PathEvent {
match *ps {
PathSegment::MoveTo { x, y } => PathEvent::MoveTo(point(x, y)),
PathSegment::LineTo { x, y } => PathEvent::LineTo(point(x, y)),
PathSegment::CurveTo { x1, y1, x2, y2, x, y, } => {
PathEvent::CubicTo(point(x1, y1), point(x2, y2), point(x, y))
}
PathSegment::ClosePath => PathEvent::Close,
}
}
pub struct PathConv<'a>(SegmentIter<'a>);
// Alias for the iterator returned by usvg::Path::iter()
type SegmentIter<'a> = slice::Iter<'a, PathSegment>;
// Alias for our `interface` iterator
type PathConvIter<'a> = iter::Map<SegmentIter<'a>, fn(&PathSegment) -> PathEvent>;
// Provide a function which gives back a PathIter which is compatible with
// tesselators, so we don't have to implement the PathIterator trait
impl<'a> PathConv<'a> {
pub fn path_iter(self) -> PathIter<PathConvIter<'a>> {
PathIter::new(self.0.map(as_event))
}
}
pub fn convert_path<'a>(p: &'a Path) -> PathConv<'a> {
PathConv(p.segments.iter())
}

Wyświetl plik

@ -0,0 +1,265 @@
extern crate lyon;
//extern crate gnuplot;
//use sort_polygons::gnuplot::AxesCommon;
//use lyon::tessellation as tess;
use lyon::path::iterator::PathIterator;
use lyon::path::FlattenedEvent;
use lyon::algorithms::path::iterator::Flattened;
#[derive(PartialEq)]
pub struct Polygon {
pub vertices: Vec<lyon::math::Point>,
}
impl Polygon {
pub fn new() -> Self {
Polygon {
vertices: Vec::new(),
}
}
pub fn from_path<Iter: PathIterator>(path: Flattened<Iter>) -> Vec<Self> {
let mut polys = Vec::new();
let mut current_poly = None;
for evt in path {
match evt {
FlattenedEvent::MoveTo(p) => {
let mut poly = Polygon::new();
poly.vertices.push(p);
current_poly = Some(poly);
}
FlattenedEvent::LineTo(p) => {
if let Some(ref mut poly) = current_poly {
poly.vertices.push(p);
}
}
FlattenedEvent::Close => {
polys.push(current_poly.unwrap());
current_poly = None;
}
}
}
polys
}
}
pub struct PolyPoint<'a> {
vertex: &'a lyon::math::Point,
prev: &'a lyon::math::Point,
next: &'a lyon::math::Point,
//pub poly_parent: &'a mut Option<&'a Polygon<'a>>,
poly_idx: usize,
// TODO: equality operator that just compares 'vertex'
}
impl<'a> PolyPoint<'a> {
pub fn list(polys: &'a Vec<Polygon>) -> Vec<PolyPoint> {
let mut pts = Vec::new();
for (poly_idx, poly) in polys.iter().enumerate() {
assert!(poly.vertices.len() >= 3, "Got a degenerate polygon with only {} vertices.", poly.vertices.len());
let mut v1 = &poly.vertices[poly.vertices.len()-2]; // second last element
let mut v2 = &poly.vertices[poly.vertices.len()-1]; // last element
debug_assert!(v1 != v2, "got same element twice: point {}", v1);
for v3 in &poly.vertices {
pts.push(PolyPoint {
prev: v1,
vertex: v2,
next: v3,
poly_idx: poly_idx,
});
//trace!("adding Vertex: {}, {}, {}", v1, v2, v3);
v1 = v2;
v2 = v3;
}
}
pts
}
}
// returns points sorted in descending y order
pub fn sort_poly_points(pts: &mut Vec<PolyPoint>) {
// sorting floats is only possible when we don't have NaNs
pts.sort_by(|a, b| b.vertex.y.partial_cmp(&a.vertex.y).unwrap() );
}
struct Edge<'a> {
upper: &'a lyon::math::Point, // higher y
lower: &'a lyon::math::Point, // lower y
poly_idx: usize,
}
impl<'a> Edge<'a> {
pub fn interpolate_x(&self, y: f32) -> f32 {
debug_assert!(self.upper.y >= y && y >= self.lower.y,
"interpolation point must lie between edge's end points: Edge is from {} to {}, query y is {}.", self.upper.y, self.lower.y, y);
let r = (y - self.lower.y) / (self.upper.y - self.lower.y);
return r * (self.upper.x - self.lower.x) + self.lower.x;
}
}
// insert or remove edge from scanline (scanline is at 'vert')
fn handle_edge<'a>(scanline: &mut Vec<Edge<'a>>, vert: &'a lyon::math::Point, other: &'a lyon::math::Point, poly_idx: usize) {
if vert == other {
return; // ignore degenerate edges with zero length
}
//debug_assert!(vert != other, "Edge must consist of two distinct points, but got {} twice.", vert);
trace!(" -> handling edge from {} to {} (poly {})", vert, other, poly_idx);
if other.y == vert.y {
trace!(" -> ignoring horizontal edges");
return;
}
if other.y > vert.y {
// edge ends at scanline
// remove it from scanline
// TODO: implement ordering trait for Edge that uses interpolated x value so we can find
// our edge more efficiently
trace!(" -> removing edge, it ends here");
scanline.retain(|edge| edge.lower != vert);
} else {
// edge starts at scanline
// insert it in a sorted fashion
let index;
match scanline.binary_search_by(|edge| {edge.interpolate_x(vert.y).partial_cmp(&vert.x).unwrap()}) {
Ok(i) => index = i, // found other edge at this point. TODO: This should not happen and we probably want at least a warning here.
Err(i) => index = i, // not found, but it belongs there
}
trace!(" -> insert edge at index {}", index);
scanline.insert(index, Edge { upper: vert, lower: other, poly_idx: poly_idx });
}
}
#[derive(Clone)]
pub struct ParentInfo<'a> {
pub polygon: &'a Polygon,
pub parent_idx: Option<usize>,
pub level: usize, // 0 means poly is outermost
}
pub fn create_parent_list<'a>(polygons: &'a Vec<Polygon>) -> Vec<ParentInfo<'a>> {
let mut pts = PolyPoint::list(&polygons);
sort_poly_points(&mut pts);
let mut current_scanline: Vec<Edge> = Vec::new();
let mut parents: Vec<Option<ParentInfo>> = vec![None; polygons.len()];
for (_step, pt) in pts.iter().enumerate() {
trace!("scanline is at y = {}", pt.vertex.y);
// look at edge (prev, vertex)
handle_edge(&mut current_scanline, pt.vertex, pt.prev, pt.poly_idx);
// look at edge (vertex, next)
handle_edge(&mut current_scanline, pt.vertex, pt.next, pt.poly_idx);
let mut parent_stack: Vec<usize> = Vec::new();
/*
let mut fig = gnuplot::Figure::new();
{
let mut ax = fig.axes2d();
ax.set_title(&format!("Step {}", _step), &[]);
ax.lines(&[pt.vertex.x, pt.prev.x], &[pt.vertex.y, pt.prev.y], &[gnuplot::Color("black"), gnuplot::LineWidth(2.0)]);
ax.lines(&[pt.vertex.x, pt.next.x], &[pt.vertex.y, pt.next.y], &[gnuplot::Color("black"), gnuplot::LineWidth(2.0)]);
ax.points(&[pt.vertex.x], &[pt.vertex.y], &[gnuplot::Color("black"), gnuplot::PointSize(5.0), gnuplot::PointSymbol('o')]);
*/
// count number of edges between current vertex and the outside (while ignoring edges of
// the current polygon)
for ref edge in &current_scanline {
// only look at edges on the left of the current vertex
if edge.interpolate_x(pt.vertex.y) >= pt.vertex.x {
break;
}
// ignore edges from current polygon
if edge.poly_idx == pt.poly_idx {
continue;
}
//ax.lines(&[edge.upper.x, edge.lower.x], &[edge.upper.y, edge.lower.y], &[gnuplot::Color("red"), gnuplot::LineWidth(2.0)]);
// push or pop polys to/from stack
let mut pop = false;
if let Some(p) = parent_stack.last() {
if *p == edge.poly_idx {
pop = true;
}
}
if pop {
parent_stack.pop();
} else {
parent_stack.push(edge.poly_idx);
}
}
/*
}
fig.show();
*/
trace!(" -> handling point {:?}", pt.vertex);
trace!(" -> last edge on stack of {}: {:?}", parent_stack.len(), parent_stack.last());
if !parents[pt.poly_idx].is_some() {
// parent information not yet defined, add it
parents[pt.poly_idx] = Some(ParentInfo{
polygon: &polygons[pt.poly_idx],
parent_idx: parent_stack.last().cloned(),
level : parent_stack.len(),
});
trace!(" -> assigned parent {:?}", parent_stack.last());
} else if let Some(ref pi) = &parents[pt.poly_idx] {
// polygon at poly_idx already has a parent & level defined
// make sure it is the right one
// (this should not be necessary, just to make sure our implementation is correct
// and our assumptions were valid)
assert!(pi.level == parent_stack.len(),
"Invalid level for polygon {}: Expected {}, but we previously calculated {}.",
pt.poly_idx, parent_stack.len(), pi.level);
assert!(pi.parent_idx.is_some() == parent_stack.last().is_some(),
"Invalid parent for polygon {}: Expected to have parent? {}. Previously determined: {}.",
pt.poly_idx, parent_stack.last().is_some(), pi.parent_idx.is_some());
if let Some(p) = pi.parent_idx {
assert!(p == *parent_stack.last().unwrap(),
"Invalid parent computed: Polygon {} already has parent {}, but we just found {:?} as parent.",
pt.poly_idx, p, parent_stack.last());
}
}
}
assert!(parents.len() == polygons.len(), "Did not process all polygons. Only got {} out of {}.", parents.len(), polygons.len());
parents.into_iter().map(|p| p.unwrap()).collect()
}