123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- 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<usize>,
- }
- impl DeleteObjects {
- pub fn delete(&self, layers: &mut ArrayOf<Layer>) {
- 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<u32>,
- count: Option<u32>,
- edge_offset: Option<f64>,
- start_offset: Option<f64>,
- end_offset: Option<f64>,
- angle: Option<f64>,
- rotate_angle: Option<f64>,
- line_reduction: Option<f64>,
- loop_distance: Option<f64>,
- loop_count: Option<u32>,
- pattern: Option<HatchPattern>,
- follow_edge_once: Option<bool>,
- cross_hatch: Option<bool>,
- }
- impl From<HatchConfig> 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<f64>,
- },
- #[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<Layer>) -> 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<f64>,
- origin: Option<Coordinate>,
- pen: Option<u32>,
- layer: Option<usize>,
- array: Option<ArrayConfig>,
- hatch: Option<HatchConfig>,
- export: Option<PathBuf>,
- replace_object: Option<usize>,
- }
- impl ObjectOperation {
- pub fn process(&self, pens: &Vec<Pen>, layers: &mut ArrayOf<Layer>) {
- 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<usize> = (0..(array.rows * array.columns)).collect();
- if array.randomize_order {
- seq.shuffle(&mut thread_rng());
- }
- // Generate objects
- let mut new_obj: Vec<Object> = 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);
- }
- }
- }
|