Browse Source

Add support for x/y pen patterning

Kevin Lee 4 months ago
parent
commit
6be9dae0a4
5 changed files with 177 additions and 117 deletions
  1. 18 12
      README.md
  2. 3 1
      samples/config.yml
  3. 65 34
      src/config/object.rs
  4. 61 68
      src/config/pen.rs
  5. 30 2
      src/ezcad/pen.rs

+ 18 - 12
README.md

@@ -63,14 +63,18 @@ Incrementing a specific field by some value across a sequence of pens:
 ``` yaml
 Ops: 
   - !PatternPen
-    From: 1
-    To: 5
-
-    # Specify one of the following:
-    Field: !Loops 1
-    Field: !Speed 100.0
-    Field: !Power 10.0
-    Field: !Frequency 1000
+    Index: 1
+    Count: 5
+    Field: <PenPattern>
+```
+
+Where `<PenPattern>` is one of the following:
+
+``` yaml
+Field: !Loops 1
+Field: !Speed 100.0
+Field: !Power 10.0
+Field: !Frequency 1000
 ```
 
 Exporting a pen to a file:
@@ -120,7 +124,7 @@ Ops:
     ReplaceObject: 0
 ```
 
-`<InputObject>` can be one of the following:
+Where `<InputObject>` is one of the following:
 
 ``` yaml
 Input: !Rectangle { Width: 3.0, Height: 2.0 }
@@ -129,7 +133,7 @@ Input: !Import { Path: "exported_object.bin" }
 Input: !Existing { Layer: 0, Object: 1 }
 ```
 
-`<ArrayOptions>` options:
+Where `<ArrayOptions>` is:
 
 ``` yaml
 Array:
@@ -138,9 +142,11 @@ Array:
   Spacing: 1.0
   RandomizeOrder: True
   StartingPen: 1
+  PatternX: <PenPattern>
+  PatternY: <PenPattern> // Optional
 ```
 
-`<HatchOptions>` options:
+Where `<HatchOptions>` is:
 
 ``` yaml
 Hatch:
@@ -162,7 +168,7 @@ Hatch:
   Crosshatch: True
 ```
 
-`<Pattern>` options:
+Where `<Pattern>` is one of the following:
 
 ``` yaml
 Pattern: !Directional

+ 3 - 1
samples/config.yml

@@ -39,10 +39,12 @@ Ops:
     Pen: 1
     Array:
       Columns: 3
-      Rows: 2
+      Rows: 4
       Spacing: 6.0
       RandomizeOrder: False
       StartingPen: 0
+      PatternX: !Speed 1.0
+      PatternY: !Power -1.0
     Hatch:
       LineSpacing: 0.01
     ReplaceObject: 0

+ 65 - 34
src/config/object.rs

@@ -16,6 +16,8 @@ use log::{debug, error, warn};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
 
+use super::pen::PatternField;
+
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
 pub struct DeleteObjects {
@@ -111,7 +113,9 @@ pub struct ArrayConfig {
     rows: usize,
     spacing: f64,
     randomize_order: bool,
-    starting_pen: u32,
+    starting_pen: usize,
+    pattern_x: PatternField,
+    pattern_y: Option<PatternField>,
 }
 
 #[derive(Debug, Serialize, Deserialize, strum::Display)]
@@ -179,7 +183,7 @@ pub struct ObjectOperation {
 }
 
 impl ObjectOperation {
-    pub fn process(&self, pens: &Vec<Pen>, layers: &mut ArrayOf<Layer>) {
+    pub fn process(&self, pens: &mut Vec<Pen>, layers: &mut ArrayOf<Layer>) {
         debug!("Begin processing of object {:?}", self.input);
 
         let mut object: Object = self.input.new(layers);
@@ -205,7 +209,7 @@ impl ObjectOperation {
 
         // Process conversion to hatch object
         let object = self.hatch.as_ref().map_or(object.clone(), |hatch| {
-            if self.array.is_some() {
+            if hatch.pen.is_some() && self.array.is_some() {
                 warn!("Ignoring hatch pen setting as values will be overridden by array setting");
             }
             let hatch_setting: HatchSetting = (*hatch).into();
@@ -254,46 +258,73 @@ impl ObjectOperation {
 
         // Process array generation of object
         let new_objects = self.array.as_ref().map_or(vec![object.clone()], |array| {
+            let mut new_obj: Vec<Object> = vec![];
+
             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,
+            // Generate objects
+            for y in 0..array.rows {
+                for x in 0..array.columns {
+                    let delta: Coordinate = bottom_left
+                        + Coordinate {
+                            x: array.spacing * x as f64,
+                            y: array.spacing * y as f64,
+                        };
+
+                    let pen: usize = array.starting_pen + y * array.columns + x;
+
+                    let mut object: Object = object.clone();
+                    object.move_relative(Some(delta), None);
+                    object.set_pen(pen.try_into().unwrap());
+
+                    debug!(
+                        "Adding new object at {} with pen #{}",
+                        object.core().origin,
+                        pen
+                    );
+                    new_obj.push(object);
                 }
-            };
-
-            // 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);
+            // Generate pens
+            match &array.pattern_y {
+                None => {
+                    array.pattern_x.pattern(
+                        &mut pens
+                            .iter_mut()
+                            .enumerate()
+                            .skip(array.starting_pen)
+                            .take(array.columns * array.rows),
+                    );
+                }
+                Some(pattern_y) => {
+                    pattern_y.pattern(
+                        &mut pens
+                            .iter_mut()
+                            .enumerate()
+                            .skip(array.starting_pen)
+                            .step_by(array.columns)
+                            .take(array.rows),
+                    );
+                    for y in 0..array.rows {
+                        array.pattern_x.pattern(
+                            &mut pens
+                                .iter_mut()
+                                .enumerate()
+                                .skip(array.starting_pen)
+                                .skip(y * array.columns)
+                                .take(array.columns),
+                        )
+                    }
+                }
+            }
 
-                debug!(
-                    "Adding new array instance at {} with pen #{}",
-                    object.core().origin,
-                    pen
-                );
-                new_obj.push(object);
+            if array.randomize_order {
+                debug!("Randomizing draw order of array objects");
+                new_obj.shuffle(&mut thread_rng());
             }
 
             new_obj

+ 61 - 68
src/config/pen.rs

@@ -1,10 +1,5 @@
-use std::{
-    fs::File,
-    io::{Cursor, Read, Write},
-    path::PathBuf,
-};
+use std::path::PathBuf;
 
-use binrw::{BinRead, BinWriterExt};
 use ezcad::{pen::Pen, types::Rgba};
 use log::debug;
 use serde::{Deserialize, Serialize};
@@ -28,61 +23,59 @@ pub struct Patch {
 }
 
 impl Patch {
-    fn patch(&self, id: usize, pens: &mut Vec<Pen>) {
-        let to_patch: &mut Pen = pens.get_mut(id).expect("Invalid pen index");
-
+    fn patch(&self, pen: &mut Pen) {
         self.color.map(|color| {
-            debug!("Patching color for pen #{} to {:?}", id, color);
-            *to_patch.color = color.into()
+            debug!("Patching pen color to {:?}", color);
+            *pen.color = color.into()
         });
 
         self.enabled.map(|enabled| {
-            debug!("Patching enablement for pen #{} to {}", id, enabled);
-            *to_patch.disabled = !enabled as u32;
+            debug!("Patching pen enablement to {}", enabled);
+            *pen.disabled = !enabled as u32;
         });
 
         self.loop_count.map(|loop_count| {
-            debug!("Patching loop count for pen #{} to {}", id, loop_count);
+            debug!("Patching pen loop count to {}", loop_count);
             assert!(loop_count > 0, "Pen loop count must be greater than zero");
-            *to_patch.loop_count = loop_count;
+            *pen.loop_count = loop_count;
         });
 
         self.speed.map(|speed| {
-            debug!("Patching speed for pen #{} to {}", id, speed);
+            debug!("Patching pen speed {}", speed);
             assert!(
                 speed > SPEED_MIN && speed <= SPEED_MAX,
                 "Pen speed must be between {} and {}",
                 SPEED_MIN,
                 SPEED_MAX
             );
-            *to_patch.speed = speed;
+            *pen.speed = speed;
         });
 
         self.power.map(|power| {
-            debug!("Patching power for pen #{} to {}", id, power);
+            debug!("Patching pen power {}", power);
             assert!(
                 power > POWER_MIN && power <= POWER_MAX,
                 "Pen power must be between {} and {}",
                 POWER_MIN,
                 POWER_MAX
             );
-            *to_patch.power = power;
+            *pen.power = power;
         });
 
         self.frequency.map(|frequency| {
-            debug!("Patching frequency for pen #{} to {}", id, frequency);
+            debug!("Patching pen frequency {}", frequency);
             assert!(
                 frequency >= FREQUENCY_MIN && frequency <= FREQUENCY_MAX,
                 "Pen frequency must be between {} and {}",
                 FREQUENCY_MIN,
                 FREQUENCY_MAX
             );
-            *to_patch.frequency = frequency;
-            *to_patch.frequency_2 = frequency.try_into().unwrap();
+            *pen.frequency = frequency;
+            *pen.frequency_2 = frequency.try_into().unwrap();
         });
 
         // Always enable custom settings for pen
-        *to_patch.use_default = 0;
+        *pen.use_default = 0;
     }
 }
 
@@ -96,7 +89,9 @@ pub struct PatchPen {
 
 impl PatchPen {
     pub fn patch(&self, pens: &mut Vec<Pen>) {
-        self.patch.patch(self.pen, pens);
+        debug!("Patching pen #{}", self.pen);
+        let pen: &mut Pen = pens.get_mut(self.pen).expect("Invalid pen index");
+        self.patch.patch(pen);
     }
 }
 
@@ -140,7 +135,8 @@ impl ClonePen {
 
                     // Patch pen if needed
                     self.patch.as_ref().map(|patch| {
-                        patch.patch(idx, pens);
+                        debug!("Patching pen #{}", idx);
+                        patch.patch(dst);
                     });
                 }
             }
@@ -150,7 +146,8 @@ impl ClonePen {
 
                 // Patch pen if needed
                 self.patch.as_ref().map(|patch| {
-                    patch.patch(self.to, pens);
+                    debug!("Patching pen #{}", self.to);
+                    patch.patch(dst);
                 });
             }
         }
@@ -165,55 +162,39 @@ pub enum PatternField {
     Frequency(i32),
 }
 
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct PatternPen {
-    from: usize,
-    to: usize,
-    field: PatternField,
-}
+impl PatternField {
+    pub fn pattern(&self, pens: &mut dyn Iterator<Item = (usize, &mut Pen)>) {
+        // Obtain settings from source (first) pen
+        let (src_idx, src) = pens.next().expect("Pattern must involve at least one pen");
 
-impl PatternPen {
-    pub fn pattern(&self, pens: &mut Vec<Pen>) {
-        debug!("Patterning from pen #{} to #{}", self.from, self.to);
-        assert!(
-            self.to > self.from,
-            "Target pen(s) must be greater than source pen"
-        );
-
-        // Obtain settings from first pen
-        let src: Pen = pens.get(self.from).expect("Invalid pen index").clone();
-
-        let mut setting: PatternField = match self.field {
+        let mut setting: PatternField = match self {
             PatternField::Loops(_) => {
                 debug!(
                     "Initial loop count from pen #{} is {}",
-                    self.from, *src.loop_count
+                    src_idx, *src.loop_count
                 );
                 PatternField::Loops((*src.loop_count).try_into().unwrap())
             }
             PatternField::Speed(_) => {
-                debug!("Initial speed from pen #{} is {}", self.from, *src.speed);
+                debug!("Initial speed from pen #{} is {}", src_idx, *src.speed);
                 PatternField::Speed(*src.speed)
             }
             PatternField::Power(_) => {
-                debug!("Initial power from pen #{} is {}", self.from, *src.power);
+                debug!("Initial power from pen #{} is {}", src_idx, *src.power);
                 PatternField::Power(*src.power)
             }
             PatternField::Frequency(_) => {
                 debug!(
                     "Initial frequency from pen #{} is {}",
-                    self.from, *src.frequency
+                    src_idx, *src.frequency
                 );
                 PatternField::Frequency((*src.frequency).try_into().unwrap())
             }
         };
 
-        for idx in (self.from..=self.to).skip(1) {
-            let dst: &mut Pen = pens.get_mut(idx).expect("Invalid pen index");
-
+        for (idx, dst) in pens {
             // Calculate new setting
-            setting = match (setting, &self.field) {
+            setting = match (setting, self) {
                 (PatternField::Loops(prev), PatternField::Loops(incr)) => {
                     let value: i32 = prev + incr;
                     debug!("Patching loop count for pen #{} to {}", idx, value);
@@ -274,6 +255,31 @@ impl PatternPen {
     }
 }
 
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct PatternPen {
+    index: usize,
+    count: usize,
+    field: PatternField,
+}
+
+impl PatternPen {
+    pub fn pattern(&self, pens: &mut Vec<Pen>) {
+        debug!(
+            "Patterning from pen #{} to #{}",
+            self.index,
+            self.index + self.count - 1
+        );
+        self.field.pattern(
+            &mut pens
+                .iter_mut()
+                .enumerate()
+                .skip(self.index)
+                .take(self.count),
+        )
+    }
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
 pub struct ImportExportPen {
@@ -291,13 +297,7 @@ impl ImportExportPen {
 
         let pen = pens.get(self.index).expect("Invalid pen index");
 
-        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
-        buffer.write_le(pen).expect("Failed to serialize pen");
-
-        let mut output: File = File::create(&self.path).expect("Failed to open output file");
-        output
-            .write_all(buffer.into_inner().as_slice())
-            .expect("Failed to write to output file");
+        pen.write_to_file(&self.path);
     }
 
     pub fn import(&self, pens: &mut Vec<Pen>) {
@@ -307,14 +307,7 @@ impl ImportExportPen {
             self.path.to_string_lossy()
         );
 
-        let mut input: File = File::open(&self.path).expect("Failed to open input file");
-
-        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
-        input
-            .read_to_end(buffer.get_mut())
-            .expect("Failed to read input file");
-
-        let pen: Pen = Pen::read_le(&mut buffer).expect("Failed to deserialize input as pen");
+        let pen: Pen = Pen::read_from_file(&self.path);
 
         let dst: &mut Pen = pens.get_mut(self.index).expect("Invalid pen index");
         *dst = pen;

+ 30 - 2
src/ezcad/pen.rs

@@ -1,6 +1,11 @@
-use std::fmt::Debug;
+use std::{
+    fmt::Debug,
+    fs::File,
+    io::{Cursor, Read, Write},
+    path::PathBuf,
+};
 
-use binrw::{BinRead, BinWrite, FilePtr64};
+use binrw::{BinRead, BinWrite, BinWriterExt, FilePtr64};
 use diff::Diff;
 
 use crate::{
@@ -92,6 +97,29 @@ pub struct Pen {
     _unknown_8: [Field; 26],
 }
 
+impl Pen {
+    pub fn read_from_file(path: &PathBuf) -> Self {
+        let mut input: File = File::open(path).expect("Failed to open input file");
+
+        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
+        input
+            .read_to_end(buffer.get_mut())
+            .expect("Failed to read input file");
+
+        Pen::read_le(&mut buffer).expect("Failed to deserialize input as object")
+    }
+
+    pub fn write_to_file(&self, path: &PathBuf) {
+        let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
+        buffer.write_le(self).expect("Failed to serialize object");
+
+        let mut output: File = File::create(path).expect("Failed to open output file");
+        output
+            .write_all(buffer.into_inner().as_slice())
+            .expect("Failed to write to output file");
+    }
+}
+
 // Custom Debug implementation to only print known fields
 #[cfg(not(feature = "default-debug"))]
 impl Debug for Pen {