outlinify/src/geometry/gerber.rs
2024-08-23 10:52:33 +02:00

246 lines
12 KiB
Rust

use std::collections::HashMap;
use gerber_types::{
Aperture, ApertureMacro, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode,
MCode, MacroContent, Operation, Unit,
};
use tracing::{error, info};
use crate::{
geometry::{
elements::{
circle::Circle, linepath::LinePath, obround::Obround, rectangle::Rectangle, Element,
},
point::Point,
},
gerber::doc::GerberDoc,
};
use super::{
elements::{composite::Composite, polygon::Polygon},
union::{union_lines, union_with_apertures},
Geometry,
};
impl From<GerberDoc> for Geometry {
fn from(gerber: GerberDoc) -> Self {
// working variables
let mut selected_aperture = None;
let mut selected_interpolation_mode = InterpolationMode::Linear;
let mut current_position = Point::new(0., 0.);
let mut active_path: LinePath = LinePath::new();
let mut path_container: Vec<LinePath> = Vec::new();
let mut added_apertures: Vec<Element> = Vec::new();
// create geometries by applying all gerber commands
for command in gerber.commands {
match command {
Command::FunctionCode(f) => {
match f {
FunctionCode::DCode(code) => {
match code {
DCode::Operation(op) => match op {
Operation::Interpolate(coordinates, offset) => {
// create line by interpolating from current position to new position
if selected_interpolation_mode == InterpolationMode::Linear
{
// add current position as starting position if active path is empty (=> start new one)
if active_path.is_empty() {
active_path.add(current_position);
}
// add point (from gerber coordinates) to active path
match Point::try_from(&coordinates) {
Ok(point) => {
active_path.add(point);
}
Err(e) => error!("{e:?}"),
}
} else {
// TODO
// self.add_arc_segment(coord, offset.as_ref().expect(format!("No offset coord with 'Circular' state\r\n{:#?}", c).as_str()))
}
// move current coordinates to new position
Self::move_position(&coordinates, &mut current_position);
}
// move current coordinates to new position
Operation::Move(m) => {
// check if a aperture is selected and if it's circular
if let Some(Aperture::Circle(c)) =
selected_aperture.as_ref()
{
// check if a path is currently active
if !active_path.is_empty() {
// finish active path if there is an active one
active_path.finalize(c.diameter);
path_container.push(active_path);
}
}
// create new active path and move position tp current position
active_path = LinePath::new();
Self::move_position(&m, &mut current_position);
}
// add selected Aperture
Operation::Flash(f) => {
Self::add_geometry(
&mut added_apertures,
&gerber.aperture_macros,
&current_position,
&f,
&selected_aperture,
);
Self::move_position(&f, &mut current_position);
}
},
// select an aperture
DCode::SelectAperture(ap) => {
// check if a aperture is selected and if it's circular
if let Some(Aperture::Circle(c)) = selected_aperture.as_ref() {
// check if a path is currently active
if !active_path.is_empty() {
// finish active path if there is an active one before selecting new aperture
active_path.finalize(c.diameter);
path_container.push(active_path);
// create new active path
active_path = LinePath::new();
}
}
selected_aperture = Some(
gerber
.apertures
.get(&ap)
.unwrap_or_else(|| {
panic!("Unknown aperture id '{}'", ap)
})
.clone(),
)
}
}
}
FunctionCode::GCode(code) => match code {
GCode::InterpolationMode(im) => selected_interpolation_mode = im,
GCode::Comment(c) => info!("[COMMENT] \"{}\"", c),
_ => error!("Unsupported GCode:\r\n{:#?}", code),
},
FunctionCode::MCode(m) => {
// check for end of file
if m == MCode::EndOfFile && !active_path.is_empty() {
// finish current path if one is present
if let Some(Aperture::Circle(c)) = selected_aperture.as_ref() {
active_path.finalize(c.diameter);
path_container.push(active_path);
break; // finish executing commands
}
}
}
}
}
Command::ExtendedCode(_) => {}
}
}
// union all drawn lines into nets of conductors
let conductor_net = union_lines(&path_container);
// union conductors with apertures
let united_nets = union_with_apertures(&added_apertures, conductor_net).unwrap();
Self {
united_nets,
apertures: added_apertures,
paths: path_container,
units: gerber.units.unwrap_or(Unit::Millimeters).into(),
}
}
}
impl Geometry {
fn move_position(coord: &Coordinates, position: &mut Point) {
if let Ok(pos) = Point::try_from(coord) {
*position = pos;
};
}
fn add_geometry(
geometries: &mut Vec<Element>,
aperture_macros: &HashMap<String, ApertureMacro>,
position: &Point,
coordinates: &Coordinates,
aperture: &Option<Aperture>,
) {
let target = match Point::try_from(coordinates) {
Ok(point) => point,
Err(_) => *position,
};
match aperture.as_ref().expect("No aperture selected") {
Aperture::Circle(c) => {
geometries.push(Element::Circle(Circle::from_aperture_circle(c, target)));
}
Aperture::Rectangle(r) => {
geometries.push(Element::Rectangle(Rectangle::from_aperture_rectangular(
r, target,
)));
}
Aperture::Obround(o) => {
geometries.push(Element::Obround(Obround::from_aperture_obround(o, target)));
}
Aperture::Polygon(p) => {
// TODO add polygon
error!("Unsupported Polygon aperture:\r\n{:#?}", p);
}
Aperture::Other(o) => {
// split at '/' -> name/arguments
if let Some((name, args)) = o.split_once("/") {
// parse variables from args
let variables: Vec<f64> = args
.split(",")
.map(|s| s.parse::<f64>().unwrap_or(0.))
.collect();
if let Some(ap_macro) = aperture_macros.get(name) {
let mut composite = Composite::new();
// add elements from macro
for aperture in &ap_macro.content {
match aperture {
MacroContent::Circle(c) => composite.add(Element::Circle(
Circle::from_macro(c, &variables, target),
)),
MacroContent::VectorLine(vl) => composite.add(Element::Rectangle(
Rectangle::from_macro_vector_line(vl, &variables, target),
)),
MacroContent::CenterLine(cl) => composite.add(Element::Rectangle(
Rectangle::from_macro_center_line(cl, &variables, target),
)),
MacroContent::Outline(ol) => composite.add(Element::Polygon(
Polygon::from_macro(ol, &variables, target),
)),
MacroContent::Polygon(p) => {
error!("Polygon Not implemented yet")
}
MacroContent::Moire(m) => {
error!("Moire Not implemented yet")
}
MacroContent::Thermal(t) => {
error!("Thermal Not implemented yet")
}
MacroContent::VariableDefinition(_) => {}
MacroContent::Comment(_) => {}
};
}
composite.finalize();
geometries.push(Element::Composite(composite));
} else {
error!("Unsupported Other aperture:\r\n{:#?}", o);
}
} else {
error!("Unsupported Other aperture:\r\n{:#?}", o);
}
}
}
}
}