use std::path::PathBuf; use ezcad::{ array_of::ArrayOf, layer::Layer, objects::{ circle::Circle, hatch::{Hatch, HatchFlag, HatchPattern, HatchSetting, Hatches}, rectangle::Rectangle, Object, ObjectCore, Translate, }, pen::Pen, types::{Coordinate, ObjectType}, }; use log::{debug, error, warn}; use rand::{seq::SliceRandom, thread_rng}; use serde::{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, pen: Option, 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.pen.map(|x| *ret.pen = x.into()); 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 { #[serde(rename_all = "PascalCase")] Rectangle { width: f64, height: f64, round_corner: Option, }, #[serde(rename_all = "PascalCase")] Circle { radius: f64 }, #[serde(rename_all = "PascalCase")] Import { path: PathBuf }, #[serde(rename_all = "PascalCase")] Existing { layer: usize, object: usize }, } impl InputObject { fn new(&self, layers: &ArrayOf) -> Object { match self { InputObject::Rectangle { width, height, round_corner, } => Object::Rectangle(Rectangle { drawn_corner_a: Coordinate::from((-width / 2.0, -height / 2.0)).into(), drawn_corner_b: Coordinate::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!("Begin processing of object {:?}", self.input); let mut object: Object = self.input.new(layers); // Process basic transformation if self.origin.is_some() || self.z.is_some() { debug!( "Translating object to origin {:?} and Z {:?}", self.origin, self.z ); object.move_absolute(self.origin, self.z); } self.pen.map(|pen| { if self.array.is_some() { warn!("Ignoring pen setting as values will be overridden by array setting"); } else { assert!(pen < pens.len().try_into().unwrap(), "Invalid pen index"); debug!("Setting object pen to #{}", pen); object.set_pen(pen); } }); // Process conversion to hatch object let object = self.hatch.as_ref().map_or(object.clone(), |hatch| { if self.array.is_some() { warn!("Ignoring hatch pen setting as values will be overridden by array setting"); } let hatch_setting: HatchSetting = (*hatch).into(); match object { Object::Curve(_) | Object::Point(_) => { error!("Points, lines, and curves cannot be hatched"); object } Object::Rectangle(_) | Object::Circle(_) | Object::Ellipse(_) | Object::Polygon(_) => { debug!("Converting to hatch object"); // Build new hatch object (no hatch lines) Object::Hatch(Hatch { core: ObjectCore { origin: object.core().origin, z: object.core().z, ..ObjectCore::default(ObjectType::Hatch) }, outline: vec![object].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(), }), }) } Object::Hatch(hatch) => { debug!("Modifying existing hatch settings"); // Modify existing hatch (delete existing hatch lines) Object::Hatch(Hatch { 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(), }), ..hatch }) } } }); // Process array generation of object let new_objects = self.array.as_ref().map_or(vec![object.clone()], |array| { let bottom_left: Coordinate = Coordinate { 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; Coordinate { 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 delta: Coordinate = calc_pt(obj_idx); let pen: u32 = array.starting_pen + u32::try_from(obj_idx).unwrap(); let mut object: Object = object.clone(); object.move_relative(Some(delta), None); object.set_pen(pen); debug!( "Adding new array instance at {} with pen #{}", object.core().origin, pen ); new_obj.push(object); } new_obj }); let layer_id: usize = self.layer.unwrap_or(0); let layer: &mut Layer = layers.get_mut(layer_id).expect("Invalid layer index"); // Either export the object, replace an existing object, or append if neither if let Some(path) = &self.export { if new_objects.len() > 1 { warn!("Exporting only the first object in list of objects"); } else { debug!( "Exporting object {} in layer #{} to '{}'", new_objects[0], layer_id, path.to_string_lossy() ); } new_objects[0].write_to_file(path); } else if let Some(object) = self.replace_object { assert!(object < layer.objects.len(), "Invalid object index"); debug!( "Replacing object #{} in layer #{} with new objects", object, layer_id ); layer.objects.splice(object..=object, new_objects); } else { debug!("Extending layer #{} with new objects", layer_id); layer.objects.extend(new_objects); } } }