Browse Source

Add support for patterning hatch settings

Kevin Lee 1 month ago
parent
commit
f2f0f28ed9
8 changed files with 548 additions and 87 deletions
  1. 152 25
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 23 5
      README.md
  4. 166 0
      src/config/hatch.rs
  5. 1 0
      src/config/mod.rs
  6. 156 22
      src/config/object.rs
  7. 29 29
      src/config/pen.rs
  8. 20 6
      src/main.rs

+ 152 - 25
Cargo.lock

@@ -95,6 +95,12 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
 [[package]]
 name = "bitflags"
 version = "2.4.1"
@@ -169,6 +175,32 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
+[[package]]
+name = "console"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "unicode-width",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
+dependencies = [
+ "console",
+ "shell-words",
+ "tempfile",
+ "thiserror",
+ "zeroize",
+]
+
 [[package]]
 name = "diff-struct"
 version = "0.5.3"
@@ -197,6 +229,12 @@ version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
 [[package]]
 name = "env_logger"
 version = "0.10.1"
@@ -226,6 +264,12 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
 [[package]]
 name = "getrandom"
 version = "0.2.11"
@@ -337,6 +381,7 @@ dependencies = [
  "binrw",
  "clap",
  "clap-verbosity-flag",
+ "dialoguer",
  "diff-struct",
  "env_logger",
  "human-repr",
@@ -471,6 +516,12 @@ dependencies = [
  "syn 2.0.48",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
 [[package]]
 name = "owo-colors"
 version = "3.5.0"
@@ -541,6 +592,15 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
 [[package]]
 name = "regex"
 version = "1.10.4"
@@ -576,7 +636,7 @@ version = "0.38.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
 dependencies = [
- "bitflags",
+ "bitflags 2.4.1",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -639,6 +699,12 @@ dependencies = [
  "unsafe-libyaml",
 ]
 
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -695,6 +761,19 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "tempfile"
+version = "3.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.4.0"
@@ -704,6 +783,26 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
 [[package]]
 name = "toml_datetime"
 version = "0.6.3"
@@ -727,6 +826,12 @@ version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 name = "unsafe-libyaml"
 version = "0.2.10"
@@ -791,7 +896,16 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.0",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -811,17 +925,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.0",
- "windows_aarch64_msvc 0.52.0",
- "windows_i686_gnu 0.52.0",
- "windows_i686_msvc 0.52.0",
- "windows_x86_64_gnu 0.52.0",
- "windows_x86_64_gnullvm 0.52.0",
- "windows_x86_64_msvc 0.52.0",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
@@ -832,9 +947,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -844,9 +959,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -856,9 +971,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -868,9 +989,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -880,9 +1001,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -892,9 +1013,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -904,9 +1025,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
@@ -916,3 +1037,9 @@ checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
 dependencies = [
  "memchr",
 ]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"

+ 1 - 0
Cargo.toml

@@ -17,6 +17,7 @@ path = "src/main.rs"
 binrw = "0.13.3"
 clap = { version = "4.4.11", features = ["derive"] }
 clap-verbosity-flag = "2.1.1"
+dialoguer = "0.11.0"
 diff-struct = "0.5.3"
 env_logger = "0.10.1"
 human-repr = { version = "1.1.0", features = ["serde"] }

+ 23 - 5
README.md

@@ -28,7 +28,7 @@ Options:
 
 ### Sub-Commands
 
-Diff two .mlp files and print differences between the two
+Diff two .mlp files and print differences between the two.
 
 ``` text
 Usage: minilase.exe --input <INPUT> diff [OPTIONS] --diff-file <DIFF_FILE>
@@ -40,7 +40,9 @@ Options:
   -h, --help                   Print help
 ```
 
-Queries input .mlp file for pen or object info
+Queries input .mlp file for pen or object info.
+
+`<Pen>` and `<Object>` supports indexes (`6`) and ranges (`..3`, `2..=3`, or `5..`).
 
 ``` text
 Usage: minilase.exe --input <INPUT> query [OPTIONS]
@@ -54,7 +56,7 @@ Options:
   -h, --help                         Print help
 ```
 
-Applies configuration YAML to input .mlp file
+Applies configuration YAML to input .mlp file.
 
 ``` text
 Usage: minilase.exe --input <INPUT> apply [OPTIONS] --config <CONFIG>
@@ -212,10 +214,26 @@ Array:
   Spacing: 1.0
   RandomizeOrder: True
   StartingPen: 1
-  PatternX: <PenPattern> // Optional
-  PatternY: <PenPattern> // Optional
+  PatternPenX: <PenPattern> // Optional
+  PatternPenY: <PenPattern> // Optional
+  PatternHatchX: <HatchPattern> // Optional
+  PatternHatchY: <HatchPattern> // Optional
 ```
 
+Where `<HatchPattern>` is one of the following:
+
+``` yaml
+Field: !Count 1
+Field: !LineSpacing 0.001
+Field: !EdgeOffset 0.1
+Field: !StartOffset 0.1
+Field: !EndOffset 0.1
+Field: !Angle 45.0
+Field: !RotateAngle 45.0
+Field: !LineReduction 1.0
+Field: !LoopDistance 1.0
+Field: !LoopCount 1
+```
 Where `<HatchOptions>` is:
 
 ``` yaml

+ 166 - 0
src/config/hatch.rs

@@ -0,0 +1,166 @@
+use ezcad::objects::{hatch::HatchSetting, Object};
+use log::debug;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize)]
+
+pub enum PatternHatchField {
+    Count(u32),
+    LineSpacing(f64),
+    EdgeOffset(f64),
+    StartOffset(f64),
+    EndOffset(f64),
+    Angle(f64),
+    RotateAngle(f64),
+    LineReduction(f64),
+    LoopDistance(f64),
+    LoopCount(u32),
+}
+
+impl PatternHatchField {
+    pub fn pattern(&self, objects: &mut dyn Iterator<Item = (usize, &mut Object)>) {
+        let (src_idx, src) = objects
+            .next()
+            .expect("Pattern must involve at least one pen");
+
+        // Obtain settings from first object
+        let mut setting: PatternHatchField = match src {
+            Object::Hatch(src) => {
+                let setting = src
+                    .hatch_settings
+                    .iter()
+                    .find(|h| h.enabled.into())
+                    .expect("Hatch missing enabled settings");
+
+                let setting: PatternHatchField = match self {
+                    PatternHatchField::Count(_) => {
+                        debug!("Initial hatch count from object #{} is {}", src_idx, *setting.count);
+                        PatternHatchField::Count(*setting.count)
+
+                    },
+                    PatternHatchField::LineSpacing(_) => {
+                        debug!("Initial hatch line spacing from object #{} is {}", src_idx, *setting.line_spacing);
+                        PatternHatchField::LineSpacing(*setting.line_spacing)
+                    },
+                    PatternHatchField::EdgeOffset(_) => {
+                        debug!("Initial hatch edge offset from object #{} is {}", src_idx, *setting.edge_offset);
+                        PatternHatchField::EdgeOffset(*setting.edge_offset)
+                    },
+                    PatternHatchField::StartOffset(_) => {
+                        debug!("Initial hatch start offset from object #{} is {}", src_idx, *setting.start_offset);
+                        PatternHatchField::StartOffset(*setting.start_offset)
+                    },
+                    PatternHatchField::EndOffset(_) => {
+                        debug!("Initial hatch end offset from object #{} is {}", src_idx, *setting.end_offset);
+                        PatternHatchField::EndOffset(*setting.end_offset)
+                    },
+                    PatternHatchField::Angle(_) => {
+                        debug!("Initial hatch angle from object #{} is {}", src_idx, *setting.angle);
+                        PatternHatchField::Angle(*setting.angle)
+                    },
+                    PatternHatchField::RotateAngle(_) => {
+                        debug!("Initial hatch rotate angle from object #{} is {}", src_idx, *setting.rotate_angle);
+                        PatternHatchField::RotateAngle(*setting.rotate_angle)
+                    },
+                    PatternHatchField::LineReduction(_) => {
+                        debug!("Initial hatch line reduction angle from object #{} is {}", src_idx, *setting.line_reduction);
+                        PatternHatchField::LineReduction(*setting.line_reduction)
+                    },
+                    PatternHatchField::LoopDistance(_) => {
+                        debug!("Initial hatch loop distance from object #{} is {}", src_idx, *setting.loop_distance);
+                        PatternHatchField::LoopDistance(*setting.loop_distance)
+                    },
+                    PatternHatchField::LoopCount(_) => {
+                        debug!("Initial hatch loop count from object #{} is {}", src_idx, *setting.loop_count);
+                        PatternHatchField::LoopCount(*setting.loop_count)
+                    },
+                };
+
+                setting
+            }
+            _ => panic!("Object #{} not a hatch object", src_idx)
+        };
+
+        for (idx, dst) in objects {
+            // Calculate new setting
+            setting = match (setting, self) {
+                (PatternHatchField::Count(prev), PatternHatchField::Count(incr)) => {
+                    let value: u32 = prev + incr;
+                    debug!("Patching hatch count for object #{} to {}", idx, value);
+                    PatternHatchField::Count(value)
+                }
+                (PatternHatchField::LineSpacing(prev), PatternHatchField::LineSpacing(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch line spacing for object #{} to {}", idx, value);
+                    PatternHatchField::LineSpacing(value)
+                }
+                (PatternHatchField::EdgeOffset(prev), PatternHatchField::EdgeOffset(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch edge offset for object #{} to {}", idx, value);
+                    PatternHatchField::EdgeOffset(value)
+                }
+                (PatternHatchField::StartOffset(prev), PatternHatchField::StartOffset(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch start offset for object #{} to {}", idx, value);
+                    PatternHatchField::StartOffset(value)
+                }
+                (PatternHatchField::EndOffset(prev), PatternHatchField::LineSpacing(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch end offset for object #{} to {}", idx, value);
+                    PatternHatchField::EndOffset(value)
+                }
+                (PatternHatchField::Angle(prev), PatternHatchField::Angle(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch angle for object #{} to {}", idx, value);
+                    PatternHatchField::Angle(value)
+                }
+                (PatternHatchField::RotateAngle(prev), PatternHatchField::RotateAngle(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch rotate angle for object #{} to {}", idx, value);
+                    PatternHatchField::RotateAngle(value)
+                }
+                (PatternHatchField::LineReduction(prev), PatternHatchField::LineReduction(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch line reduction for object #{} to {}", idx, value);
+                    PatternHatchField::LineReduction(value)
+                }
+                (PatternHatchField::LoopDistance(prev), PatternHatchField::LoopDistance(incr)) => {
+                    let value: f64 = prev + incr;
+                    debug!("Patching hatch loop distance for object #{} to {}", idx, value);
+                    PatternHatchField::LoopDistance(value)
+                }
+                (PatternHatchField::LoopCount(prev), PatternHatchField::LoopCount(incr)) => {
+                    let value: u32 = prev + incr;
+                    debug!("Patching hatch loop count for object #{} to {}", idx, value);
+                    PatternHatchField::LoopCount(value)
+                }
+                _ => unreachable!(),
+            };
+
+            // Apply setting
+            match dst {
+                Object::Hatch(dst) => {
+                    let dst: &mut HatchSetting = dst
+                        .hatch_settings
+                        .iter_mut()
+                        .find(|h| h.enabled.into())
+                        .expect("Hatch missing enabled settings");
+
+                    match setting {
+                        PatternHatchField::Count(x) => *dst.count = x,
+                        PatternHatchField::LineSpacing(x) => *dst.line_spacing = x,
+                        PatternHatchField::EdgeOffset(x) => *dst.edge_offset = x,
+                        PatternHatchField::StartOffset(x) => *dst.start_offset = x,
+                        PatternHatchField::EndOffset(x) => *dst.end_offset = x,
+                        PatternHatchField::Angle(x) => *dst.angle = x,
+                        PatternHatchField::RotateAngle(x) => *dst.rotate_angle = x,
+                        PatternHatchField::LineReduction(x) => *dst.line_reduction = x,
+                        PatternHatchField::LoopDistance(x) => *dst.loop_distance = x,
+                        PatternHatchField::LoopCount(x) => *dst.loop_count = x,
+                    }
+                },
+                _ => panic!("Object #{} not a hatch object", src_idx)
+            }
+        }
+    }
+}

+ 1 - 0
src/config/mod.rs

@@ -6,6 +6,7 @@ use self::{
     pen::{ClonePen, ImportExportPen, PatchPen, PatternPen, RandomizePen},
 };
 
+pub mod hatch;
 pub mod object;
 pub mod pen;
 

+ 156 - 22
src/config/object.rs

@@ -16,7 +16,7 @@ use log::{debug, error, warn};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
 
-use super::pen::PatternField;
+use super::{hatch::PatternHatchField, pen::PatternPenField};
 
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
@@ -114,8 +114,10 @@ pub struct ArrayConfig {
     spacing: f64,
     randomize_order: bool,
     starting_pen: usize,
-    pattern_x: Option<PatternField>,
-    pattern_y: Option<PatternField>,
+    pattern_pen_x: Option<PatternPenField>,
+    pattern_pen_y: Option<PatternPenField>,
+    pattern_hatch_x: Option<PatternHatchField>,
+    pattern_hatch_y: Option<PatternHatchField>,
 }
 
 #[derive(Debug, Serialize, Deserialize, strum::Display)]
@@ -292,22 +294,76 @@ impl ObjectOperation {
                     }
                 }
 
-                // Generate pens
-                match &array.pattern_y {
-                    None => {
-                        if let Some(pattern_x) = &array.pattern_x {
-                            pattern_x.pattern(
+                match (&array.pattern_hatch_y, &array.pattern_hatch_x, &array.pattern_pen_y, &array.pattern_pen_x) {
+                    (None, None, None, None) => (),
+                    (Some(hatch_y), Some(hatch_x), None, None) => {
+                        for x in 0..array.columns {
+                            hatch_y.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
+                        }
+                        for y in 0..array.rows {
+                            hatch_x.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
+                        }
+                    }
+                    (None, None, Some(pen_y), Some(pen_x)) => {
+                        for x in 0..array.columns {
+                            pen_y.pattern(
+                                &mut pens
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x + array.starting_pen)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
+                        }
+                        for y in 0..array.rows {
+                            pen_x.pattern(
                                 &mut pens
                                     .iter_mut()
                                     .enumerate()
                                     .skip(array.starting_pen)
-                                    .take(array.columns * array.rows),
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
+                        }
+                    }
+                    (Some(hatch_y), None, None, Some(pen_x)) => {
+                        for x in 0..array.columns {
+                            hatch_y.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
                             );
                         }
+                        for y in 0..array.rows {
+                            pen_x.pattern(
+                                &mut pens
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(array.starting_pen)
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
+                        }
                     }
-                    Some(pattern_y) => {
+                    (None, Some(hatch_x), Some(pen_y), None) => {
                         for x in 0..array.columns {
-                            pattern_y.pattern(
+                            pen_y.pattern(
                                 &mut pens
                                     .iter_mut()
                                     .enumerate()
@@ -316,21 +372,99 @@ impl ObjectOperation {
                                     .take(array.rows),
                             );
                         }
-                        if let Some(pattern_x) = &array.pattern_x {
-                            for y in 0..array.rows {
-                                pattern_x.pattern(
-                                    &mut pens
-                                        .iter_mut()
-                                        .enumerate()
-                                        .skip(array.starting_pen)
-                                        .skip(y * array.columns)
-                                        .take(array.columns),
-                                )
-                            }
+                        for y in 0..array.rows {
+                            hatch_x.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
+                        }
+                    }
+                    (Some(hatch_y), None, None, None) => {
+                        for x in 0..array.columns {
+                            hatch_y.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
+                        }
+                    }
+                    (None, Some(hatch_x), None, None) => {
+                        hatch_x.pattern(
+                            &mut new_obj
+                                .iter_mut()
+                                .enumerate()
+                                .take(array.columns * array.rows),
+                        );
+                    }
+                    (None, None, Some(pen_y), None) => {
+                        for x in 0..array.columns {
+                            pen_y.pattern(
+                                &mut pens
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x + array.starting_pen)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
                         }
                     }
+                    (None, None, None, Some(pen_x)) => {
+                        pen_x.pattern(
+                            &mut pens
+                                .iter_mut()
+                                .enumerate()
+                                .skip(array.starting_pen)
+                                .take(array.columns * array.rows),
+                        );
+                    }
+                    _ => panic!("Unsupported combination of patterning X and Y")
                 }
 
+                // // Generate pens
+                // match &array.pattern_pen_y {
+                //     None => {
+                //         if let Some(pen_x) = &array.pattern_pen_x {
+                //             pen_x.pattern(
+                //                 &mut pens
+                //                     .iter_mut()
+                //                     .enumerate()
+                //                     .skip(array.starting_pen)
+                //                     .take(array.columns * array.rows),
+                //             );
+                //         }
+                //     }
+                //     Some(pen_y) => {
+                //         for x in 0..array.columns {
+                //             pen_y.pattern(
+                //                 &mut pens
+                //                     .iter_mut()
+                //                     .enumerate()
+                //                     .skip(x + array.starting_pen)
+                //                     .step_by(array.columns)
+                //                     .take(array.rows),
+                //             );
+                //         }
+                //         if let Some(pen_x) = &array.pattern_pen_x {
+                //             for y in 0..array.rows {
+                //                 pen_x.pattern(
+                //                     &mut pens
+                //                         .iter_mut()
+                //                         .enumerate()
+                //                         .skip(array.starting_pen)
+                //                         .skip(y * array.columns)
+                //                         .take(array.columns),
+                //                 )
+                //             }
+                //         }
+                //     }
+                // }
+
                 if array.randomize_order {
                     debug!("Randomizing draw order of array objects");
                     new_obj.shuffle(&mut thread_rng());

+ 29 - 29
src/config/pen.rs

@@ -169,7 +169,7 @@ impl ClonePen {
 }
 
 #[derive(Debug, Serialize, Deserialize)]
-pub enum PatternField {
+pub enum PatternPenField {
     Loops(i32),
     Speed(f64),
     Power(f64),
@@ -177,53 +177,53 @@ pub enum PatternField {
     PulseWidth(u32),
 }
 
-impl PatternField {
+impl PatternPenField {
     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");
 
-        let mut setting: PatternField = match self {
-            PatternField::Loops(_) => {
+        let mut setting: PatternPenField = match self {
+            PatternPenField::Loops(_) => {
                 debug!(
                     "Initial loop count from pen #{} is {}",
                     src_idx, *src.loop_count
                 );
-                PatternField::Loops((*src.loop_count).try_into().unwrap())
+                PatternPenField::Loops((*src.loop_count).try_into().unwrap())
             }
-            PatternField::Speed(_) => {
+            PatternPenField::Speed(_) => {
                 debug!("Initial speed from pen #{} is {}", src_idx, *src.speed);
-                PatternField::Speed(*src.speed)
+                PatternPenField::Speed(*src.speed)
             }
-            PatternField::Power(_) => {
+            PatternPenField::Power(_) => {
                 debug!("Initial power from pen #{} is {}", src_idx, *src.power);
-                PatternField::Power(*src.power)
+                PatternPenField::Power(*src.power)
             }
-            PatternField::Frequency(_) => {
+            PatternPenField::Frequency(_) => {
                 debug!(
                     "Initial frequency from pen #{} is {}",
                     src_idx, *src.frequency
                 );
-                PatternField::Frequency((*src.frequency).try_into().unwrap())
+                PatternPenField::Frequency((*src.frequency).try_into().unwrap())
             }
-            PatternField::PulseWidth(_) => {
+            PatternPenField::PulseWidth(_) => {
                 debug!(
                     "Initial pulse width from pen #{} is {}ns",
                     src_idx, *src.pulse_width
                 );
-                PatternField::PulseWidth(*src.pulse_width)
+                PatternPenField::PulseWidth(*src.pulse_width)
             }
         };
 
         for (idx, dst) in pens {
             // Calculate new setting
             setting = match (setting, self) {
-                (PatternField::Loops(prev), PatternField::Loops(incr)) => {
+                (PatternPenField::Loops(prev), PatternPenField::Loops(incr)) => {
                     let value: i32 = prev + incr;
                     debug!("Patching loop count for pen #{} to {}", idx, value);
                     assert!(value > 0, "Pen loop count must be greater than zero");
-                    PatternField::Loops(value)
+                    PatternPenField::Loops(value)
                 }
-                (PatternField::Speed(prev), PatternField::Speed(incr)) => {
+                (PatternPenField::Speed(prev), PatternPenField::Speed(incr)) => {
                     let value: f64 = prev + incr;
                     debug!("Patching speed for pen #{} to {}", idx, value);
                     assert!(
@@ -232,9 +232,9 @@ impl PatternField {
                         SPEED_MIN,
                         SPEED_MAX
                     );
-                    PatternField::Speed(value)
+                    PatternPenField::Speed(value)
                 }
-                (PatternField::Power(prev), PatternField::Power(incr)) => {
+                (PatternPenField::Power(prev), PatternPenField::Power(incr)) => {
                     let value: f64 = prev + incr;
                     debug!("Patching power for pen #{} to {}", idx, value);
                     assert!(
@@ -243,9 +243,9 @@ impl PatternField {
                         POWER_MIN,
                         POWER_MAX
                     );
-                    PatternField::Power(value)
+                    PatternPenField::Power(value)
                 }
-                (PatternField::Frequency(prev), PatternField::Frequency(incr)) => {
+                (PatternPenField::Frequency(prev), PatternPenField::Frequency(incr)) => {
                     let value: i32 = prev + incr;
                     debug!("Patching frequency for pen #{} to {}", idx, value);
                     assert!(
@@ -255,9 +255,9 @@ impl PatternField {
                         FREQUENCY_MIN,
                         FREQUENCY_MAX
                     );
-                    PatternField::Frequency(value)
+                    PatternPenField::Frequency(value)
                 }
-                (PatternField::PulseWidth(prev), PatternField::PulseWidth(incr)) => {
+                (PatternPenField::PulseWidth(prev), PatternPenField::PulseWidth(incr)) => {
                     let mut pw = PulseWidth::iter();
                     let _ = pw
                         .find(|x| u32::from(*x) == prev)
@@ -266,21 +266,21 @@ impl PatternField {
                     let mut pw = pw.skip((*incr - 1).try_into().unwrap());
                     let next: u32 = pw.next().expect("Pulse width out of bounds").into();
                     debug!("Patching pulse width for pen #{} to {}ns", idx, next);
-                    PatternField::PulseWidth(next)
+                    PatternPenField::PulseWidth(next)
                 }
                 _ => unreachable!(),
             };
 
             // Patch updated value
             match setting {
-                PatternField::Loops(x) => *dst.loop_count = x.try_into().unwrap(),
-                PatternField::Speed(x) => *dst.speed = x,
-                PatternField::Power(x) => *dst.power = x,
-                PatternField::Frequency(x) => {
+                PatternPenField::Loops(x) => *dst.loop_count = x.try_into().unwrap(),
+                PatternPenField::Speed(x) => *dst.speed = x,
+                PatternPenField::Power(x) => *dst.power = x,
+                PatternPenField::Frequency(x) => {
                     *dst.frequency = x.try_into().unwrap();
                     *dst.frequency_2 = x.try_into().unwrap();
                 }
-                PatternField::PulseWidth(x) => {
+                PatternPenField::PulseWidth(x) => {
                     *dst.pulse_width = x;
                     *dst.pulse_width_2 = x.try_into().unwrap();
                 }
@@ -302,7 +302,7 @@ impl PatternField {
 pub struct PatternPen {
     index: usize,
     count: usize,
-    field: PatternField,
+    field: PatternPenField,
 }
 
 impl PatternPen {

+ 20 - 6
src/main.rs

@@ -2,13 +2,14 @@ use std::{
     fs::File,
     io::{Cursor, Read, Write},
     ops::RangeInclusive,
-    path::PathBuf,
+    path::{Path, PathBuf},
     time::Instant,
 };
 
 use binrw::{BinRead, BinWrite, BinWriterExt};
 use clap::{error::ErrorKind, Args, Error, Parser, Subcommand};
 use clap_verbosity_flag::{InfoLevel, Verbosity};
+use dialoguer::Confirm;
 use diff::Diff;
 use env_logger::Target;
 use ezcad::{file::EzCadHeader, layer::Layer, objects::Object};
@@ -268,7 +269,6 @@ fn main() {
 
             // Process output
             args.output.map(|output| {
-                info!("Writing output file '{}'", output.to_string_lossy());
                 // Serialize to memory buffer for perf
                 let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
                 let time: Instant = Instant::now();
@@ -278,10 +278,24 @@ fn main() {
                 trace!("Output file encode time: {:?}", time.elapsed());
 
                 // Write buffer to output file
-                let mut output: File = File::create(output).expect("Failed to open output file");
-                output
-                    .write_all(buffer.into_inner().as_slice())
-                    .expect("Failed to write to output file");
+                if !Path::new(&output).exists()
+                    || Confirm::new()
+                        .with_prompt(format!(
+                            "File '{}' exists! Overwrite?",
+                            &output.to_string_lossy()
+                        ))
+                        .interact()
+                        .unwrap()
+                {
+                    info!("Writing output file '{}'", output.to_string_lossy());
+                    let mut output: File =
+                        File::create(output).expect("Failed to open output file");
+                    output
+                        .write_all(buffer.into_inner().as_slice())
+                        .expect("Failed to write to output file");
+                } else {
+                    warn!("Skipping write due to file conflict");
+                }
             });
         }
     }