use std::{ fs::File, io::{Cursor, Read, Write}, path::PathBuf, }; use binrw::{BinRead, BinWriterExt}; use ezcad::{ array_of::ArrayOf, layer::Layer, objects::{ circle::Circle, hatch::{Hatch, HatchFlag, HatchPattern, HatchSetting, Hatches}, rectangle::Rectangle, Object, ObjectCore, Translate, }, pen::Pen, types::{ObjectType, Point}, }; use log::{debug, warn}; use rand::{seq::SliceRandom, thread_rng}; use serde::{de, Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteObjects { layer: usize, object: Option, } impl DeleteObjects { pub fn delete(&self, layers: &mut ArrayOf) { debug!( "Deleting layer #{} {}", match &self.object { Some(object) => format!("object #{}", object), None => format!("all objects"), }, self.layer, ); let layer: &mut Layer = layers.get_mut(self.layer).expect("Invalid layer index"); match self.object { Some(index) => { assert!(index < layer.objects.len(), "Invalid object index"); layer.objects.remove(index); } None => layer.objects.clear(), } } } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct HatchConfig { line_spacing: f64, count: Option, edge_offset: Option, start_offset: Option, end_offset: Option, angle: Option, rotate_angle: Option, line_reduction: Option, loop_distance: Option, loop_count: Option, pattern: Option, follow_edge_once: Option, cross_hatch: Option, } impl From for HatchSetting { fn from(value: HatchConfig) -> Self { let mut flags: HatchFlag = match value.pattern { Some(pattern) => pattern.into(), None => HatchPattern::Directional.into(), }; value .follow_edge_once .map(|x| flags.set_follow_edge_once(x.into())); value.cross_hatch.map(|x| flags.set_cross_hatch(x.into())); if value.start_offset.is_none() && value.end_offset.is_none() { flags.set_average_distribute_line(1); } let mut ret = Self::default(); *ret.line_spacing = value.line_spacing; value.count.map(|x| *ret.count = x.into()); value.edge_offset.map(|x| *ret.edge_offset = x.into()); value.start_offset.map(|x| *ret.start_offset = x.into()); value.end_offset.map(|x| *ret.end_offset = x.into()); value.angle.map(|x| *ret.angle = x.into()); value.rotate_angle.map(|x| *ret.rotate_angle = x.into()); value.line_reduction.map(|x| *ret.line_reduction = x.into()); value.loop_distance.map(|x| *ret.loop_distance = x.into()); value.loop_count.map(|x| *ret.loop_count = x.into()); *ret.flags = flags; *ret.enabled = true.into(); ret } } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ArrayConfig { columns: usize, rows: usize, spacing: f64, randomize_order: bool, starting_pen: u32, } #[derive(Debug, Serialize, Deserialize, strum::Display)] #[serde(rename_all = "PascalCase")] pub enum InputObject { Rectangle { width: f64, height: f64, round_corner: Option, }, Circle { radius: f64, }, Import { path: PathBuf, }, Existing { layer: usize, object: usize, }, } impl InputObject { fn new(&self, layers: &ArrayOf) -> Object { match self { InputObject::Rectangle { width, height, round_corner, } => Object::Rectangle(Rectangle { corner_a: Point::from((-width / 2.0, -height / 2.0)).into(), corner_b: Point::from((width / 2.0, height / 2.0)).into(), round_bottom_left: round_corner.unwrap_or(0.0).into(), round_bottom_right: round_corner.unwrap_or(0.0).into(), round_top_left: round_corner.unwrap_or(0.0).into(), round_top_right: round_corner.unwrap_or(0.0).into(), ..Default::default() }), InputObject::Circle { radius } => Object::Circle(Circle { radius: (*radius).into(), ..Default::default() }), InputObject::Import { path } => Object::read_from_file(path), InputObject::Existing { layer, object } => { let layer: &Layer = layers.get(*layer).expect("Invalid layer index"); layer .objects .get(*object) .expect("Invalid object index") .clone() } } } } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ObjectOperation { input: InputObject, z: Option, origin: Option, pen: Option, layer: Option, array: Option, hatch: Option, export: Option, replace_object: Option, } impl ObjectOperation { pub fn process(&self, pens: &Vec, layers: &mut ArrayOf) { debug!("Processing object {}", self.input); let mut object: Object = self.input.new(layers); // Process basic transformation object.translate(self.origin, self.z); self.pen.map(|pen| object.set_pen(pen)); // Process conversion to hatch object // Process array generation of object let new_objects = self.array.as_ref().map_or(vec![object.clone()], |array| { let bottom_left: Point = Point { x: (array.columns - 1) as f64 * array.spacing / -2.0, y: (array.rows - 1) as f64 * array.spacing / -2.0, }; // Closure that returns origin point of given index in array, where index starts // from bottom left and increments to the right and wraps around to the row above let calc_pt = |index: usize| { let x_pos: f64 = (index % array.columns) as f64; let y_pos: f64 = (index / array.columns) as f64; Point { x: bottom_left.x + array.spacing * x_pos, y: bottom_left.y + array.spacing * y_pos, } }; // Randomize draw order let mut seq: Vec = (0..(array.rows * array.columns)).collect(); if array.randomize_order { seq.shuffle(&mut thread_rng()); } // Generate objects let mut new_obj: Vec = vec![]; for obj_idx in seq.into_iter() { let mut object: Object = object.clone(); object.translate(Some(calc_pt(obj_idx)), None); object.set_pen(array.starting_pen + u32::try_from(obj_idx).unwrap()); new_obj.push(object); } new_obj }); // Process export of object self.export.as_ref().map(|path| { if new_objects.len() > 1 { warn!("Exporting only the first object in list of objects"); } else { debug!( "Exporting object {} to '{}'", new_objects[0], path.to_string_lossy() ); } new_objects[0].write_to_file(path); }); // Append or replace object in layer let layer: &mut Layer = layers .get_mut(self.layer.unwrap_or(0)) .expect("Invalid layer index"); match self.replace_object { Some(object) => { // debug!("Replacing object #{} with {}", object, new_objects); layer.objects.splice(object..=object, new_objects); } None => { layer.objects.extend(new_objects); } } } } // #[derive(Debug, Serialize, Deserialize)] // #[serde(rename_all = "PascalCase")] // pub struct Array { // layer: usize, // z: f64, // columns: usize, // rows: usize, // spacing: f64, // starting_pen: usize, // object: InputObject, // hatch: Option, // } // impl Array { // pub fn generate(&self, pens: &Vec, layers: &mut ArrayOf) { // debug!( // "Generating {} x {} array of {:?} with spacing of {} starting from pen #{} on layer #{}", // self.columns, self.rows, self.object, self.spacing, self.starting_pen, self.layer // ); // assert!( // self.rows >= 1 && self.columns >= 1, // "Invalid row/column value" // ); // assert!( // self.starting_pen + (self.rows * self.columns) < pens.len(), // "Invalid starting pen" // ); // let layer: &mut Layer = layers.get_mut(self.layer).expect("Invalid layer index"); // let bottom_left: Point = Point { // x: (self.columns - 1) as f64 * self.spacing / -2.0, // y: (self.rows - 1) as f64 * self.spacing / -2.0, // }; // // Closure that returns origin point of given index in array, where index starts // // from bottom left and increments to the right and wraps around to the row above // let calc_pt = |index: usize| { // let x_pos: f64 = (index % self.columns) as f64; // let y_pos: f64 = (index / self.columns) as f64; // Point { // x: bottom_left.x + self.spacing * x_pos, // y: bottom_left.y + self.spacing * y_pos, // } // }; // // Randomize draw order // let mut seq: Vec = (0..(self.rows * self.columns)).collect(); // seq.shuffle(&mut thread_rng()); // // Generate objects and append to layer // for obj_idx in seq.into_iter() { // let pen: u32 = (self.starting_pen + obj_idx).try_into().unwrap(); // // // Build outline object // // let mut rect: Rectangle = Rectangle::default(); // // let origin: Point = calc_pt(obj_idx); // // *rect.corner_a = Point { // // x: origin.x - self.width / 2.0, // // y: origin.y - self.height / 2.0, // // }; // // *rect.corner_b = Point { // // x: origin.x + self.width / 2.0, // // y: origin.y + self.height / 2.0, // // }; // // *rect.core.origin = origin; // // *rect.core.pen = pen; // // *rect.core.z = self.z; // // debug!( // // "Adding hatched rectangle with pen #{} at {} (from {} to {})", // // *rect.core.pen, *rect.core.origin, *rect.corner_a, *rect.corner_b // // ); // // let mut hatch_setting: HatchSetting = self.hatch.into(); // // *hatch_setting.pen = pen; // // // Build hatch object // // let hatch: Hatch = Hatch { // // core: ObjectCore { // // origin: origin.into(), // // z: self.z.into(), // // ..ObjectCore::default(ObjectType::Hatch) // // }, // // outline: vec![Object::Rectangle(rect)].into(), // // legacy_setting: vec![hatch_setting.clone()].into(), // // hatch_settings: vec![hatch_setting.clone()].into(), // // hatches: Some(Hatches { // // core: ObjectCore::default(ObjectType::HatchLine), // // hatch_lines: vec![].into(), // // }), // // }; // // layer.objects.push(Object::Hatch(hatch)); // } // } // }