16 次代码提交 8db860afbd ... 4401ff1da2

作者 SHA1 备注 提交日期
  Kevin Lee 4401ff1da2 Object modify and toggle limit check 9 月之前
  Kevin Lee 0e87759622 Add power density to pen query 9 月之前
  Kevin Lee 41a76c99ab Add density range to randomization 9 月之前
  Kevin Lee 86119b86b7 Refactor power density as Hatch fn 9 月之前
  Kevin Lee 3680f61a82 Cleanup 9 月之前
  Kevin Lee 07b1a7423f F64 compare and support for layer queries 9 月之前
  Kevin Lee 6daafb8b80 Add query hatch power 9 月之前
  Kevin Lee cc43d402b2 Add measured pulse power 9 月之前
  Kevin Lee 844bd4781c Update test 10 月之前
  Kevin Lee 122ab2a3fa Use non-iterator version of WindowMut 10 月之前
  Kevin Lee fb16f06b97 Update README 10 月之前
  Kevin Lee c34caeed08 Pattern X then Y for consistency 10 月之前
  Kevin Lee 21228ffce4 Minor refactor 10 月之前
  Kevin Lee 9f14e7e60e Add param to force overwrite 10 月之前
  Kevin Lee b65076bff1 Refactor and print f64 as {:.3} 10 月之前
  Kevin Lee f2f0f28ed9 Add support for patterning hatch settings 10 月之前
共有 36 个文件被更改,包括 916 次插入259 次删除
  1. 二进制
      1mmRect.mlp
  2. 二进制
      2mmRect.mlp
  3. 二进制
      2mmRectArray.mlp
  4. 179 25
      Cargo.lock
  5. 3 0
      Cargo.toml
  6. 二进制
      Data.xlsx
  7. 二进制
      Pulse Width.png
  8. 57 8
      README.md
  9. 38 0
      config.yml
  10. 二进制
      export.bin
  11. 二进制
      samples/Circle2.mlp
  12. 二进制
      samples/Circle3.mlp
  13. 二进制
      samples/Circle4.mlp
  14. 二进制
      samples/Circle5.mlp
  15. 二进制
      samples/Ellipse.mlp
  16. 二进制
      samples/Rectangle2.mlp
  17. 二进制
      samples/Rectangle3.mlp
  18. 二进制
      samples/Rectangle4.mlp
  19. 二进制
      samples/RectangleSkewRightUpTopLeft.mlp
  20. 130 0
      src/config/hatch.rs
  21. 22 0
      src/config/mod.rs
  22. 121 41
      src/config/object.rs
  23. 67 91
      src/config/pen.rs
  24. 4 4
      src/ezcad/objects/circle.rs
  25. 4 3
      src/ezcad/objects/ellipse.rs
  26. 21 2
      src/ezcad/objects/hatch.rs
  27. 2 1
      src/ezcad/objects/mod.rs
  28. 4 3
      src/ezcad/objects/polygon.rs
  29. 4 3
      src/ezcad/objects/rectangle.rs
  30. 34 15
      src/ezcad/pen.rs
  31. 34 5
      src/ezcad/types.rs
  32. 6 3
      src/lib.rs
  33. 95 55
      src/main.rs
  34. 二进制
      test.mlp
  35. 二进制
      test2.mlp
  36. 91 0
      test2.yml

二进制
1mmRect.mlp


二进制
2mmRect.mlp


二进制
2mmRectArray.mlp


+ 179 - 25
Cargo.lock

@@ -65,6 +65,12 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
 checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
 
 
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
 [[package]]
 [[package]]
 name = "autocfg"
 name = "autocfg"
 version = "1.1.0"
 version = "1.1.0"
@@ -95,6 +101,12 @@ dependencies = [
  "syn 1.0.109",
  "syn 1.0.109",
 ]
 ]
 
 
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
 [[package]]
 [[package]]
 name = "bitflags"
 name = "bitflags"
 version = "2.4.1"
 version = "2.4.1"
@@ -169,6 +181,32 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 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]]
 [[package]]
 name = "diff-struct"
 name = "diff-struct"
 version = "0.5.3"
 version = "0.5.3"
@@ -197,6 +235,12 @@ version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
 
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
 [[package]]
 [[package]]
 name = "env_logger"
 name = "env_logger"
 version = "0.10.1"
 version = "0.10.1"
@@ -226,6 +270,21 @@ dependencies = [
  "windows-sys 0.52.0",
  "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 = "float-cmp"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
+dependencies = [
+ "num-traits",
+]
+
 [[package]]
 [[package]]
 name = "getrandom"
 name = "getrandom"
 version = "0.2.11"
 version = "0.2.11"
@@ -337,13 +396,16 @@ dependencies = [
  "binrw",
  "binrw",
  "clap",
  "clap",
  "clap-verbosity-flag",
  "clap-verbosity-flag",
+ "dialoguer",
  "diff-struct",
  "diff-struct",
  "env_logger",
  "env_logger",
+ "float-cmp",
  "human-repr",
  "human-repr",
  "itertools",
  "itertools",
  "log",
  "log",
  "modular-bitfield",
  "modular-bitfield",
  "num",
  "num",
+ "num-format",
  "num_enum",
  "num_enum",
  "rand",
  "rand",
  "regex",
  "regex",
@@ -408,6 +470,16 @@ dependencies = [
  "num-traits",
  "num-traits",
 ]
 ]
 
 
+[[package]]
+name = "num-format"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
+dependencies = [
+ "arrayvec",
+ "itoa",
+]
+
 [[package]]
 [[package]]
 name = "num-integer"
 name = "num-integer"
 version = "0.1.45"
 version = "0.1.45"
@@ -471,6 +543,12 @@ dependencies = [
  "syn 2.0.48",
  "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]]
 [[package]]
 name = "owo-colors"
 name = "owo-colors"
 version = "3.5.0"
 version = "3.5.0"
@@ -541,6 +619,15 @@ dependencies = [
  "getrandom",
  "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]]
 [[package]]
 name = "regex"
 name = "regex"
 version = "1.10.4"
 version = "1.10.4"
@@ -576,7 +663,7 @@ version = "0.38.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
 checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
 dependencies = [
 dependencies = [
- "bitflags",
+ "bitflags 2.4.1",
  "errno",
  "errno",
  "libc",
  "libc",
  "linux-raw-sys",
  "linux-raw-sys",
@@ -639,6 +726,12 @@ dependencies = [
  "unsafe-libyaml",
  "unsafe-libyaml",
 ]
 ]
 
 
+[[package]]
+name = "shell-words"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
+
 [[package]]
 [[package]]
 name = "static_assertions"
 name = "static_assertions"
 version = "1.1.0"
 version = "1.1.0"
@@ -695,6 +788,19 @@ dependencies = [
  "unicode-ident",
  "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]]
 [[package]]
 name = "termcolor"
 name = "termcolor"
 version = "1.4.0"
 version = "1.4.0"
@@ -704,6 +810,26 @@ dependencies = [
  "winapi-util",
  "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]]
 [[package]]
 name = "toml_datetime"
 name = "toml_datetime"
 version = "0.6.3"
 version = "0.6.3"
@@ -727,6 +853,12 @@ version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 [[package]]
 name = "unsafe-libyaml"
 name = "unsafe-libyaml"
 version = "0.2.10"
 version = "0.2.10"
@@ -791,7 +923,16 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
 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]]
 [[package]]
@@ -811,17 +952,18 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "windows-targets"
 name = "windows-targets"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
 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]]
 [[package]]
@@ -832,9 +974,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 
 [[package]]
 [[package]]
 name = "windows_aarch64_gnullvm"
 name = "windows_aarch64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 
 [[package]]
 [[package]]
 name = "windows_aarch64_msvc"
 name = "windows_aarch64_msvc"
@@ -844,9 +986,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 
 [[package]]
 [[package]]
 name = "windows_aarch64_msvc"
 name = "windows_aarch64_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 
 [[package]]
 [[package]]
 name = "windows_i686_gnu"
 name = "windows_i686_gnu"
@@ -856,9 +998,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 
 [[package]]
 [[package]]
 name = "windows_i686_gnu"
 name = "windows_i686_gnu"
-version = "0.52.0"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 
 [[package]]
 [[package]]
 name = "windows_i686_msvc"
 name = "windows_i686_msvc"
@@ -868,9 +1016,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 
 [[package]]
 [[package]]
 name = "windows_i686_msvc"
 name = "windows_i686_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_gnu"
 name = "windows_x86_64_gnu"
@@ -880,9 +1028,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_gnu"
 name = "windows_x86_64_gnu"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_gnullvm"
 name = "windows_x86_64_gnullvm"
@@ -892,9 +1040,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_gnullvm"
 name = "windows_x86_64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_msvc"
 name = "windows_x86_64_msvc"
@@ -904,9 +1052,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 
 [[package]]
 [[package]]
 name = "windows_x86_64_msvc"
 name = "windows_x86_64_msvc"
-version = "0.52.0"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 
 [[package]]
 [[package]]
 name = "winnow"
 name = "winnow"
@@ -916,3 +1064,9 @@ checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
 dependencies = [
 dependencies = [
  "memchr",
  "memchr",
 ]
 ]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"

+ 3 - 0
Cargo.toml

@@ -17,13 +17,16 @@ path = "src/main.rs"
 binrw = "0.13.3"
 binrw = "0.13.3"
 clap = { version = "4.4.11", features = ["derive"] }
 clap = { version = "4.4.11", features = ["derive"] }
 clap-verbosity-flag = "2.1.1"
 clap-verbosity-flag = "2.1.1"
+dialoguer = "0.11.0"
 diff-struct = "0.5.3"
 diff-struct = "0.5.3"
 env_logger = "0.10.1"
 env_logger = "0.10.1"
+float-cmp = "0.10.0"
 human-repr = { version = "1.1.0", features = ["serde"] }
 human-repr = { version = "1.1.0", features = ["serde"] }
 itertools = "0.12.0"
 itertools = "0.12.0"
 log = "0.4.20"
 log = "0.4.20"
 modular-bitfield = "0.11.2"
 modular-bitfield = "0.11.2"
 num = "0.4.1"
 num = "0.4.1"
+num-format = "0.4.4"
 num_enum = "0.7.1"
 num_enum = "0.7.1"
 rand = "0.8.5"
 rand = "0.8.5"
 regex = "1.10.4"
 regex = "1.10.4"

二进制
Data.xlsx


二进制
Pulse Width.png


+ 57 - 8
README.md

@@ -28,7 +28,7 @@ Options:
 
 
 ### Sub-Commands
 ### Sub-Commands
 
 
-Diff two .mlp files and print differences between the two
+Diff two .mlp files and print differences between the two.
 
 
 ``` text
 ``` text
 Usage: minilase.exe --input <INPUT> diff [OPTIONS] --diff-file <DIFF_FILE>
 Usage: minilase.exe --input <INPUT> diff [OPTIONS] --diff-file <DIFF_FILE>
@@ -40,7 +40,9 @@ Options:
   -h, --help                   Print help
   -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
 ``` text
 Usage: minilase.exe --input <INPUT> query [OPTIONS]
 Usage: minilase.exe --input <INPUT> query [OPTIONS]
@@ -54,7 +56,7 @@ Options:
   -h, --help                         Print help
   -h, --help                         Print help
 ```
 ```
 
 
-Applies configuration YAML to input .mlp file
+Applies configuration YAML to input .mlp file.
 
 
 ``` text
 ``` text
 Usage: minilase.exe --input <INPUT> apply [OPTIONS] --config <CONFIG>
 Usage: minilase.exe --input <INPUT> apply [OPTIONS] --config <CONFIG>
@@ -62,6 +64,7 @@ Usage: minilase.exe --input <INPUT> apply [OPTIONS] --config <CONFIG>
 Options:
 Options:
   -c, --config <CONFIG>  Configuration file
   -c, --config <CONFIG>  Configuration file
   -o, --output <OUTPUT>  Output file to write to
   -o, --output <OUTPUT>  Output file to write to
+  -w, --overwrite        Overwrite output file if it exists
   -v, --verbose...       Increase logging verbosity
   -v, --verbose...       Increase logging verbosity
   -q, --quiet...         Decrease logging verbosity
   -q, --quiet...         Decrease logging verbosity
   -h, --help             Print help
   -h, --help             Print help
@@ -71,6 +74,34 @@ Options:
 
 
 Operations defined in the configuration file are applied in order of definition.
 Operations defined in the configuration file are applied in order of definition.
 
 
+## Machine Parameters
+
+`<PulseWidth>` can be 2/4/6/8/12/20/30/45/60/80/100/150/200/250/350/500.
+
+Note: each pulse width has a minimum and maximum frequency for model YDFLP-20-M7-S-R:
+``` text
+2ns => 850kHz to 4MHz
+4ns => 500kHz to 4MHz
+6ns => 320kHz to 4MHz
+8ns => 250kHz to 4MHz
+12ns => 170kHz to 3MHz
+20ns => 115kHz to 3MHz
+30ns => 90kHz to 3MHz
+45ns => 75kHz to 2MHz
+60ns => 65kHz to 2MHz
+80ns => 60kHz to 2MHz
+100ns => 45kHz to 1MHz
+150ns => 30kHz to 1MHz
+200ns => 25kHz to 1MHz
+250ns => 25kHz to 900kHz
+350ns => 25kHz to 600kHz
+500ns => 25kHz to 500kHz
+```
+
+Power density of a hatch pattern is estimated using the following formula:
+
+Power density = 1/line spacing * power * 1/speed * frequency * pulse width power
+
 ### Pen Operations
 ### Pen Operations
 
 
 Setting values of specific fields for a given pen:
 Setting values of specific fields for a given pen:
@@ -78,7 +109,7 @@ Setting values of specific fields for a given pen:
 ``` yaml
 ``` yaml
 Ops: 
 Ops: 
   - !PatchPen
   - !PatchPen
-    Pen: 0                  # Target pen
+    Pen: 0 # Target pen
 
 
     # Specify one or more of the following:
     # Specify one or more of the following:
     Color: [127, 127, 127]
     Color: [127, 127, 127]
@@ -90,8 +121,6 @@ Ops:
     PulseWidth: <PulseWidth>
     PulseWidth: <PulseWidth>
 ```
 ```
 
 
-Where `<PulseWidth>` can be 2/4/6/8/12/20/30/45/60/80/100/150/200/250/350/500.
-
 Cloning pen(s) (and optionally override settings):
 Cloning pen(s) (and optionally override settings):
 
 
 ``` yaml
 ``` yaml
@@ -145,6 +174,10 @@ Op:
     Power: [10, 100, 5] # [min, max, step]
     Power: [10, 100, 5] # [min, max, step]
     Frequency: [20000, 100000, 1000] # [min, max, step]
     Frequency: [20000, 100000, 1000] # [min, max, step]
     PulseWidth: [2, 350, 2] # [min, max, step]
     PulseWidth: [2, 350, 2] # [min, max, step]
+
+    PowerDensity: [10000, 1000000] # [min, max]
+
+    EnforceLimits: bool # Enforces pulse width / frequency limitations
 ```
 ```
 
 
 Exporting a pen to a file:
 Exporting a pen to a file:
@@ -212,10 +245,26 @@ Array:
   Spacing: 1.0
   Spacing: 1.0
   RandomizeOrder: True
   RandomizeOrder: True
   StartingPen: 1
   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:
 Where `<HatchOptions>` is:
 
 
 ``` yaml
 ``` yaml

+ 38 - 0
config.yml

@@ -0,0 +1,38 @@
+Ops: 
+  - !PatchPen
+    Pen: 0
+    Speed: 2000
+    Frequency: 60000
+    Power: 20.0
+    PulseWidth: 80
+
+  - !ClonePen
+    From: 0
+    To: 255
+    Inclusive: true
+
+  - !RandomizePen
+    Index: 0
+    Count: 256
+    Speed: [1000, 10000, 500]
+    Power: [10, 30, 10]
+    Frequency: [10000, 200000, 1000]
+    PulseWidth: [2, 350, 2]
+    PowerDensity: [1000, 100000000]
+    EnforceLimits: true
+
+  - !Object
+    Input: !Existing { Layer: 0, Object: 0 }
+    Modify:
+      Z: 4.5
+    Array:
+      Columns: 3
+      Rows: 3
+      Spacing: 2.0
+      RandomizeOrder: True
+      StartingPen: 0
+      # PatternPenX: !Speed 100
+      # PatternPenY: !Power 5
+      # PatternHatchX: !LineSpacing 0.01
+      #PatternHatchY: !Count 1
+    ReplaceObject: 0

二进制
export.bin


二进制
samples/Circle2.mlp


二进制
samples/Circle3.mlp


二进制
samples/Circle4.mlp


二进制
samples/Circle5.mlp


二进制
samples/Ellipse.mlp


二进制
samples/Rectangle2.mlp


二进制
samples/Rectangle3.mlp


二进制
samples/Rectangle4.mlp


二进制
samples/RectangleSkewRightUpTopLeft.mlp


+ 130 - 0
src/config/hatch.rs

@@ -0,0 +1,130 @@
+use ezcad::{
+    objects::{hatch::HatchSetting, Object},
+    FP,
+};
+use itertools::Itertools;
+use log::debug;
+use serde::{Deserialize, Serialize};
+
+use super::double_window_mut;
+
+#[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 mut objects: Vec<(usize, &mut Object)> = objects.collect_vec();
+
+        double_window_mut(&mut objects[..], |prev, next| {
+            let (_prev_idx, prev): (usize, &mut HatchSetting) = match prev.1 {
+                Object::Hatch(hatch) => (
+                    prev.0,
+                    hatch
+                        .hatch_settings
+                        .iter_mut()
+                        .find(|h| h.enabled.into())
+                        .expect("Hatch missing enabled setting"),
+                ),
+                _ => panic!("Object #{} not a hatch object", prev.0),
+            };
+
+            let (next_idx, next): (usize, &mut HatchSetting) = match next.1 {
+                Object::Hatch(hatch) => (
+                    next.0,
+                    hatch
+                        .hatch_settings
+                        .iter_mut()
+                        .find(|h| h.enabled.into())
+                        .expect("Hatch missing enabled setting"),
+                ),
+                _ => panic!("Object #{} not a hatch object", next.0),
+            };
+
+            match self {
+                PatternHatchField::Count(incr) => {
+                    let value: u32 = *prev.count + incr;
+                    debug!("Patching hatch count for object #{} to {}", next_idx, value);
+                    *next.count = value;
+                }
+                PatternHatchField::LineSpacing(incr) => {
+                    let value: f64 = *prev.line_spacing + incr;
+                    debug!(
+                        "Patching line spacing for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.line_spacing = value;
+                }
+                PatternHatchField::EdgeOffset(incr) => {
+                    let value: f64 = *prev.edge_offset + incr;
+                    debug!(
+                        "Patching edge offset for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.edge_offset = value;
+                }
+                PatternHatchField::StartOffset(incr) => {
+                    let value: f64 = *prev.start_offset + incr;
+                    debug!(
+                        "Patching start offset for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.start_offset = value;
+                }
+                PatternHatchField::EndOffset(incr) => {
+                    let value: f64 = *prev.end_offset + incr;
+                    debug!(
+                        "Patching end offset for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.end_offset = value;
+                }
+                PatternHatchField::Angle(incr) => {
+                    let value: f64 = *prev.angle + incr;
+                    debug!("Patching angle for object #{} to {:.FP$}", next_idx, value);
+                    *next.angle = value;
+                }
+                PatternHatchField::RotateAngle(incr) => {
+                    let value: f64 = *prev.rotate_angle + incr;
+                    debug!(
+                        "Patching rotate angle for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.rotate_angle = value;
+                }
+                PatternHatchField::LineReduction(incr) => {
+                    let value: f64 = *prev.line_reduction + incr;
+                    debug!(
+                        "Patching line reduction for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.line_reduction = value;
+                }
+                PatternHatchField::LoopDistance(incr) => {
+                    let value: f64 = *prev.loop_distance + incr;
+                    debug!(
+                        "Patching loop distance for object #{} to {:.FP$}",
+                        next_idx, value
+                    );
+                    *next.loop_distance = value;
+                }
+                PatternHatchField::LoopCount(incr) => {
+                    let value: u32 = *prev.loop_count + incr;
+                    debug!("Patching loop count for object #{} to {}", next_idx, value);
+                    *next.loop_count = value;
+                }
+            }
+        });
+    }
+}

+ 22 - 0
src/config/mod.rs

@@ -6,6 +6,7 @@ use self::{
     pen::{ClonePen, ImportExportPen, PatchPen, PatternPen, RandomizePen},
     pen::{ClonePen, ImportExportPen, PatchPen, PatternPen, RandomizePen},
 };
 };
 
 
+pub mod hatch;
 pub mod object;
 pub mod object;
 pub mod pen;
 pub mod pen;
 
 
@@ -47,3 +48,24 @@ impl Operations for Vec<Operation> {
 pub struct Config {
 pub struct Config {
     pub ops: Vec<Operation>,
     pub ops: Vec<Operation>,
 }
 }
+
+/// Helper function that returns a window of two mutable elements
+pub fn double_window_mut<T, F>(slice: &mut [T], mut function: F)
+where
+    F: FnMut(&mut T, &mut T),
+{
+    for start in 0..(slice.len().saturating_sub(1)) {
+        let (a, b) = slice.split_at_mut(start + 1);
+        function(&mut a[a.len() - 1], &mut b[0])
+    }
+}
+
+#[test]
+fn test_double_window_mut() {
+    let mut data: Vec<u8> = vec![1, 2, 3, 4, 5];
+    double_window_mut(&mut data[..], |prev, next| {
+        println!("Prev: {}, Next: {}", prev, next);
+        *next = *next + *prev;
+    });
+    assert_eq!(data, vec![1, 3, 6, 10, 15]);
+}

+ 121 - 41
src/config/object.rs

@@ -16,7 +16,7 @@ use log::{debug, error, warn};
 use rand::{seq::SliceRandom, thread_rng};
 use rand::{seq::SliceRandom, thread_rng};
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 
 
-use super::pen::PatternField;
+use super::{hatch::PatternHatchField, pen::PatternPenField};
 
 
 #[derive(Debug, Serialize, Deserialize)]
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
 #[serde(rename_all = "PascalCase")]
@@ -85,7 +85,7 @@ impl From<HatchConfig> for HatchSetting {
             flags.set_average_distribute_line(1);
             flags.set_average_distribute_line(1);
         }
         }
 
 
-        let mut ret = Self::default();
+        let mut ret: HatchSetting = Self::default();
 
 
         *ret.line_spacing = value.line_spacing;
         *ret.line_spacing = value.line_spacing;
         value.pen.map(|x| *ret.pen = x.into());
         value.pen.map(|x| *ret.pen = x.into());
@@ -114,8 +114,10 @@ pub struct ArrayConfig {
     spacing: f64,
     spacing: f64,
     randomize_order: bool,
     randomize_order: bool,
     starting_pen: usize,
     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)]
 #[derive(Debug, Serialize, Deserialize, strum::Display)]
@@ -170,24 +172,14 @@ impl InputObject {
 
 
 #[derive(Debug, Serialize, Deserialize)]
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
 #[serde(rename_all = "PascalCase")]
-pub struct ObjectOperation {
-    input: InputObject,
+pub struct ObjectModify {
     z: Option<f64>,
     z: Option<f64>,
     origin: Option<Point>,
     origin: Option<Point>,
     pen: Option<u32>,
     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: &mut Vec<Pen>, layers: &mut ArrayOf<Layer>) {
-        debug!("Begin processing of object {:?}", self.input);
-
-        let mut object: Object = self.input.new(layers);
-
+impl ObjectModify {
+    pub fn process(&self, object: &mut Object, pens: &mut Vec<Pen>) {
         // Process basic transformation
         // Process basic transformation
         if self.origin.is_some() || self.z.is_some() {
         if self.origin.is_some() || self.z.is_some() {
             debug!(
             debug!(
@@ -198,14 +190,34 @@ impl ObjectOperation {
         }
         }
 
 
         self.pen.map(|pen| {
         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");
                 assert!(pen < pens.len().try_into().unwrap(), "Invalid pen index");
                 debug!("Setting object pen to #{}", pen);
                 debug!("Setting object pen to #{}", pen);
                 object.set_pen(pen);
                 object.set_pen(pen);
-            }
+           
         });
         });
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct ObjectOperation {
+    input: InputObject,
+    modify: Option<ObjectModify>,
+    layer: Option<usize>,
+    array: Option<ArrayConfig>,
+    hatch: Option<HatchConfig>,
+    export: Option<PathBuf>,
+    replace_object: Option<usize>,
+}
+
+impl ObjectOperation {
+    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);
+
+        // Modify object properties
+        self.modify.as_ref().map(|modify| modify.process(&mut object, pens));
 
 
         // Process conversion to hatch object
         // Process conversion to hatch object
         let object = self.hatch.as_ref().map_or(object.clone(), |hatch| {
         let object = self.hatch.as_ref().map_or(object.clone(), |hatch| {
@@ -292,22 +304,65 @@ impl ObjectOperation {
                     }
                     }
                 }
                 }
 
 
-                // Generate pens
-                match &array.pattern_y {
-                    None => {
-                        if let Some(pattern_x) = &array.pattern_x {
-                            pattern_x.pattern(
+                if (array.pattern_hatch_x.is_some() && array.pattern_pen_x.is_some())
+                    || (array.pattern_hatch_y.is_some() && array.pattern_pen_y.is_some())
+                {
+                    panic!("Conflict with X or Y axis patterning options");
+                }
+
+                let pattern_x: bool =
+                    array.pattern_hatch_x.is_some() || array.pattern_pen_x.is_some();
+                let pattern_y: bool =
+                    array.pattern_hatch_y.is_some() || array.pattern_pen_y.is_some();
+
+                if let Some(pen_x) = &array.pattern_pen_x {
+                    if pattern_y {
+                        for y in 0..array.rows {
+                            pen_x.pattern(
                                 &mut pens
                                 &mut pens
                                     .iter_mut()
                                     .iter_mut()
                                     .enumerate()
                                     .enumerate()
                                     .skip(array.starting_pen)
                                     .skip(array.starting_pen)
-                                    .take(array.columns * array.rows),
-                            );
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
+                        }
+                    } else {
+                        pen_x.pattern(
+                            &mut pens
+                                .iter_mut()
+                                .enumerate()
+                                .skip(array.starting_pen)
+                                .take(array.columns * array.rows),
+                        );
+                    }
+                }
+
+                if let Some(hatch_x) = &array.pattern_hatch_x {
+                    if pattern_y {
+                        for y in 0..array.rows {
+                            hatch_x.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(y * array.columns)
+                                    .take(array.columns),
+                            )
                         }
                         }
+                    } else {
+                        hatch_x.pattern(
+                            &mut new_obj
+                                .iter_mut()
+                                .enumerate()
+                                .take(array.columns * array.rows),
+                        );
                     }
                     }
-                    Some(pattern_y) => {
+                }
+
+                if let Some(pen_y) = &array.pattern_pen_y {
+                    if pattern_x {
                         for x in 0..array.columns {
                         for x in 0..array.columns {
-                            pattern_y.pattern(
+                            pen_y.pattern(
                                 &mut pens
                                 &mut pens
                                     .iter_mut()
                                     .iter_mut()
                                     .enumerate()
                                     .enumerate()
@@ -316,17 +371,42 @@ impl ObjectOperation {
                                     .take(array.rows),
                                     .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),
-                                )
-                            }
+                    } else {
+                        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(hatch_y) = &array.pattern_hatch_y {
+                    if pattern_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),
+                            );
+                        }
+                    } else {
+                        for x in 0..array.columns {
+                            hatch_y.pattern(
+                                &mut new_obj
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
                         }
                         }
                     }
                     }
                 }
                 }

+ 67 - 91
src/config/pen.rs

@@ -10,6 +10,8 @@ use rand::{seq::SliceRandom, Rng};
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use strum::IntoEnumIterator;
 use strum::IntoEnumIterator;
 
 
+use super::double_window_mut;
+
 const SPEED_MIN: f64 = 0.0;
 const SPEED_MIN: f64 = 0.0;
 const SPEED_MAX: f64 = 100000.0;
 const SPEED_MAX: f64 = 100000.0;
 const POWER_MIN: f64 = 0.0;
 const POWER_MIN: f64 = 0.0;
@@ -106,7 +108,7 @@ impl PatchPen {
         debug!("Patching pen #{}", self.pen);
         debug!("Patching pen #{}", self.pen);
         let pen: &mut Pen = pens.get_mut(self.pen).expect("Invalid pen index");
         let pen: &mut Pen = pens.get_mut(self.pen).expect("Invalid pen index");
         self.patch.patch(pen);
         self.patch.patch(pen);
-        pen.valid_settings();
+        pen.valid_settings(true);
     }
     }
 }
 }
 
 
@@ -169,7 +171,7 @@ impl ClonePen {
 }
 }
 
 
 #[derive(Debug, Serialize, Deserialize)]
 #[derive(Debug, Serialize, Deserialize)]
-pub enum PatternField {
+pub enum PatternPenField {
     Loops(i32),
     Loops(i32),
     Speed(f64),
     Speed(f64),
     Power(f64),
     Power(f64),
@@ -177,77 +179,48 @@ pub enum PatternField {
     PulseWidth(u32),
     PulseWidth(u32),
 }
 }
 
 
-impl PatternField {
+impl PatternPenField {
     pub fn pattern(&self, pens: &mut dyn Iterator<Item = (usize, &mut Pen)>) {
     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(_) => {
-                debug!(
-                    "Initial loop count from pen #{} is {}",
-                    src_idx, *src.loop_count
-                );
-                PatternField::Loops((*src.loop_count).try_into().unwrap())
-            }
-            PatternField::Speed(_) => {
-                debug!("Initial speed from pen #{} is {}", src_idx, *src.speed);
-                PatternField::Speed(*src.speed)
-            }
-            PatternField::Power(_) => {
-                debug!("Initial power from pen #{} is {}", src_idx, *src.power);
-                PatternField::Power(*src.power)
-            }
-            PatternField::Frequency(_) => {
-                debug!(
-                    "Initial frequency from pen #{} is {}",
-                    src_idx, *src.frequency
-                );
-                PatternField::Frequency((*src.frequency).try_into().unwrap())
-            }
-            PatternField::PulseWidth(_) => {
-                debug!(
-                    "Initial pulse width from pen #{} is {}ns",
-                    src_idx, *src.pulse_width
-                );
-                PatternField::PulseWidth(*src.pulse_width)
-            }
-        };
-
-        for (idx, dst) in pens {
-            // Calculate new setting
-            setting = match (setting, self) {
-                (PatternField::Loops(prev), PatternField::Loops(incr)) => {
-                    let value: i32 = prev + incr;
-                    debug!("Patching loop count for pen #{} to {}", idx, value);
+        let mut pens: Vec<(usize, &mut Pen)> = pens.collect_vec();
+
+        double_window_mut(&mut pens[..], |prev, next| {
+            let _prev_idx: usize = prev.0;
+            let prev: &mut Pen = prev.1;
+            let next_idx: usize = next.0;
+            let next: &mut Pen = next.1;
+
+            match self {
+                PatternPenField::Loops(incr) => {
+                    let value: i32 = i32::try_from(*prev.loop_count).unwrap() + incr;
+                    debug!("Patching loop count for pen #{} to {}", next_idx, value);
                     assert!(value > 0, "Pen loop count must be greater than zero");
                     assert!(value > 0, "Pen loop count must be greater than zero");
-                    PatternField::Loops(value)
+                    *next.loop_count = value.try_into().unwrap()
                 }
                 }
-                (PatternField::Speed(prev), PatternField::Speed(incr)) => {
-                    let value: f64 = prev + incr;
-                    debug!("Patching speed for pen #{} to {}", idx, value);
+                PatternPenField::Speed(incr) => {
+                    let value: f64 = *prev.speed + incr;
+                    debug!("Patching speed for pen #{} to {}", next_idx, value);
                     assert!(
                     assert!(
                         value > SPEED_MIN && value <= SPEED_MAX,
                         value > SPEED_MIN && value <= SPEED_MAX,
                         "Pen speed must be between {} and {}",
                         "Pen speed must be between {} and {}",
                         SPEED_MIN,
                         SPEED_MIN,
                         SPEED_MAX
                         SPEED_MAX
                     );
                     );
-                    PatternField::Speed(value)
+                    *next.speed = value;
                 }
                 }
-                (PatternField::Power(prev), PatternField::Power(incr)) => {
-                    let value: f64 = prev + incr;
-                    debug!("Patching power for pen #{} to {}", idx, value);
+                PatternPenField::Power(incr) => {
+                    let value: f64 = *prev.power + incr;
+                    debug!("Patching power for pen #{} to {}", next_idx, value);
                     assert!(
                     assert!(
                         value > POWER_MIN && value <= POWER_MAX,
                         value > POWER_MIN && value <= POWER_MAX,
                         "Pen power must be between {} and {}",
                         "Pen power must be between {} and {}",
                         POWER_MIN,
                         POWER_MIN,
                         POWER_MAX
                         POWER_MAX
                     );
                     );
-                    PatternField::Power(value)
+                    *next.power = value;
                 }
                 }
-                (PatternField::Frequency(prev), PatternField::Frequency(incr)) => {
-                    let value: i32 = prev + incr;
-                    debug!("Patching frequency for pen #{} to {}", idx, value);
+                PatternPenField::Frequency(incr) => {
+                    let value: i32 = i32::try_from(*prev.frequency).unwrap() + incr;
+                    debug!("Patching frequency for pen #{} to {}", next_idx, value);
                     assert!(
                     assert!(
                         value >= FREQUENCY_MIN.try_into().unwrap()
                         value >= FREQUENCY_MIN.try_into().unwrap()
                             && value <= FREQUENCY_MAX.try_into().unwrap(),
                             && value <= FREQUENCY_MAX.try_into().unwrap(),
@@ -255,45 +228,31 @@ impl PatternField {
                         FREQUENCY_MIN,
                         FREQUENCY_MIN,
                         FREQUENCY_MAX
                         FREQUENCY_MAX
                     );
                     );
-                    PatternField::Frequency(value)
+                    *next.frequency = value.try_into().unwrap();
+                    *next.frequency_2 = value.try_into().unwrap();
                 }
                 }
-                (PatternField::PulseWidth(prev), PatternField::PulseWidth(incr)) => {
+                PatternPenField::PulseWidth(incr) => {
                     let mut pw = PulseWidth::iter();
                     let mut pw = PulseWidth::iter();
                     let _ = pw
                     let _ = pw
-                        .find(|x| u32::from(*x) == prev)
+                        .find(|x| u32::from(*x) == *prev.pulse_width)
                         .expect("Unknown pulse width");
                         .expect("Unknown pulse width");
 
 
                     let mut pw = pw.skip((*incr - 1).try_into().unwrap());
                     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)
-                }
-                _ => 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) => {
-                    *dst.frequency = x.try_into().unwrap();
-                    *dst.frequency_2 = x.try_into().unwrap();
-                }
-                PatternField::PulseWidth(x) => {
-                    *dst.pulse_width = x;
-                    *dst.pulse_width_2 = x.try_into().unwrap();
+                    let pw: u32 = pw.next().expect("Pulse width out of bounds").into();
+                    debug!("Patching pulse width for pen #{} to {}ns", next_idx, next);
+                    *next.pulse_width = pw;
+                    *next.pulse_width_2 = pw.try_into().unwrap();
                 }
                 }
             }
             }
 
 
             // Randomize pen color
             // Randomize pen color
-            *dst.color = Rgba::random().into();
+            *next.color = Rgba::random().into();
 
 
             // Always enable custom settings for pen
             // Always enable custom settings for pen
-            *dst.use_default = 0;
+            *next.use_default = 0;
 
 
-            dst.valid_settings();
-        }
+            next.valid_settings(true);
+        });
     }
     }
 }
 }
 
 
@@ -302,7 +261,7 @@ impl PatternField {
 pub struct PatternPen {
 pub struct PatternPen {
     index: usize,
     index: usize,
     count: usize,
     count: usize,
-    field: PatternField,
+    field: PatternPenField,
 }
 }
 
 
 impl PatternPen {
 impl PatternPen {
@@ -355,6 +314,8 @@ pub struct RandomizePen {
     power: Option<(f64, f64, f64)>,
     power: Option<(f64, f64, f64)>,
     frequency: Option<(u32, u32, u32)>,
     frequency: Option<(u32, u32, u32)>,
     pulse_width: Option<(PulseWidth, PulseWidth, usize)>,
     pulse_width: Option<(PulseWidth, PulseWidth, usize)>,
+    power_density: Option<(u64, u64)>,
+    enforce_limits: Option<bool>,
 }
 }
 
 
 impl RandomizePen {
 impl RandomizePen {
@@ -420,17 +381,32 @@ impl RandomizePen {
                     setting.pulse_width = Some(*width);
                     setting.pulse_width = Some(*width);
                 }
                 }
 
 
+                setting.apply(pen);
+
+                // Check if power density value is within range
+                if let Some((range_min, range_max)) = self.power_density {
+                    let power_density: u64 = pen.power_density();
+                    if power_density < range_min || power_density > range_max {
+                        debug!("Retrying (power density {power_density} out of range)");
+                        continue;
+                    }
+                }
+
+                // Check settings
+                if self.enforce_limits.unwrap_or(true) {
+                    debug!("Checking settings");
+                    if !pen.valid_settings(false) {
+                        debug!("Retrying (invalid setting)");
+                        continue;
+                    }
+                }
+
                 // Check for duplicates
                 // Check for duplicates
                 if !generated.contains(&setting) {
                 if !generated.contains(&setting) {
                     generated.push(setting);
                     generated.push(setting);
-                    setting.apply(pen);
-                    if !pen.valid_settings() {
-                        debug!("Retrying..");
-                    } else {
-                        break;
-                    }
+                    break;
                 } else {
                 } else {
-                    debug!("Duplicate random setting");
+                    debug!("Retrying (duplicate setting)");
                 }
                 }
 
 
                 // Fail out if max attempts reached (insufficient search space)
                 // Fail out if max attempts reached (insufficient search space)
@@ -442,7 +418,7 @@ impl RandomizePen {
                 }
                 }
             }
             }
 
 
-            pen.valid_settings();
+            pen.valid_settings(true);
         }
         }
     }
     }
 }
 }

+ 4 - 4
src/ezcad/objects/circle.rs

@@ -7,6 +7,7 @@ use log::warn;
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{ObjectType, Point, F64, U32},
     types::{ObjectType, Point, F64, U32},
+    FP,
 };
 };
 
 
 use super::{ObjectCore, ObjectModifier, Translate};
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -31,17 +32,16 @@ impl Display for Circle {
             *self.drawn_origin - Point::from(*self.radius),
             *self.drawn_origin - Point::from(*self.radius),
             *self.drawn_origin + Point::from(*self.radius),
             *self.drawn_origin + Point::from(*self.radius),
         );
         );
-
-        if format!("{}", origin) != format!("{}", *self.core.origin) {
+        if origin != *self.core.origin {
             warn!(
             warn!(
-                "Origin mismatch! Core: {}, Calculated: {}",
+                "Origin mismatch! Core: {:.FP$}, Calculated: {:.FP$}",
                 *self.core.origin, origin
                 *self.core.origin, origin
             );
             );
         }
         }
 
 
         write!(
         write!(
             f,
             f,
-            "{}, Origin: {}, Width : {:.2}, Height: {:.2}, Start Radian: {:.2}, Clockwise: {}",
+            "{}, Origin: {}, Width : {:.FP$}, Height: {:.FP$}, Start Radian: {:.FP$}, Clockwise: {}",
             self.core,
             self.core,
             origin,
             origin,
             width,
             width,

+ 4 - 3
src/ezcad/objects/ellipse.rs

@@ -7,6 +7,7 @@ use log::warn;
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{ObjectType, Point, F64, U32},
     types::{ObjectType, Point, F64, U32},
+    FP,
 };
 };
 
 
 use super::{ObjectCore, ObjectModifier, Translate};
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -33,16 +34,16 @@ impl Display for Ellipse {
             .modifier
             .modifier
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
 
 
-        if format!("{}", origin) != format!("{}", *self.core.origin) {
+        if origin != *self.core.origin {
             warn!(
             warn!(
-                "Origin mismatch! Core: {}, Calculated: {}",
+                "Origin mismatch! Core: {:.FP$}, Calculated: {:.FP$}",
                 *self.core.origin, origin
                 *self.core.origin, origin
             );
             );
         }
         }
 
 
         write!(
         write!(
             f,
             f,
-            "{}, Origin: {}, Width: {:.2}, Height: {:.2}, Start Radian: {:.2}, End Radian: {:.2}, Open Curve: {}", 
+            "{}, Origin: {}, Width: {:.FP$}, Height: {:.FP$}, Start Radian: {:.FP$}, End Radian: {:.FP$}, Open Curve: {}", 
             self.core,
             self.core,
             origin,
             origin,
             width,
             width,

+ 21 - 2
src/ezcad/objects/hatch.rs

@@ -3,7 +3,9 @@ use std::fmt::{Debug, Display};
 use crate::{
 use crate::{
     array_of::ArrayOf,
     array_of::ArrayOf,
     field_of::FieldOf,
     field_of::FieldOf,
+    pen::Pen,
     types::{Field, Point, F64, U32},
     types::{Field, Point, F64, U32},
+    FP,
 };
 };
 use binrw::{binrw, BinRead, BinWrite};
 use binrw::{binrw, BinRead, BinWrite};
 use diff::Diff;
 use diff::Diff;
@@ -410,7 +412,7 @@ impl Display for Hatch {
             if setting.enabled.into() {
             if setting.enabled.into() {
                 write!(
                 write!(
                     f,
                     f,
-                    "\nHatch #{}: All Calc: {}, Follow Edge Once: {}, Cross Hatch: {}, Pattern: {}, Angle: {:.2}, Pen: {}, Count: {}, Line Space: {:.2}, Avg Distribte Line: {}",
+                    "\nHatch #{}: All Calc: {}, Follow Edge Once: {}, Cross Hatch: {}, Pattern: {}, Angle: {:.FP$}, Pen: {}, Count: {}, Line Space: {:.FP$}, Avg Distribte Line: {}",
                     index,
                     index,
                     setting.flags.all_calc() != 0,
                     setting.flags.all_calc() != 0,
                     setting.flags.follow_edge_once() != 0,
                     setting.flags.follow_edge_once() != 0,
@@ -424,7 +426,7 @@ impl Display for Hatch {
                 )?;
                 )?;
                 write!(
                 write!(
                     f,
                     f,
-                    "\n          Edge Offset: {:.2}, Start Offset: {:.2}, End Offset: {:.2}, Line Reduction: {:.2}, Loop Count: {}, Loop Distance: {:.2}, Auto Rotate: {}, Auto Rotate Angle: {:.2}",
+                    "\n          Edge Offset: {:.FP$}, Start Offset: {:.FP$}, End Offset: {:.FP$}, Line Reduction: {:.FP$}, Loop Count: {}, Loop Distance: {:.FP$}, Auto Rotate: {}, Auto Rotate Angle: {:.FP$}",
                     *setting.edge_offset,
                     *setting.edge_offset,
                     *setting.start_offset,
                     *setting.start_offset,
                     *setting.end_offset,
                     *setting.end_offset,
@@ -482,3 +484,20 @@ impl Translate for Hatch {
         self.core.move_relative(delta, z);
         self.core.move_relative(delta, z);
     }
     }
 }
 }
+
+impl Hatch {
+    /// Estimate the power density from the hatch's line spacing and pen speed/frequency/power/pulse
+    pub fn calc_power_density(&self, pens: &Vec<Pen>) -> u64 {
+        let pen: &Pen = pens
+            .get(*self.core.pen as usize)
+            .expect("Invalid pen index");
+        let line_spacing: f64 = *self
+            .hatch_settings
+            .iter()
+            .find(|h| h.enabled.into())
+            .expect("Hatch object does not have enabled settings")
+            .line_spacing;
+
+        (1.0 / line_spacing) as u64 * pen.power_density()
+    }
+}

+ 2 - 1
src/ezcad/objects/mod.rs

@@ -17,6 +17,7 @@ use modular_bitfield::{
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{Field, ObjectType, Point, WString, F64, U16, U32},
     types::{Field, ObjectType, Point, WString, F64, U16, U32},
+    FP,
 };
 };
 
 
 use self::{
 use self::{
@@ -79,7 +80,7 @@ impl Display for ObjectCore {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(
         write!(
             f,
             f,
-            "Type: {}, Enabled: {}, Pen: {}, Count: {}, Z: {:.2}",
+            "Type: {}, Enabled: {}, Pen: {}, Count: {}, Z: {:.FP$}",
             self.obj_type,
             self.obj_type,
             (self.flags.disabled() == 0),
             (self.flags.disabled() == 0),
             self.pen,
             self.pen,

+ 4 - 3
src/ezcad/objects/polygon.rs

@@ -7,6 +7,7 @@ use log::warn;
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{ObjectType, Point, F64, U32},
     types::{ObjectType, Point, F64, U32},
+    FP,
 };
 };
 
 
 use super::{ObjectCore, ObjectModifier, Translate};
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -35,16 +36,16 @@ impl Display for Polygon {
             .modifier
             .modifier
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
 
 
-        if format!("{}", origin) != format!("{}", *self.core.origin) {
+        if origin != *self.core.origin {
             warn!(
             warn!(
-                "Origin mismatch! Core: {}, Calculated: {}",
+                "Origin mismatch! Core: {:.FP$}, Calculated: {:.FP$}",
                 *self.core.origin, origin
                 *self.core.origin, origin
             );
             );
         }
         }
 
 
         write!(
         write!(
             f,
             f,
-            "{}, Origin: {}, Width: {:.2}, Height: {:.2}, Inverted: {}, Offset CX: {:.2}, Offset CY: {:.2}, Offset DX: {:.2}, Offset: DY: {:.2}, Edges: {}",
+            "{}, Origin: {}, Width: {:.FP$}, Height: {:.FP$}, Inverted: {}, Offset CX: {:.FP$}, Offset CY: {:.FP$}, Offset DX: {:.FP$}, Offset: DY: {:.FP$}, Edges: {}",
             self.core,
             self.core,
             origin,
             origin,
             width,
             width,

+ 4 - 3
src/ezcad/objects/rectangle.rs

@@ -7,6 +7,7 @@ use log::warn;
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{ObjectType, Point, F64},
     types::{ObjectType, Point, F64},
+    FP,
 };
 };
 
 
 use super::{ObjectCore, ObjectModifier, Translate};
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -33,16 +34,16 @@ impl Display for Rectangle {
             .modifier
             .modifier
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
             .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
 
 
-        if format!("{}", origin) != format!("{}", *self.core.origin) {
+        if origin != *self.core.origin {
             warn!(
             warn!(
-                "Origin mismatch! Core: {}, Calculated: {}",
+                "Origin mismatch! Core: {:.FP$}, Calculated: {:.FP$}",
                 *self.core.origin, origin
                 *self.core.origin, origin
             );
             );
         }
         }
 
 
         write!(
         write!(
             f,
             f,
-            "{}, Origin: {}, Width: {:.2}, Height: {:.2}",
+            "{}, Origin: {}, Width: {:.FP$}, Height: {:.FP$}",
             self.core, origin, width, height,
             self.core, origin, width, height,
         )
         )
     }
     }

+ 34 - 15
src/ezcad/pen.rs

@@ -9,10 +9,12 @@ use binrw::{BinRead, BinWrite, BinWriterExt, FilePtr64};
 use diff::Diff;
 use diff::Diff;
 use human_repr::HumanCount;
 use human_repr::HumanCount;
 use log::{error, warn};
 use log::{error, warn};
+use num_enum::TryFromPrimitive;
 
 
 use crate::{
 use crate::{
     field_of::FieldOf,
     field_of::FieldOf,
     types::{Bool, Field, PulseWidth, Rgba, WString, WobbleType, F64, U32},
     types::{Bool, Field, PulseWidth, Rgba, WString, WobbleType, F64, U32},
+    FP,
 };
 };
 
 
 #[derive(BinRead, Debug)]
 #[derive(BinRead, Debug)]
@@ -123,12 +125,12 @@ impl Pen {
             .expect("Failed to write to output file");
             .expect("Failed to write to output file");
     }
     }
 
 
-    pub fn valid_settings(&self) -> bool {
+    pub fn valid_settings(&self, warn_limits: bool) -> bool {
         let mut ret: bool = true;
         let mut ret: bool = true;
 
 
         if *self.frequency != *self.frequency_2 as u32 {
         if *self.frequency != *self.frequency_2 as u32 {
             error!(
             error!(
-                "Mismatch pen internal frequency setting: ({}, {:.3})",
+                "Mismatch pen internal frequency setting: ({}, {:.FP$})",
                 *self.frequency, *self.frequency_2
                 *self.frequency, *self.frequency_2
             );
             );
             ret = false;
             ret = false;
@@ -136,7 +138,7 @@ impl Pen {
 
 
         if *self.pulse_width != *self.pulse_width_2 as u32 {
         if *self.pulse_width != *self.pulse_width_2 as u32 {
             error!(
             error!(
-                "Mismatch pen internal pulse width setting: ({}, {:.3})",
+                "Mismatch pen internal pulse width setting: ({}, {:.FP$})",
                 *self.pulse_width, *self.pulse_width_2
                 *self.pulse_width, *self.pulse_width_2
             );
             );
             ret = false;
             ret = false;
@@ -145,19 +147,23 @@ impl Pen {
         match PulseWidth::try_from(*self.pulse_width) {
         match PulseWidth::try_from(*self.pulse_width) {
             Ok(pw) => match *self.frequency {
             Ok(pw) => match *self.frequency {
                 freq if freq < pw.min_freq() => {
                 freq if freq < pw.min_freq() => {
-                    warn!(
-                        "Pen frequency of {} lower than pulse width minimum frequency of {}",
-                        *self.frequency,
-                        pw.min_freq()
-                    );
+                    if warn_limits {
+                        warn!(
+                            "Pen frequency of {} lower than pulse width minimum frequency of {}",
+                            *self.frequency,
+                            pw.min_freq()
+                        );
+                    }
                     ret = false;
                     ret = false;
                 }
                 }
                 freq if freq > pw.max_freq() => {
                 freq if freq > pw.max_freq() => {
-                    warn!(
-                        "Pen frequency of {} higher than pulse width maximum frequency of {}",
-                        *self.frequency,
-                        pw.max_freq()
-                    );
+                    if warn_limits {
+                        warn!(
+                            "Pen frequency of {} higher than pulse width maximum frequency of {}",
+                            *self.frequency,
+                            pw.max_freq()
+                        );
+                    }
                     ret = false;
                     ret = false;
                 }
                 }
                 _ => (),
                 _ => (),
@@ -170,17 +176,30 @@ impl Pen {
 
 
         ret
         ret
     }
     }
+
+    /// Estimate the power density from the speed/frequency/power/pulse
+    pub fn power_density(&self) -> u64 {
+        let power: f64 = *self.power;
+        let speed: f64 = *self.speed;
+        let frequency: f64 = f64::from(*self.frequency);
+        let pulse: f64 = PulseWidth::try_from_primitive(*self.pulse_width)
+            .expect("Invalid pen pulse width")
+            .power_ratio();
+
+        (power * (1.0 / speed) * frequency * pulse) as u64
+    }
 }
 }
 
 
 impl std::fmt::Display for Pen {
 impl std::fmt::Display for Pen {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(
         write!(
             f,
             f,
-            "Speed: {}mm/s, Frequency: {}, Power: {}%, Pulse Width: {}ns",
+            "Speed: {}mm/s, Frequency: {}, Power: {}%, Pulse Width: {}ns, Power Density: {}",
             self.speed,
             self.speed,
             self.frequency.human_count("Hz"),
             self.frequency.human_count("Hz"),
             self.power,
             self.power,
-            self.pulse_width
+            self.pulse_width,
+            self.power_density()
         )
         )
     }
     }
 }
 }

+ 34 - 5
src/ezcad/types.rs

@@ -5,13 +5,14 @@ use std::{
 
 
 use binrw::{binrw, BinRead, BinWrite};
 use binrw::{binrw, BinRead, BinWrite};
 use diff::{Diff, VecDiff};
 use diff::{Diff, VecDiff};
+use float_cmp::approx_eq;
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use rand::{thread_rng, Rng};
 use rand::{thread_rng, Rng};
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 use serde_repr::{Deserialize_repr, Serialize_repr};
 use serde_repr::{Deserialize_repr, Serialize_repr};
 use strum::EnumIter;
 use strum::EnumIter;
 
 
-use crate::{array_of::ArrayOfPrimitive, field_of::FieldOf};
+use crate::{array_of::ArrayOfPrimitive, field_of::FieldOf, FP};
 
 
 /// Generic field with structure of length + data
 /// Generic field with structure of length + data
 pub type Field = ArrayOfPrimitive<u8>;
 pub type Field = ArrayOfPrimitive<u8>;
@@ -127,9 +128,7 @@ impl Rgba {
     }
     }
 }
 }
 
 
-#[derive(
-    Copy, Clone, Debug, Default, Diff, PartialEq, BinRead, BinWrite, Serialize, Deserialize,
-)]
+#[derive(Copy, Clone, Debug, Default, Diff, BinRead, BinWrite, Serialize, Deserialize)]
 #[diff(attr(
 #[diff(attr(
     #[derive(Debug, PartialEq)]
     #[derive(Debug, PartialEq)]
 ))]
 ))]
@@ -139,9 +138,15 @@ pub struct Point {
     pub y: f64,
     pub y: f64,
 }
 }
 
 
+impl PartialEq for Point {
+    fn eq(&self, other: &Self) -> bool {
+        approx_eq!(f64, self.x, other.x) && approx_eq!(f64, self.y, other.y)
+    }
+}
+
 impl Display for Point {
 impl Display for Point {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "({:.2}, {:.2})", self.x, self.y)
+        write!(f, "({:.FP$}, {:.FP$})", self.x, self.y)
     }
     }
 }
 }
 
 
@@ -356,4 +361,28 @@ impl PulseWidth {
             PulseWidth::Ns500 => 500_000,
             PulseWidth::Ns500 => 500_000,
         }
         }
     }
     }
+
+    /// Ratio of total output power per pulse from the area under the curve of the pulse
+    /// waveform over time. Pulses below 20ns are assumed to be linear discrete pulses
+    /// with no decay over time, while longer pulse widths will follow natural log profile
+    pub fn power_ratio(&self) -> f64 {
+        match self {
+            PulseWidth::Ns2 => 1.0,
+            PulseWidth::Ns4 => 3.17,
+            PulseWidth::Ns6 => 5.34,
+            PulseWidth::Ns8 => 7.51,
+            PulseWidth::Ns12 => 11.85,
+            PulseWidth::Ns20 => 17.29,
+            PulseWidth::Ns30 => 22.04,
+            PulseWidth::Ns45 => 26.78,
+            PulseWidth::Ns60 => 30.17,
+            PulseWidth::Ns80 => 33.54,
+            PulseWidth::Ns100 => 36.16,
+            PulseWidth::Ns150 => 40.91,
+            PulseWidth::Ns200 => 44.28,
+            PulseWidth::Ns250 => 46.90,
+            PulseWidth::Ns350 => 50.84,
+            PulseWidth::Ns500 => 55.03,
+        }
+    }
 }
 }

+ 6 - 3
src/lib.rs

@@ -1,3 +1,6 @@
-pub mod ezcad;
-
-pub use ezcad::*;
+pub mod ezcad;
+
+pub use ezcad::*;
+
+// Precision when comparing/printing float values
+pub const FP: usize = 3;

+ 95 - 55
src/main.rs

@@ -2,17 +2,20 @@ use std::{
     fs::File,
     fs::File,
     io::{Cursor, Read, Write},
     io::{Cursor, Read, Write},
     ops::RangeInclusive,
     ops::RangeInclusive,
-    path::PathBuf,
+    path::{Path, PathBuf},
     time::Instant,
     time::Instant,
 };
 };
 
 
 use binrw::{BinRead, BinWrite, BinWriterExt};
 use binrw::{BinRead, BinWrite, BinWriterExt};
 use clap::{error::ErrorKind, Args, Error, Parser, Subcommand};
 use clap::{error::ErrorKind, Args, Error, Parser, Subcommand};
 use clap_verbosity_flag::{InfoLevel, Verbosity};
 use clap_verbosity_flag::{InfoLevel, Verbosity};
+use dialoguer::Confirm;
 use diff::Diff;
 use diff::Diff;
 use env_logger::Target;
 use env_logger::Target;
-use ezcad::{file::EzCadHeader, layer::Layer, objects::Object};
+use ezcad::{file::EzCadHeader, layer::Layer, objects::Object, pen::Pen};
+use itertools::Itertools;
 use log::{info, trace, warn};
 use log::{info, trace, warn};
+use num_format::{Locale, ToFormattedString};
 use regex::Regex;
 use regex::Regex;
 
 
 use crate::config::{Config, Operations};
 use crate::config::{Config, Operations};
@@ -25,7 +28,7 @@ struct Cli {
     command: SubCommands,
     command: SubCommands,
 
 
     /// Input .mlp file to parse
     /// Input .mlp file to parse
-    #[arg(short, long)]
+    #[arg(short = 'i', long)]
     input: PathBuf,
     input: PathBuf,
 
 
     #[command(flatten)]
     #[command(flatten)]
@@ -43,7 +46,7 @@ enum SubCommands {
 #[derive(Debug, Args)]
 #[derive(Debug, Args)]
 struct DiffCmd {
 struct DiffCmd {
     /// File to diff input against
     /// File to diff input against
-    #[arg(short, long)]
+    #[arg(short = 'd', long)]
     diff_file: PathBuf,
     diff_file: PathBuf,
 }
 }
 
 
@@ -51,17 +54,17 @@ struct DiffCmd {
 #[derive(Debug, Args)]
 #[derive(Debug, Args)]
 struct QueryCmd {
 struct QueryCmd {
     /// Print info for pens
     /// Print info for pens
-    #[arg(short, long)]
+    #[arg(short = 'p', long)]
     #[arg(value_parser = parse_range)]
     #[arg(value_parser = parse_range)]
     pen: Option<RangeInclusive<usize>>,
     pen: Option<RangeInclusive<usize>>,
 
 
     /// Print info for objects
     /// Print info for objects
-    #[arg(short, long)]
+    #[arg(short = 'o', long)]
     #[arg(value_parser = parse_range)]
     #[arg(value_parser = parse_range)]
     object: Option<RangeInclusive<usize>>,
     object: Option<RangeInclusive<usize>>,
 
 
     /// Object layer to query object on
     /// Object layer to query object on
-    #[arg(short = 'b', long)]
+    #[arg(short = 'l', long)]
     object_layer: Option<usize>,
     object_layer: Option<usize>,
 }
 }
 
 
@@ -69,12 +72,16 @@ struct QueryCmd {
 #[derive(Debug, Args)]
 #[derive(Debug, Args)]
 struct ApplyConfig {
 struct ApplyConfig {
     /// Configuration file
     /// Configuration file
-    #[arg(short, long)]
+    #[arg(short = 'c', long)]
     config: PathBuf,
     config: PathBuf,
 
 
     /// Output file to write to
     /// Output file to write to
-    #[arg(short, long)]
+    #[arg(short = 'o', long)]
     output: Option<PathBuf>,
     output: Option<PathBuf>,
+
+    /// Overwrite output file if it exists
+    #[arg(short = 'w', long)]
+    overwrite: bool,
 }
 }
 
 
 /// Helper function to parse a string as an RangeInclusive<usize>
 /// Helper function to parse a string as an RangeInclusive<usize>
@@ -181,15 +188,10 @@ fn main() {
             );
             );
         }
         }
         SubCommands::Query(args) => {
         SubCommands::Query(args) => {
+            let pens: &Vec<Pen> = &file.pens_offset.data.pens;
+
             // Print info on pens with non-default settings
             // Print info on pens with non-default settings
-            for (index, pen) in file
-                .pens_offset
-                .data
-                .pens
-                .iter()
-                .filter(|x| *x.use_default == 0)
-                .enumerate()
-            {
+            for (index, pen) in pens.iter().filter(|x| *x.use_default == 0).enumerate() {
                 trace!("Pen {}: {:#?}", index, pen);
                 trace!("Pen {}: {:#?}", index, pen);
             }
             }
 
 
@@ -211,45 +213,69 @@ fn main() {
                     info!(
                     info!(
                         "Pen #{}: {}",
                         "Pen #{}: {}",
                         pen,
                         pen,
-                        file.pens_offset
-                            .data
-                            .pens
-                            .get(pen)
-                            .expect("Invalid pen index")
+                        pens.get(pen).expect("Invalid pen index")
                     );
                     );
                 }
                 }
             });
             });
 
 
             // Process object query
             // Process object query
-            args.object.map(|obj_range| {
-                warn!("Object origin, width, and height values may be incorrect if object is skewed or rotated");
-
-                let layer_index: usize = args.object_layer.unwrap_or(0);
-                let layer: &Layer = file
-                    .layers_offset
-                    .get(layer_index)
-                    .expect("Invalid layer index");
-
-                for object_index in obj_range {
-                    let object: &Object = layer
-                        .objects
-                        .get(object_index)
-                        .expect("Invalid object index");
-                    let pen_index: u32 = *object.core().pen;
-                    info!(
-                        "Layer #{}, Object #{}:\n{}\nPen: #{}: {}",
-                        layer_index,
-                        object_index,
-                        object,
-                        pen_index,
-                        file.pens_offset
-                            .data
-                            .pens
-                            .get(pen_index as usize)
-                            .expect("Invalid pen index in object")
-                    );
-                }
+            let layer_idx: usize = args.object_layer.unwrap_or_else(|| {
+                (*file.layers_offset)
+                    .iter()
+                    .find_position(|x| x.objects.len() != 0)
+                    .expect("No objects defined in any layer")
+                    .0
             });
             });
+            let layer: &Layer = file
+                .layers_offset
+                .get(layer_idx)
+                .expect("Invalid layer index");
+
+            warn!("Object origin, width, and height values may be incorrect if object is skewed or rotated");
+            let objects: Vec<(usize, &Object)> = if let Some(obj_range) = args.object {
+                obj_range
+                    .clone()
+                    .zip(
+                        layer
+                            .objects
+                            .get(obj_range)
+                            .expect("Invalid object query range")
+                            .iter(),
+                    )
+                    .collect_vec()
+            } else {
+                layer.objects.iter().enumerate().collect_vec()
+            };
+
+            for (obj_idx, object) in objects {
+                let pen_idx: u32 = *object.core().pen;
+                let pen: &Pen = pens
+                    .get(pen_idx as usize)
+                    .expect("Invalid pen index in object");
+
+                // Calculate estimated power density if object is hatched
+                let hatch_power: Option<String> = match object {
+                    Object::Hatch(hatch) => Some(
+                        hatch
+                            .calc_power_density(pens)
+                            .to_formatted_string(&Locale::en),
+                    ),
+                    _ => None,
+                };
+
+                // Print info
+                info!(
+                    "Layer #{}, Object #{}:\n{}\nPen: #{}: {}{}",
+                    layer_idx,
+                    obj_idx,
+                    object,
+                    pen_idx,
+                    pen,
+                    hatch_power.map_or(String::new(), |p| format!(
+                        "\nEstimated hatch power density: {p}"
+                    ))
+                );
+            }
         }
         }
         SubCommands::Apply(args) => {
         SubCommands::Apply(args) => {
             // Process config
             // Process config
@@ -268,7 +294,6 @@ fn main() {
 
 
             // Process output
             // Process output
             args.output.map(|output| {
             args.output.map(|output| {
-                info!("Writing output file '{}'", output.to_string_lossy());
                 // Serialize to memory buffer for perf
                 // Serialize to memory buffer for perf
                 let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
                 let mut buffer: Cursor<Vec<u8>> = Cursor::new(vec![]);
                 let time: Instant = Instant::now();
                 let time: Instant = Instant::now();
@@ -278,10 +303,25 @@ fn main() {
                 trace!("Output file encode time: {:?}", time.elapsed());
                 trace!("Output file encode time: {:?}", time.elapsed());
 
 
                 // Write buffer to output file
                 // 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()
+                    || args.overwrite
+                    || 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");
+                }
             });
             });
         }
         }
     }
     }

二进制
test.mlp


二进制
test2.mlp


+ 91 - 0
test2.yml

@@ -0,0 +1,91 @@
+Ops: 
+  - !PatchPen
+    Pen: 0
+    Speed: 500
+    Frequency: 50000
+    Power: 30.0
+    PulseWidth: 2
+
+  - !ClonePen
+    From: 0
+    To: 255
+    Inclusive: True
+
+  # - !PatchPen
+  #   Pen: 0
+  #   Power: 10.0
+
+  # - !PatternPen
+  #   From: 0
+  #   To: 40
+  #   Field: !Power 2
+
+  # - !DeleteObjects
+  #   Layer: 0
+
+  # - !HatchArray
+  #   Layer: 0
+  #   Width: 3.0
+  #   Height: 2.0
+  #   Columns: 8
+  #   Rows: 5
+  #   Spacing: 0.5
+  #   Z: 0.0
+  #   StartingPen: 0
+  #   Hatch:
+  #     LineSpacing: 0.1
+
+  # - !Object
+  #   Input: !Existing { Layer: 0, Object: 0 }
+  #   Export: 'export.bin'
+
+  - !Object
+    # Input: !Rectangle { Width: 2, Height: 3 }
+    Input: !Existing { Layer: 0, Object: 0 }
+    Z: 0.8
+    # Origin: { X: 10.0, Y: 10.0 }
+    # Pen: 0
+    Array:
+      Columns: 15
+      Rows: 10
+      Spacing: 2.3
+      RandomizeOrder: True
+      StartingPen: 0
+      # PatternX: !Frequency 5000
+      # PatternY: !Power 1.0
+      # PatternX: !PulseWidth 1
+      # PatternY: !Frequency 10000
+    # Hatch:
+    #   LineSpacing: 0.01
+    ReplaceObject: 0
+
+  - !RandomizePen
+    Index: 0
+    Count: 30
+    Speed: [100, 1000, 100] # [min, max, step]
+    Power: [10, 100, 5] # [min, max, step]
+    Frequency: [20000, 1000000, 1000] # [min, max, step]
+    PulseWidth: [2, 80, 2] # [min, max, step]
+
+  # - !Object
+  #   Input: !Rectangle { Width: 10, Height: 5}
+  #   Z: 0.0
+  #   Origin: { X: 10.0, Y: 10.0 }
+  #   Pen: 0
+  #   ReplaceObject: 0
+
+  # - !Object
+  #   Input: !Existing { Layer: 0, Object: 0 }
+  #   Origin: { X: 10.0, Y: 10.0 }
+  #   ReplaceObject: 0
+
+  # - !Object
+  #   Input: !Existing { Layer: 0, Object: 1 }
+  #   Origin: { X: 10.0, Y: 10.0 }
+  #   ReplaceObject: 1
+
+  # - !Object
+  #   Input: !Circle { Radius: 3.0 }
+
+  # - !Object
+  #   Input: !Import { Path: 'export.bin' }