From e01cc0e39c887e192a7ad591ac5ff4fd37b4177d Mon Sep 17 00:00:00 2001 From: Hlars Date: Thu, 15 Aug 2024 18:34:13 +0200 Subject: [PATCH] Initial commit --- .gitignore | 8 + .vscode/settings.json | 26 + Cargo.lock | 4576 +++++++++++++++++ Cargo.toml | 21 + src/application/canvas/excellons.rs | 56 + src/application/canvas/geometries.rs | 149 + src/application/canvas/gerbers.rs | 99 + src/application/canvas/live_position.rs | 63 + src/application/canvas/mod.rs | 116 + src/application/egui.rs | 239 + src/application/mod.rs | 55 + .../panels/actions/excellon_actions.rs | 51 + .../panels/actions/geometry_actions.rs | 34 + .../panels/actions/gerber_actions.rs | 28 + src/application/panels/actions/mod.rs | 28 + src/application/panels/header.rs | 62 + src/application/panels/mod.rs | 3 + src/application/panels/sidebar.rs | 39 + src/excellon/doc.rs | 58 + src/excellon/drills.rs | 54 + src/excellon/errors.rs | 14 + src/excellon/mod.rs | 278 + src/export/dxf.rs | 17 + src/export/mod.rs | 2 + src/export/svg.rs | 77 + src/geometry/elements/circle.rs | 139 + src/geometry/elements/linepath.rs | 143 + src/geometry/elements/mod.rs | 79 + src/geometry/elements/obround.rs | 112 + src/geometry/elements/rectangle.rs | 108 + src/geometry/gerber.rs | 215 + src/geometry/helpers.rs | 154 + src/geometry/mod.rs | 109 + src/geometry/point.rs | 197 + src/geometry/union.rs | 189 + src/gerber/aperture_macros.rs | 76 + src/gerber/doc.rs | 83 + src/gerber/mod.rs | 803 +++ src/main.rs | 44 + src/outline_geometry/mod.rs | 214 + 40 files changed, 8818 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/application/canvas/excellons.rs create mode 100644 src/application/canvas/geometries.rs create mode 100644 src/application/canvas/gerbers.rs create mode 100644 src/application/canvas/live_position.rs create mode 100644 src/application/canvas/mod.rs create mode 100644 src/application/egui.rs create mode 100644 src/application/mod.rs create mode 100644 src/application/panels/actions/excellon_actions.rs create mode 100644 src/application/panels/actions/geometry_actions.rs create mode 100644 src/application/panels/actions/gerber_actions.rs create mode 100644 src/application/panels/actions/mod.rs create mode 100644 src/application/panels/header.rs create mode 100644 src/application/panels/mod.rs create mode 100644 src/application/panels/sidebar.rs create mode 100644 src/excellon/doc.rs create mode 100644 src/excellon/drills.rs create mode 100644 src/excellon/errors.rs create mode 100644 src/excellon/mod.rs create mode 100644 src/export/dxf.rs create mode 100644 src/export/mod.rs create mode 100644 src/export/svg.rs create mode 100644 src/geometry/elements/circle.rs create mode 100644 src/geometry/elements/linepath.rs create mode 100644 src/geometry/elements/mod.rs create mode 100644 src/geometry/elements/obround.rs create mode 100644 src/geometry/elements/rectangle.rs create mode 100644 src/geometry/gerber.rs create mode 100644 src/geometry/helpers.rs create mode 100644 src/geometry/mod.rs create mode 100644 src/geometry/point.rs create mode 100644 src/geometry/union.rs create mode 100644 src/gerber/aperture_macros.rs create mode 100644 src/gerber/doc.rs create mode 100644 src/gerber/mod.rs create mode 100644 src/main.rs create mode 100644 src/outline_geometry/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c3d61b --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target +/tests +*.gbr +*.svg +*.drl +*.jpg +*.dxf +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..61b2eca --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +{ + "cSpell.words": [ + "color", + "Color", + "consts", + "cutout", + "eframe", + "egui", + "emath", + "epaint", + "excellon", + "Excellon", + "excellons", + "gerbers", + "Heatsink", + "linepath", + "obround", + "Obround", + "Outlinify", + "powi", + "rect", + "Rect", + "regmatch", + "Soldermask" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f7321ee --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4576 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "accesskit" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" +dependencies = [ + "enumn", + "serde", +] + +[[package]] +name = "accesskit_consumer" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" +dependencies = [ + "accesskit", +] + +[[package]] +name = "accesskit_macos" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" +dependencies = [ + "accesskit", + "accesskit_consumer", + "async-channel", + "async-once-cell", + "atspi", + "futures-lite 1.13.0", + "once_cell", + "serde", + "zbus 3.15.2", +] + +[[package]] +name = "accesskit_windows" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" +dependencies = [ + "accesskit", + "accesskit_consumer", + "once_cell", + "paste", + "static_assertions", + "windows 0.48.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "winit", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-activity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +dependencies = [ + "android-properties", + "bitflags 2.6.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arboard" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" +dependencies = [ + "clipboard-win", + "log", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "x11rb", +] + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "ashpd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" +dependencies = [ + "async-fs 2.1.2", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "serde", + "serde_repr", + "url", + "zbus 4.4.0", +] + +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.1.0", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock 3.4.0", + "blocking", + "futures-lite 2.3.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock 3.4.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.7.2", + "rustix 0.38.34", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.3.3", + "blocking", + "futures-lite 2.3.0", +] + +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.34", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-process" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" +dependencies = [ + "async-channel", + "async-io 2.3.3", + "async-lock 3.4.0", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.3.1", + "futures-lite 2.3.0", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "async-signal" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32" +dependencies = [ + "async-io 2.3.3", + "async-lock 3.4.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.34", + "signal-hook-registry", + "slab", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus 3.15.2", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus 3.15.2", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys 0.3.5", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys 0.1.0-beta.1", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys 0.2.1", + "objc2 0.4.1", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite 2.3.0", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" + +[[package]] +name = "calloop" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +dependencies = [ + "bitflags 2.6.0", + "log", + "polling 3.7.2", + "rustix 0.38.34", + "slab", + "thiserror", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.6.0", + "log", + "polling 3.7.2", + "rustix 0.38.34", + "slab", + "thiserror", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop 0.12.4", + "rustix 0.38.34", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.34", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits 0.2.19", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipper2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e0a916e10c9df340b61739f89a146f8bf7cc4e877d50df6637fd01593ed8f8" +dependencies = [ + "clipper2c-sys", + "libc", + "thiserror", +] + +[[package]] +name = "clipper2c-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2995248c63ec82bf22c921cfd26fe407a4402b7039e045d286f7470cd83fe9e" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.5", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dxf" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9003fc9926c63a280a37f9b0ea318cc40a5da4f8ca0025beae8f4ff22082e2" +dependencies = [ + "byteorder", + "chrono", + "encoding_rs", + "enum_primitive", + "image 0.23.14", + "itertools", + "num", + "uuid 0.8.2", + "xmltree", +] + +[[package]] +name = "ecolor" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6b451ff1143f6de0f33fc7f1b68fecfd2c7de06e104de96c4514de3f5396f8" +dependencies = [ + "bytemuck", + "emath", + "serde", +] + +[[package]] +name = "eframe" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6490ef800b2e41ee129b1f32f9ac15f713233fe3bc18e241a1afe1e4fb6811e0" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image 0.25.2", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "winit", +] + +[[package]] +name = "egui" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c97e70a2768de630f161bb5392cbd3874fcf72868f14df0e002e82e06cb798" +dependencies = [ + "accesskit", + "ahash", + "emath", + "epaint", + "log", + "nohash-hasher", + "serde", +] + +[[package]] +name = "egui-wgpu" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47c7a7c707877c3362a321ebb4f32be811c0b91f7aebf345fb162405c0218b4c" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "thiserror", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4e066af341bf92559f60dbdf2020b2a03c963415349af5f3f8d79ff7a4926" +dependencies = [ + "accesskit_winit", + "ahash", + "arboard", + "egui", + "log", + "raw-window-handle 0.6.2", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2bdc8b38cfa17cc712c4ae079e30c71c00cd4c2763c9e16dc7860a02769103" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset 0.9.1", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "egui_plot" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7acc4fe778c41b91d57e04c1a2cf5765b3dc977f9f8384d2bb2eb4254855365" +dependencies = [ + "ahash", + "egui", + "emath", + "serde", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "emath" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6a21708405ea88f63d8309650b4d77431f4bc28fb9d8e6f77d3963b51249e6" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", +] + +[[package]] +name = "enumflags2" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "epaint" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0dcc0a0771e7500e94cd1cb797bd13c9f23b9409bdc3c824e2cbc562b7fa01" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "log", + "nohash-hasher", + "parking_lot", + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "error-code" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "error-stack" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe413319145d1063f080f27556fd30b1d70b01e2ba10c2a6e40d4be982ffc5d1" +dependencies = [ + "anyhow", + "rustc_version", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.1.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gerber-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecf78a269d7b4f73953f30174b18f52c1bb44f7d58f4d830a76c0ea023ad8bc" +dependencies = [ + "chrono", + "conv", + "num-rational 0.4.2", + "thiserror", + "uuid 1.10.0", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs 0.8.20", +] + +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" +dependencies = [ + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "icrate", + "libloading 0.8.5", + "objc2 0.4.1", + "once_cell", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +dependencies = [ + "cfg_aliases 0.1.1", + "glutin", + "raw-window-handle 0.5.2", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" +dependencies = [ + "gl_generator", + "windows-sys 0.48.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.6.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +dependencies = [ + "bitflags 2.6.0", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.6.0", + "com", + "libc", + "libloading 0.8.5", + "thiserror", + "widestring", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2 0.3.0", + "dispatch", + "objc2 0.4.1", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational 0.3.2", + "num-traits 0.2.19", + "png 0.16.8", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "image" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits 0.2.19", + "png 0.17.13", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.5", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.72", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libredox" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall 0.4.1", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "naga" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.6.0", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits 0.2.19", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset 0.9.1", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-bigint 0.3.3", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.3.2", + "num-traits 0.2.19", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint 0.3.3", + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits 0.2.19", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +dependencies = [ + "objc-sys 0.3.5", + "objc2-encode 3.0.0", +] + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys 0.3.5", + "objc2-encode 4.0.3", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "orbclient" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f0d54bde9774d3a51dcf281a5def240c71996bc6ca05d2c847ec8b2b216166" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "outlinify" +version = "0.1.0" +dependencies = [ + "clipper2", + "dxf", + "eframe", + "egui_plot", + "error-stack", + "gerber-types", + "lazy-regex", + "rfd", + "svg", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owned_ttf_parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.3", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +dependencies = [ + "atomic-waker", + "fastrand 2.1.0", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix 0.38.34", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" + +[[package]] +name = "quick-xml" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[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 = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rfd" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a73a7337fc24366edfca76ec521f51877b114e42dab584008209cca6719251" +dependencies = [ + "ashpd", + "block", + "dispatch", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "pollster", + "raw-window-handle 0.6.2", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.18.1", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.6.0", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.34", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.31.2", + "wayland-protocols-wlr 0.2.0", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.6.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.34", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols 0.32.3", + "wayland-protocols-wlr 0.3.3", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "svg" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "700efb40f3f559c23c18b446e8ed62b08b56b2bb3197b36d57e0470b4102779e" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.1.0", + "rustix 0.38.34", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "ttf-parser" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8686b91785aff82828ed725225925b33b4fde44c4bb15876e5f7c832724c420a" + +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.72", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wayland-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.34", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +dependencies = [ + "bitflags 2.6.0", + "rustix 0.38.34", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.6.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +dependencies = [ + "rustix 0.38.34", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" +dependencies = [ + "bitflags 2.6.0", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.32.3", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "425ba64c1e13b1c6e8c5d2541c8fac10022ca584f33da781db01b5756aef1f4e" +dependencies = [ + "block2 0.5.1", + "core-foundation", + "home", + "jni", + "log", + "ndk-context", + "objc2 0.5.2", + "objc2-foundation", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "wgpu" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "document-features", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.5", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.2", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" +dependencies = [ + "bitflags 2.6.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "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]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +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" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.29.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.6.0", + "bytemuck", + "calloop 0.12.4", + "cfg_aliases 0.1.1", + "core-foundation", + "core-graphics", + "cursor-icon", + "icrate", + "js-sys", + "libc", + "log", + "memmap2", + "ndk", + "ndk-sys", + "objc2 0.4.1", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.2", + "redox_syscall 0.3.5", + "rustix 0.38.34", + "sctk-adwaita", + "smithay-client-toolkit 0.18.1", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols 0.31.2", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.5", + "once_cell", + "rustix 0.38.34", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d491ee231a51ae64a5b762114c3ac2104b967aadba1de45c86ca42cf051513b7" + +[[package]] +name = "xdg-home" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.6.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1cb601d29fe2c2ac60a2b2e5e293994d87a1f6fa9687a31a15270f909be9c2" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "xml-rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" + +[[package]] +name = "xmltree" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8eaee9d17062850f1e6163b509947969242990ee59a35801af437abe041e70" +dependencies = [ + "xml-rs 0.7.0", +] + +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast 0.5.1", + "async-executor", + "async-fs 1.6.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process 1.8.1", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.26.4", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros 3.15.2", + "zbus_names 2.6.1", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast 0.7.1", + "async-executor", + "async-fs 2.1.2", + "async-io 2.3.3", + "async-lock 3.4.0", + "async-process 2.2.3", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.3.1", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.72", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant 3.15.2", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive 3.15.2", +] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils 1.0.1", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.72", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9d7b2f9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "outlinify" +version = "0.1.0" +edition = "2021" + +[dependencies] +eframe = "0.28.1" +egui_plot = { version = "0.28.1", features = ["serde"] } +rfd = "0.14" + +clipper2 = "0.4.1" +gerber-types = "0.3" + +svg = "0.17" +dxf = "0.5" + +lazy-regex = "3.1.0" + +tracing = "0.1" +tracing-subscriber = "0.3" +error-stack = "0.5" diff --git a/src/application/canvas/excellons.rs b/src/application/canvas/excellons.rs new file mode 100644 index 0000000..025f068 --- /dev/null +++ b/src/application/canvas/excellons.rs @@ -0,0 +1,56 @@ +use eframe::{ + egui::{Color32, Ui}, + epaint::CircleShape, +}; +use egui_plot::PlotUi; + +use crate::{ + application::Application, + geometry::{elements::Element, DrawableRaw}, +}; + +use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; + +pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) { + for (file_name, (_, excellon)) in &app.excellons { + let selected = &app.selection == file_name; + + for circle in excellon.holes.iter() { + draw_on_plot_canvas( + ui, + app, + PlotDrawer::Drawable(( + &Element::Circle(circle.to_owned()), + CanvasColour::Excellon, + selected, + )), + ); + } + } +} + +pub fn draw_excellons(ui: &mut Ui, app: &mut Application) { + for (file_name, (ex_name, excellon)) in &app.excellons { + let selected = &app.selection == file_name; + + for (i, circle) in excellon.holes.iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + circle.canvas_pos(), + ("ExcellonArea", ex_name, i), + Drawer::Closure(&|ui| { + ui.painter().add(CircleShape::filled( + circle.position.invert_y().into(), + circle.diameter as f32 / 2., + if selected { + Color32::BROWN.gamma_multiply(0.5) + } else { + Color32::BROWN + }, + )); + }), + ); + } + } +} diff --git a/src/application/canvas/geometries.rs b/src/application/canvas/geometries.rs new file mode 100644 index 0000000..6f2d580 --- /dev/null +++ b/src/application/canvas/geometries.rs @@ -0,0 +1,149 @@ +use eframe::{ + egui::{Color32, Pos2, Ui}, + epaint::{CircleShape, PathShape, PathStroke}, +}; +use egui_plot::{Line, PlotPoint, PlotPoints, PlotUi}; + +use crate::{ + application::Application, + geometry::{elements::circle::Circle, DrawableRaw}, +}; + +use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; + +pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) { + for (file_name, geo) in &app.outlines { + let selected = &app.selection == file_name; + + // draw outline path + for path in geo.paths().iter() { + draw_on_plot_canvas( + ui, + app, + PlotDrawer::Closure(&|ui| { + // draw outline path + let mut points = path + .iter() + .map(|p| [p.x(), p.y()]) + .collect::>(); + + // calculate line width according to zoom factor + let transform = ui.transform(); + let p1: Pos2 = transform.position_from_point(&PlotPoint::from(points[0])); + let p2: Pos2 = transform.position_from_point(&PlotPoint::from([ + points[0][0] - geo.stroke as f64, + points[0][1], + ])); + let width = (p1.x - p2.x).abs(); + + points.push(points[0]); + points.push(points[1]); + let line = Line::new(PlotPoints::from(points)) + .width(width) + .color(CanvasColour::Outline.to_colour32(selected)); + + ui.line(line) + }), + ); + } + + // draw point shapes + for point in geo.points().iter() { + draw_on_plot_canvas( + ui, + app, + PlotDrawer::Closure(&|ui| { + let circle = Circle::new(*point, geo.stroke.into(), None); + circle.draw_egui_plot(ui, CanvasColour::Outline, selected); + }), + ); + // draw_floating_area_on_canvas( + // ui, + // app, + // point, + // ("GeometryAreaPoint", name, i), + // Drawer::Closure(&|ui| { + // // draw point shape + // ui.painter().add(CircleShape::filled( + // point.invert_y().into(), + // geo.stroke, + // if selected { + // Color32::DARK_BLUE.gamma_multiply(0.5) + // } else { + // Color32::DARK_BLUE + // }, + // )); + // }), + // ); + } + } + + // for circle in excellon.holes.iter() { + // draw_on_plot_canvas( + // ui, + // app, + // PlotDrawer::Drawable(( + // &Element::Circle(circle.to_owned()), + // CanvasColour::Excellon, + // selected, + // )), + // ); + // } +} + +pub fn draw_geometries(ui: &mut Ui, app: &mut Application) { + for (name, geo) in &app.outlines { + let selected = &app.selection == name; + + // draw outline path + for (i, path) in geo.paths().iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + { + let origin = path.iter().next().unwrap(); + Pos2::new(origin.x() as f32, origin.y() as f32) + }, + ("GeometryAreaOutline", name, i), + Drawer::Closure(&|ui| { + // draw outline path + ui.painter().add(PathShape::closed_line( + path.iter() + .map(|v| Pos2::new(v.x() as f32, -v.y() as f32)) + .collect::>(), + PathStroke::new( + geo.stroke, + if selected { + Color32::DARK_BLUE.gamma_multiply(0.5) + } else { + Color32::DARK_BLUE + }, + ), + )); + }), + ); + } + + // draw point shapes + for (i, point) in geo.points().iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + point, + ("GeometryAreaPoint", name, i), + Drawer::Closure(&|ui| { + // draw point shape + ui.painter().add(CircleShape::filled( + point.invert_y().into(), + geo.stroke, + if selected { + Color32::DARK_BLUE.gamma_multiply(0.5) + } else { + Color32::DARK_BLUE + }, + )); + }), + ); + } + } +} diff --git a/src/application/canvas/gerbers.rs b/src/application/canvas/gerbers.rs new file mode 100644 index 0000000..9ece61d --- /dev/null +++ b/src/application/canvas/gerbers.rs @@ -0,0 +1,99 @@ +use eframe::{ + egui::{Color32, Pos2, Ui}, + epaint::{PathShape, PathStroke}, +}; +use egui_plot::{Line, PlotPoints, PlotUi}; + +use crate::{application::Application, geometry::DrawableRaw}; + +use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; + +pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) { + for (file_name, (_, geo)) in &app.gerbers { + let selected = &app.selection == file_name; + + for geometry in geo.apertures.iter() { + draw_on_plot_canvas( + ui, + app, + PlotDrawer::Drawable((geometry, CanvasColour::Copper, selected)), + ); + } + + for line in geo.paths.iter() { + draw_on_plot_canvas( + ui, + app, + PlotDrawer::Closure(&|ui| line.draw_egui_plot(ui, CanvasColour::Copper, selected)), + ); + } + + // draw union path + for path in geo.outline_union.iter() { + let mut points = path + .iter() + .map(|p| [p.x(), p.y()]) + .collect::>(); + + points.push(points[0]); + let line = Line::new(PlotPoints::from(points)) + .color(CanvasColour::CopperOutline.to_colour32(selected)); + + ui.line(line) + } + } +} + +pub fn draw_gerbers(ui: &mut Ui, app: &mut Application) { + for (file_name, (name, geo)) in &app.gerbers { + let selected = &app.selection == file_name; + + for (i, geometry) in geo.apertures.iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + geometry.canvas_pos(), + ("GerberArea", name, i), + Drawer::Drawable((geometry, selected)), + ); + } + + for (i, line) in geo.paths.iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + line.canvas_pos(), + ("LinePath", name, i), + Drawer::Closure(&|ui| line.draw_egui(ui, selected)), + ); + } + + // draw union path + for (i, path) in geo.outline_union.iter().enumerate() { + draw_floating_area_on_canvas( + ui, + app, + { + let origin = path.iter().next().unwrap(); + Pos2::new(origin.x() as f32, origin.y() as f32) + }, + ("UnionOutline", name, i), + Drawer::Closure(&|ui| { + ui.painter().add(PathShape::closed_line( + path.iter() + .map(|v| Pos2::new(v.x() as f32, -v.y() as f32)) + .collect::>(), + PathStroke::new( + 0.1_f32, + if selected { + Color32::LIGHT_RED + } else { + Color32::BLACK + }, + ), + )); + }), + ); + } + } +} diff --git a/src/application/canvas/live_position.rs b/src/application/canvas/live_position.rs new file mode 100644 index 0000000..0e62b3e --- /dev/null +++ b/src/application/canvas/live_position.rs @@ -0,0 +1,63 @@ +use eframe::egui::{self, Color32, Pos2, TextWrapMode, Ui, Vec2}; + +use crate::application::Application; + +pub fn draw_live_position(ui: &mut Ui, app: &mut Application) { + if let Some(cursor) = cursor_canvas_position(app, ui) { + let _id = egui::Area::new(app.canvas.0.with("cursor_position_box")) + .fixed_pos(app.canvas.1.min) + // .order(egui::Order::Middle) + .default_size(Vec2::new(80., 80.)) + .show(ui.ctx(), |ui| { + // let painter = ui.painter(); + // painter.add(RectShape::filled(Rect::from_min_size(rect.min, Vec2::new(80., 80.)), Rounding::ZERO, Color32::LIGHT_BLUE)); + egui::Frame::default() + .rounding(egui::Rounding::same(4.0)) + .inner_margin(egui::Margin::same(8.0)) + .stroke(ui.ctx().style().visuals.window_stroke) + .fill(Color32::from_rgba_premultiplied(0xAD, 0xD8, 0xE6, 200)) + .show(ui, |ui| { + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); + let cursor_position = app.transform.inverse() * cursor; // + rect.min.to_vec2() - app.canvas_size.min; + let cursor_position2 = app.test_transform.inverse() * cursor; // + rect.min.to_vec2() - app.canvas_size.min; + + ui.label(format!( + "x: {} {}", + (-app.transform.translation.x + cursor.x) / app.transform.scaling, + app.variables.units + )); + ui.label(format!( + "y: {} {}", + // cursor.y / app.transform.scaling + app.test_transform.translation.y, + (-app.transform.translation.y + cursor.y) / app.transform.scaling, + app.variables.units + )); + ui.label(format!( + "cursor: {:?} {}", + cursor_position, app.variables.units + )); + ui.label(format!( + "cursor2: {:?} {}", + cursor_position2, app.variables.units + )); + + ui.label(format!("{:?} - {:?}", app.transform, app.test_transform)) + }); + }); + } +} + +fn cursor_canvas_position(app: &Application, ui: &mut Ui) -> Option { + let pointer_pos = ui.ctx().input(|i| i.pointer.hover_pos()); + + if let Some(cursor) = pointer_pos { + let pos = cursor - app.canvas.1.min; + match (pos.x, pos.y) { + (x, y) if x > 0. && y > 0. => Some(pos.to_pos2()), + (_, _) => None, + } + } else { + None + } + // pointer_pos.map(|pos| (pos - app.canvas_size.min).to_pos2()) +} diff --git a/src/application/canvas/mod.rs b/src/application/canvas/mod.rs new file mode 100644 index 0000000..054a817 --- /dev/null +++ b/src/application/canvas/mod.rs @@ -0,0 +1,116 @@ +pub mod excellons; +pub mod geometries; +pub mod gerbers; +mod live_position; + +use std::hash::Hash; + +use eframe::{ + egui::{self, Color32, Pos2, Rounding, Stroke, Ui}, + epaint::RectShape, +}; +use egui_plot::PlotUi; +use excellons::{draw_excellons, draw_excellons_}; +use geometries::{draw_geometries, draw_geometries_}; +use gerbers::{draw_gerbers, draw_gerbers_}; +use live_position::draw_live_position; + +use crate::geometry::elements::Element; + +use super::Application; + +const COPPER_COLOR: Color32 = Color32::from_rgb(0xCA, 0xF6, 0x8F); +const COPPER_COLOR_SELECTED: Color32 = Color32::YELLOW; +const COPPER_OUTLINE: Color32 = Color32::BLACK; +const COPPER_OUTLINE_SELECTED: Color32 = Color32::LIGHT_RED; +const EXCELLON_COLOR: Color32 = Color32::BROWN; +const EXCELLON_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(65, 42, 42, 128); +const OUTLINE_COLOR: Color32 = Color32::DARK_BLUE; +const OUTLINE_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(0, 0, 139, 128); + +pub enum CanvasColour { + Copper, + CopperOutline, + Excellon, + Outline, +} + +impl CanvasColour { + pub fn to_colour32(&self, selected: bool) -> Color32 { + match (self, selected) { + (CanvasColour::Copper, true) => COPPER_COLOR_SELECTED, + (CanvasColour::Copper, false) => COPPER_COLOR, + (CanvasColour::CopperOutline, true) => COPPER_OUTLINE_SELECTED, + (CanvasColour::CopperOutline, false) => COPPER_OUTLINE, + (CanvasColour::Excellon, true) => EXCELLON_COLOR_SELECTED, + (CanvasColour::Excellon, false) => EXCELLON_COLOR, + (CanvasColour::Outline, true) => OUTLINE_COLOR_SELECTED, + (CanvasColour::Outline, false) => OUTLINE_COLOR, + } + } +} + +pub fn draw_canvas(ui: &mut Ui, app: &mut Application) { + egui_plot::Plot::new("ApplicationPlot") + // .view_aspect(2.0) + .data_aspect(1.0) + .show(ui, |plot_ui| { + draw_gerbers_(plot_ui, app); + draw_excellons_(plot_ui, app); + draw_geometries_(plot_ui, app); + }); + + // draw_live_position(ui, app); + + // ui.painter().add(RectShape::stroke( + // app.canvas.1, + // Rounding::same(0.), + // Stroke::new(0.2, Color32::BLACK), + // )); + + // draw_gerbers(ui, app); + // draw_excellons(ui, app); + // draw_geometries(ui, app); +} + +pub enum Drawer<'a> { + Drawable((&'a Element, bool)), + Closure(&'a dyn Fn(&mut Ui)), +} + +fn draw_floating_area_on_canvas( + ui: &mut Ui, + app: &Application, + pos: impl Into, + name: impl Hash, + drawer: Drawer, +) { + let window_layer = ui.layer_id(); + let id = egui::Area::new(app.canvas.0.with(name)) + .current_pos(pos) + // .order(egui::Order::Middle) + .show(ui.ctx(), |ui| { + ui.set_clip_rect(app.test_transform.inverse() * app.canvas.1); + match drawer { + Drawer::Drawable((t, selected)) => t.draw_egui(ui, selected), + Drawer::Closure(fun) => fun(ui), + } + }) + .response + .layer_id; + + ui.ctx().set_transform_layer(id, app.test_transform); + ui.ctx().set_sublayer(window_layer, id); +} + +pub enum PlotDrawer<'a> { + Drawable((&'a Element, CanvasColour, bool)), + Closure(&'a dyn Fn(&mut PlotUi)), +} + +fn draw_on_plot_canvas(ui: &mut PlotUi, app: &Application, drawer: PlotDrawer) { + match drawer { + PlotDrawer::Drawable((t, colour, selected)) => t.draw_egui_plot(ui, colour, selected), + PlotDrawer::Closure(fun) => fun(ui), + } +} diff --git a/src/application/egui.rs b/src/application/egui.rs new file mode 100644 index 0000000..5771a68 --- /dev/null +++ b/src/application/egui.rs @@ -0,0 +1,239 @@ +use eframe::{ + egui::{self, Vec2}, + emath::TSTransform, +}; + +use super::{ + canvas::draw_canvas, + panels::{actions::draw_action_panel, header::draw_header, sidebar::draw_sidebar}, + Application, +}; + +impl eframe::App for Application { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // let window_size = ctx.screen_rect().size(); + + draw_header(ctx, self); + + draw_sidebar(ctx, self); + + egui::CentralPanel::default().show(ctx, |ui| { + // ui.horizontal(|ui| { + // let name_label = ui.label("Your name: "); + // ui.text_edit_singleline(&mut self.name) + // .labelled_by(name_label.id); + // }); + // ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); + // if ui.button("Increment").clicked() { + // self.age += 1; + // } + // ui.label(format!("Hello '{}', age {}", self.name, self.age)); + + // // ui.image(egui::include_image!( + // // "../../../crates/egui/assets/ferris.png" + // // )); + + let pointer_pos = ui.ctx().input(|i| i.pointer.hover_pos()); + + draw_action_panel(ui, self); + + // ui.label( + // "Pan, zoom in, and zoom out with scrolling (see the plot demo for more instructions). \ + // Double click on the background to reset.", + // ); + // ui.vertical_centered(|ui| { + // ui.add(crate::egui_github_link_file!()); + // }); + // if let Some(pos) = pointer_pos { + // ui.label(format!("Translation: {:?}", self.transform.translation)); + // ui.label(format!("ScLING: {}", self.transform.scaling)); + // ui.label(format!("Canvas start: {}", self.canvas.1.min)); + // } + ui.separator(); + + // ui.allocate_ui(ui.available_size(), |ui| { + // egui::TopBottomPanel::bottom("BottomCanvas") + // .show_separator_line(false) + // .exact_height(15.) + // .show_inside(ui, |ui| {}); + // egui::SidePanel::left("LeftCanvas") + // .show_separator_line(false) + // .exact_width(15.) + // .show_inside(ui, |ui| {}); + + // egui::CentralPanel::default() + // .frame(egui::Frame::none().inner_margin(0.).outer_margin(0.)) + // .show_inside(ui, |ui| { + // let sin: egui_plot::PlotPoints = (0..1000) + // .map(|i| { + // let x = i as f64 * 0.01; + // [x, x.sin()] + // }) + // .collect(); + // let line = egui_plot::Line::new(sin); + // egui_plot::Plot::new("my_plot") + // .view_aspect(2.0) + // .data_aspect(1.0) + // .show(ui, |plot_ui| { + // super::canvas::gerbers::draw_gerbers_(plot_ui, self); + // super::canvas::excellons::draw_excellons_(plot_ui, self); + // super::canvas::geometries::draw_geometries_(plot_ui, self) + // // plot_ui.line(line); + // // for (_, (name, geo)) in &self.gerbers { + // // let path = geo.outline_union.iter().map(|path| { + // // let mut points = path + // // .iter() + // // .map(|p| [p.x(), p.y()]) + // // .collect::>(); + + // // points.push(points[0]); + + // // ( + // // egui_plot::Line::new(egui_plot::PlotPoints::from( + // // points.clone(), + // // )) + // // .color(egui::Color32::DARK_BLUE), + // // egui_plot::Polygon::new(egui_plot::PlotPoints::from( + // // points, + // // )) + // // .fill_color(egui::Color32::LIGHT_GREEN), + // // ) + // // }); + + // // for line in path { + // // plot_ui.line(line.0); + // // plot_ui.polygon(line.1); + // // } + + // // let circle_points: egui_plot::PlotPoints = (0..=400) + // // .map(|i| { + // // let t = egui::remap( + // // i as f64, + // // 0.0..=(400 as f64), + // // 0.0..=std::f64::consts::TAU, + // // ); + // // let r = 10.; + // // [r * t.cos() + 20. as f64, r * t.sin() + 20. as f64] + // // }) + // // .collect(); + + // // let poly = egui_plot::Polygon::new( + // // egui_plot::PlotPoints::from(circle_points), + // // ) + // // .fill_color(egui::Color32::LIGHT_GREEN); + // // plot_ui.polygon(poly); + // // } + // }); + + // // let (id, rect) = ui.allocate_space(ui.available_size()); + // // let response = ui.interact(rect, id, egui::Sense::click_and_drag()); + // // self.canvas = (id, rect); + // // // Allow dragging the background as well. + // // if response.dragged() { + // // self.transform.translation += response.drag_delta(); + // // } + + // // // Plot-like reset + // // if response.double_clicked() { + // // self.transform = TSTransform::default(); + // // self.transform = TSTransform::from_translation(Vec2::new( + // // rect.width() / 2., + // // rect.height() / 2., + // // )); + // // } + + // // let transform = + // // TSTransform::from_translation(ui.min_rect().left_top().to_vec2()) + // // * self.transform; + + // // if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) { + // // // Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered. + // // if response.hovered() { + // // let pointer_in_layer = transform.inverse() * pointer; + // // let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); + // // let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); + + // // // Zoom in on pointer: + // // self.transform = self.transform + // // * TSTransform::from_translation(pointer_in_layer.to_vec2()) + // // * TSTransform::from_scaling(zoom_delta) + // // * TSTransform::from_translation(-pointer_in_layer.to_vec2()); + + // // // Pan: + // // self.transform = + // // TSTransform::from_translation(pan_delta) * self.transform; + // // } + // // } + + // // self.test_transform = transform; + // }) + // }); + + // let (id, rect) = ui.allocate_space(Vec2::new(ui.available_width(), 15.)); + // ui.painter().add(RectShape::stroke( + // rect, + // Rounding::same(0.), + // egui::Stroke::new(0.2, Color32::BLACK), + // )); + + // let (id, rect) = ui.allocate_space(Vec2::new(10., ui.available_height())); + // ui.painter().add(RectShape::stroke( + // rect, + // Rounding::same(0.), + // egui::Stroke::new(0.2, Color32::BLACK), + // )); + + // let (id, rect) = ui.allocate_space(ui.available_size()); + // let response = ui.interact(rect, id, egui::Sense::click_and_drag()); + // self.canvas = (id, rect); + // // Allow dragging the background as well. + // if response.dragged() { + // self.transform.translation += response.drag_delta(); + // } + + // // Plot-like reset + // if response.double_clicked() { + // self.transform = TSTransform::default(); + // self.transform = + // TSTransform::from_translation(Vec2::new(rect.width() / 2., rect.height() / 2.)); + // } + + // let transform = + // TSTransform::from_translation(ui.min_rect().left_top().to_vec2()) * self.transform; + + // if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) { + // // Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered. + // if response.hovered() { + // let pointer_in_layer = transform.inverse() * pointer; + // let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); + // let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta); + + // // Zoom in on pointer: + // self.transform = self.transform + // * TSTransform::from_translation(pointer_in_layer.to_vec2()) + // * TSTransform::from_scaling(zoom_delta) + // * TSTransform::from_translation(-pointer_in_layer.to_vec2()); + + // // Pan: + // self.transform = TSTransform::from_translation(pan_delta) * self.transform; + // } + // } + + // self.test_transform = transform; + + // let p = ui.painter_at(rect); + // p.add(RectShape::filled( + // Rect::from_center_size(rect.center(), Vec2::new(rect.width(), 1.)), + // Rounding::ZERO, + // Color32::LIGHT_RED, + // )); + // p.add(RectShape::filled( + // Rect::from_center_size(rect.center(), Vec2::new(1., rect.height())), + // Rounding::ZERO, + // Color32::LIGHT_RED, + // )); + + draw_canvas(ui, self); + }); + } +} diff --git a/src/application/mod.rs b/src/application/mod.rs new file mode 100644 index 0000000..a8af8ad --- /dev/null +++ b/src/application/mod.rs @@ -0,0 +1,55 @@ +mod canvas; +mod egui; +pub mod panels; + +use std::collections::HashMap; + +use eframe::{ + egui::{Id, Pos2, Rect, Vec2}, + emath::TSTransform, +}; + +use crate::{ + excellon::drills::Drills, + geometry::{Geometry, Unit}, + outline_geometry::OutlineGeometry, +}; + +pub use canvas::CanvasColour; + +pub struct Application { + gerbers: HashMap, + outlines: HashMap, + excellons: HashMap, + // geometry: Geometry, + transform: TSTransform, + test_transform: TSTransform, + canvas: (Id, Rect), + selection: String, + variables: Variables, +} + +#[derive(Debug, Default)] +pub struct Variables { + laser_line_width: f32, + units: Unit, +} + +impl Application { + pub fn new() -> Self { + Self { + gerbers: HashMap::new(), + outlines: HashMap::new(), + excellons: HashMap::new(), + // geometry, + transform: TSTransform::default(), + test_transform: TSTransform::default(), + canvas: ( + Id::new("0"), + Rect::from_center_size(Pos2::new(0., 0.), Vec2::default()), + ), + selection: "".into(), + variables: Variables::default(), + } + } +} diff --git a/src/application/panels/actions/excellon_actions.rs b/src/application/panels/actions/excellon_actions.rs new file mode 100644 index 0000000..46763a7 --- /dev/null +++ b/src/application/panels/actions/excellon_actions.rs @@ -0,0 +1,51 @@ +use eframe::egui::Ui; + +use crate::{ + application::Application, + geometry::{ClipperPath, ClipperPaths, DrawableRaw}, + outline_geometry::OutlineGeometry, +}; + +use super::HOLE_MARK_DIAMETER; + +pub fn show_excellon_actions(ui: &mut Ui, app: &mut Application) { + if let Some((name, drill)) = app.excellons.get(&app.selection) { + if ui.button("Generate cut out").clicked() { + let path = drill + .holes + .iter() + .map(|hole| hole.outline.clone()) + .collect::>() + .into(); + app.outlines.insert( + format!("{name}-CutOut"), + OutlineGeometry::new_no_inflate( + &path, + 0.1, + app.variables.units, + name, + path.bounds(), + ), + ); + } + if ui.button("Generate hole mark").clicked() { + app.outlines.insert( + format!("{name}-HoleMark"), + OutlineGeometry::point_marker( + drill.holes.iter().map(|c| c.canvas_pos()).collect(), + HOLE_MARK_DIAMETER, + app.variables.units, + name, + ClipperPaths::from( + drill + .holes + .iter() + .map(|hole| hole.outline.clone()) + .collect::>(), + ) + .bounds(), + ), + ); + } + } +} diff --git a/src/application/panels/actions/geometry_actions.rs b/src/application/panels/actions/geometry_actions.rs new file mode 100644 index 0000000..0bed6d5 --- /dev/null +++ b/src/application/panels/actions/geometry_actions.rs @@ -0,0 +1,34 @@ +use eframe::egui::{ComboBox, Ui}; + +use crate::{application::Application, export::svg::SVGConverter}; + +pub fn show_geometry_actions(ui: &mut Ui, app: &mut Application) { + if let Some(outline) = app.outlines.get_mut(&app.selection) { + ui.label("BoundingBox"); + ComboBox::from_label("Select one!") + .selected_text(format!("{:?}", outline.bounds_from)) + .show_ui(ui, |ui| { + for (key, (name, _)) in app.gerbers.iter() { + if ui + .selectable_value(&mut outline.bounds_from, name.into(), name) + .clicked() + { + if let Some((_, box_geo)) = app.gerbers.get(key) { + outline.bounding_box = box_geo.outline_union.bounds(); + } + } + } + }); + + if ui.button("Save as SVG").clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_title("Save as SVG") + .set_file_name(app.selection.to_string()) + .add_filter("SVG", &["svg", "SVG"]) + .save_file() + { + SVGConverter::export(outline, &path); + } + } + } +} diff --git a/src/application/panels/actions/gerber_actions.rs b/src/application/panels/actions/gerber_actions.rs new file mode 100644 index 0000000..2495bcf --- /dev/null +++ b/src/application/panels/actions/gerber_actions.rs @@ -0,0 +1,28 @@ +use eframe::egui::{DragValue, Ui}; + +use crate::{application::Application, outline_geometry::OutlineGeometry}; + +pub fn show_gerber_actions(ui: &mut Ui, app: &mut Application) { + if let Some((name, geo)) = app.gerbers.get(&app.selection) { + ui.horizontal(|ui| { + ui.add( + DragValue::new(&mut app.variables.laser_line_width) + .range(0.1..=10.) + .suffix(geo.units), + ); + + if ui.button("Generate Isolation").clicked() { + app.outlines.insert( + format!("{name}-Iso"), + OutlineGeometry::new( + &geo.outline_union, + app.variables.laser_line_width, + geo.units, + name, + geo.outline_union.bounds(), + ), + ); + } + }); + } +} diff --git a/src/application/panels/actions/mod.rs b/src/application/panels/actions/mod.rs new file mode 100644 index 0000000..830eb13 --- /dev/null +++ b/src/application/panels/actions/mod.rs @@ -0,0 +1,28 @@ +mod excellon_actions; +mod geometry_actions; +mod gerber_actions; + +use eframe::egui::{self, Ui, Vec2}; +use excellon_actions::show_excellon_actions; +use geometry_actions::show_geometry_actions; +use gerber_actions::show_gerber_actions; + +use crate::application::Application; + +pub const HOLE_MARK_DIAMETER: f32 = 0.1; + +pub fn draw_action_panel(ui: &mut Ui, app: &mut Application) { + let (id, rect) = ui.allocate_space(Vec2::new(ui.available_width(), 100.)); + + egui::Area::new(id) + .default_width(rect.width()) + .default_height(rect.height()) + .movable(false) + .show(ui.ctx(), |ui| { + ui.heading("Actions"); + + show_gerber_actions(ui, app); + show_excellon_actions(ui, app); + show_geometry_actions(ui, app); + }); +} diff --git a/src/application/panels/header.rs b/src/application/panels/header.rs new file mode 100644 index 0000000..4242db8 --- /dev/null +++ b/src/application/panels/header.rs @@ -0,0 +1,62 @@ +use std::{fs::File, io::BufReader}; + +use eframe::egui; + +use crate::{ + application::Application, + excellon::{drills::Drills, parse_excellon}, + geometry::Geometry, + gerber::parse_gerber, +}; + +pub fn draw_header(ctx: &egui::Context, app: &mut Application) { + egui::TopBottomPanel::top("top_panel") + .exact_height(40.) + .show(ctx, |ui| { + ui.horizontal(|ui| { + if ui.button("Open Gerber").clicked() { + if let Some(paths) = rfd::FileDialog::new() + .add_filter("Gerber", &["GBR ", "gbr", "GB", "geb"]) + .pick_files() + { + // self.picked_path = Some(path.display().to_string()); + for path in paths { + // TODO remove all unwraps + if let Ok(file) = File::open(&path) { + let gerber = parse_gerber(BufReader::new(file)); + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + app.gerbers.insert( + path.to_str().unwrap().into(), + (name, Geometry::from(gerber).to_unit(app.variables.units)), + ); + } else { + // TODO show error + }; + } + } + } + + if ui.button("Open Excellon").clicked() { + if let Some(paths) = rfd::FileDialog::new() + .add_filter("Excellon", &["DRL ", "drl"]) + .pick_files() + { + for path in paths { + // TODO remove all unwraps + if let Ok(file) = File::open(&path) { + let excellon = parse_excellon(BufReader::new(file)).unwrap(); + let drills: Drills = excellon.into(); + let name = path.file_name().unwrap().to_str().unwrap().to_string(); + app.excellons.insert( + path.to_str().unwrap().into(), + (name, drills.to_unit(app.variables.units)), + ); + } else { + // TODO show error + }; + } + } + } + }) + }); +} diff --git a/src/application/panels/mod.rs b/src/application/panels/mod.rs new file mode 100644 index 0000000..fac9f3e --- /dev/null +++ b/src/application/panels/mod.rs @@ -0,0 +1,3 @@ +pub mod actions; +pub mod header; +pub mod sidebar; diff --git a/src/application/panels/sidebar.rs b/src/application/panels/sidebar.rs new file mode 100644 index 0000000..c271470 --- /dev/null +++ b/src/application/panels/sidebar.rs @@ -0,0 +1,39 @@ +use eframe::egui::{self, CollapsingHeader}; + +use crate::{application::Application, APP_NAME}; + +pub fn draw_sidebar(ctx: &egui::Context, app: &mut Application) { + egui::SidePanel::left("left_panel") + .exact_width(230.) + .show(ctx, |ui| { + ui.heading(APP_NAME); + CollapsingHeader::new("Gerber") + .default_open(true) + .show(ui, |ui| { + for (key, (name, _)) in app.gerbers.iter() { + ui.selectable_value(&mut app.selection, key.to_string(), name); + } + }); + + CollapsingHeader::new("Excellon") + .default_open(true) + .show(ui, |ui| { + for (key, (name, _)) in app.excellons.iter() { + ui.selectable_value(&mut app.selection, key.to_string(), name); + } + }); + + CollapsingHeader::new("Geometry") + .default_open(true) + .show(ui, |ui| { + for (key, _) in app.outlines.iter() { + ui.selectable_value(&mut app.selection, key.to_string(), key); + } + }); + let (id, rect) = ui.allocate_space(ui.available_size()); + let response = ui.interact(rect, id, egui::Sense::click_and_drag()); + if response.clicked() { + app.selection = "".into(); + } + }); +} diff --git a/src/excellon/doc.rs b/src/excellon/doc.rs new file mode 100644 index 0000000..d0edf4f --- /dev/null +++ b/src/excellon/doc.rs @@ -0,0 +1,58 @@ +use std::collections::HashMap; + +use crate::geometry::{point::Point, Unit}; + +#[derive(Debug, PartialEq)] +// Representation of Gerber document +pub struct ExcellonDoc { + // // unit type, defined once per document + pub units: Unit, + pub zeroes: Zeroes, + // // format specification for coordinates, defined once per document + // pub format_specification: Option, + // /// map of apertures which can be used in draw commands later on in the document. + /// map of tools which can be used in draw commands later on in the document. + pub tools: HashMap, + pub commands: Vec, +} + +impl ExcellonDoc { + pub fn new() -> Self { + Self { + units: Unit::Inches, + zeroes: Zeroes::Leading, + tools: HashMap::new(), + commands: Vec::new(), + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Zeroes { + Leading, + Trailing, +} + +#[derive(Debug, PartialEq)] +pub struct Tool { + pub diameter: f64, +} + +impl Tool { + pub fn new(diameter: f64) -> Self { + Self { diameter } + } +} + +#[derive(Debug, PartialEq)] +pub enum Command { + FunctionCode(FunctionCode), +} + +#[derive(Debug, PartialEq)] +pub enum FunctionCode { + EndOfFile, + SelectTool(u32), + Comment(String), + Drill(Point), +} diff --git a/src/excellon/drills.rs b/src/excellon/drills.rs new file mode 100644 index 0000000..88beca8 --- /dev/null +++ b/src/excellon/drills.rs @@ -0,0 +1,54 @@ +use tracing::debug; + +use crate::{ + excellon::doc::{Command, FunctionCode}, + geometry::{elements::circle::Circle, Unit}, +}; + +use super::doc::ExcellonDoc; + +pub struct Drills { + pub holes: Vec, + pub units: Unit, +} + +impl From for Drills { + fn from(doc: ExcellonDoc) -> Self { + // working variables + let mut selected_tool = None; + let mut holes = Vec::new(); + + for command in &doc.commands { + match command { + Command::FunctionCode(code) => match code { + FunctionCode::EndOfFile => {} + FunctionCode::SelectTool(id) => selected_tool = doc.tools.get(id), + FunctionCode::Comment(c) => debug!(c), + FunctionCode::Drill(p) => { + if let Some(tool) = selected_tool { + holes.push(Circle::new(*p, tool.diameter, None)) + } + } + }, + } + } + + Self { + units: doc.units, + holes, + } + } +} + +impl Drills { + pub fn to_unit(&self, unit: Unit) -> Self { + Self { + units: unit, + holes: self + .holes + .iter() + .map(|hole| hole.to_unit(self.units, unit)) + .collect(), + } + } +} diff --git a/src/excellon/errors.rs b/src/excellon/errors.rs new file mode 100644 index 0000000..321d804 --- /dev/null +++ b/src/excellon/errors.rs @@ -0,0 +1,14 @@ +use std::fmt; + +use error_stack::Context; + +#[derive(Debug)] +pub struct ExcellonError; + +impl fmt::Display for ExcellonError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str("Error parsing Excellon document") + } +} + +impl Context for ExcellonError {} diff --git a/src/excellon/mod.rs b/src/excellon/mod.rs new file mode 100644 index 0000000..88c4306 --- /dev/null +++ b/src/excellon/mod.rs @@ -0,0 +1,278 @@ +mod doc; +pub mod drills; +mod errors; + +use std::{ + io::{BufRead, BufReader, Read}, + str::Chars, +}; + +use doc::{Command, ExcellonDoc, FunctionCode, Tool, Zeroes}; +use error_stack::{Report, ResultExt}; +use errors::ExcellonError; +use lazy_regex::{regex, Regex}; +use tracing::{debug, error}; + +use crate::geometry::{point::Point, Unit}; + +pub fn parse_excellon(reader: BufReader) -> Result> { + let mut excellon_doc = ExcellonDoc::new(); + + // Number format and units + // INCH uses 6 digits + // METRIC uses 5/6 + let re_units = regex!("^(INCH|METRIC)(?:,([TL])Z)?$"); + + let re_toolset = regex!(r#"^T(\d+)(?:.*C(\d*\.?\d*))?"#); + + let re_coordinates = regex!(r#"X?(-?[0-9.]+)?Y?(-?[0-9.]+)?"#); + + // Parse coordinates + let re_leading_zeroes = regex!(r#"^[-\+]?(0*)(\d*)"#); + + let mut inside_header = false; + + for (index, line) in reader.lines().enumerate() { + let raw_line = line.change_context(ExcellonError)?; + let line = raw_line.trim(); + + // Show the line + debug!("{}. {}", index + 1, &line); + + if !line.is_empty() { + let mut line_chars = line.chars(); + + if inside_header { + match get_next_char(&mut line_chars)? { + 'I' => parse_units(line, re_units, &mut excellon_doc), + 'M' => match get_next_char(&mut line_chars)? { + 'E' => parse_units(line, re_units, &mut excellon_doc), + '9' => inside_header = false, // End of header + _ => line_parse_failure(line, index), + }, + 'T' => parse_toolset(line, re_toolset, &mut excellon_doc)?, + '%' => inside_header = false, // End of header + ';' => parse_comment(line_chars, &mut excellon_doc), + _ => line_parse_failure(line, index), + } + } else { + match get_next_char(&mut line_chars)? { + 'G' => {} + 'M' => match get_next_char(&mut line_chars)? { + '1' => {} + '3' => excellon_doc + .commands + .push(Command::FunctionCode(FunctionCode::EndOfFile)), + '4' => match get_next_char(&mut line_chars)? { + '8' => inside_header = true, + _ => line_parse_failure(line, index), + }, + _ => line_parse_failure(line, index), + }, + 'T' => { + // Select Tool + let id = line_chars + .as_str() + .parse::() + .change_context(ExcellonError)?; + excellon_doc + .commands + .push(Command::FunctionCode(FunctionCode::SelectTool(id))) + } + 'X' | 'Y' => { + if let Some(reg_match) = re_coordinates.captures(line) { + let x = reg_match + .get(1) + .ok_or(ExcellonError) + .attach_printable("No x coordinate present")? + .as_str(); + let y = reg_match + .get(2) + .ok_or(ExcellonError) + .attach_printable("No y coordinate present")? + .as_str(); + + let x = if x.contains(".") { + x.parse::().change_context(ExcellonError)? + } else { + parse_number(x, &excellon_doc, re_leading_zeroes)? + }; + + let y = if y.contains(".") { + y.parse::().change_context(ExcellonError)? + } else { + parse_number(y, &excellon_doc, re_leading_zeroes)? + }; + + excellon_doc + .commands + .push(Command::FunctionCode(FunctionCode::Drill(Point::new(x, y)))) + } else { + error!("Could not parse Coordinates") + }; + } + ';' => parse_comment(line_chars, &mut excellon_doc), + _ => line_parse_failure(line, index), + } + } + } + } + + Ok(excellon_doc) +} + +fn get_next_char(chars: &mut Chars) -> Result { + chars.next().ok_or(ExcellonError) +} + +// parse a Excellon Comment (e.g. '; This line is a comment and is ignored') +fn parse_comment(line: Chars, doc: &mut ExcellonDoc) { + let comment = line.as_str(); + doc.commands + .push(Command::FunctionCode(FunctionCode::Comment( + comment.to_string(), + ))); +} + +// parse a Excellon unit statement (e.g. 'METRIC,LZ') +fn parse_units(line: &str, re: &Regex, doc: &mut ExcellonDoc) { + // Set the unit type + if let Some(reg_match) = re.captures(line) { + // Parse Unit + doc.units = match reg_match.get(1).map(|s| s.as_str()) { + Some("METRIC") => Unit::Millimeters, + Some("INCH") => Unit::Inches, + _ => { + error!("Incorrect excellon units format"); + doc.units + } + }; + // Parse Zeroes Type (Leading | Trailing) + doc.zeroes = match reg_match.get(2).map(|s| s.as_str()) { + Some("L") => doc::Zeroes::Leading, + Some("T") => doc::Zeroes::Trailing, + _ => { + error!("Incorrect excellon zeroes format"); + doc.zeroes + } + } + } +} + +fn parse_toolset( + line: &str, + re: &Regex, + doc: &mut ExcellonDoc, +) -> Result<(), Report> { + if let Some(reg_match) = re.captures(line) { + if let (Some(id), Some(size)) = ( + reg_match.get(1).map(|s| s.as_str()), + reg_match.get(2).map(|s| s.as_str()), + ) { + doc.tools.insert( + id.parse::().change_context(ExcellonError)?, + Tool::new(size.parse::().change_context(ExcellonError)?), + ); + } + } + Ok(()) +} + +// print a simple message in case the parser hits a dead end +fn line_parse_failure(line: &str, index: usize) { + error!( + "## Excellon Parser ## Cannot parse line:\n{} | {}", + index, line + ) +} + +// Parses coordinate numbers without period. +fn parse_number( + number_str: &str, + doc: &ExcellonDoc, + lz: &Regex, +) -> Result> { + match doc.zeroes { + Zeroes::Leading => { + // With leading zeros, when you type in a coordinate, + // the leading zeros must always be included. Trailing zeros + // are unneeded and may be left off. The CNC-7 will automatically add them. + // r'^[-\+]?(0*)(\d*)' + // 6 digits are divided by 10^4 + // If less than size digits, they are automatically added, + // 5 digits then are divided by 10^3 and so on. + + let re_match = lz + .captures(number_str) + .ok_or(ExcellonError) + .attach_printable("Leading zeroes regex does not match")?; + let (g1, g2) = ( + re_match + .get(1) + .ok_or(ExcellonError) + .attach_printable("Regex MatchGroup 2")? + .as_str(), + re_match + .get(2) + .ok_or(ExcellonError) + .attach_printable("Regex MatchGroup 2")? + .as_str(), + ); + + Ok(number_str.parse::().change_context(ExcellonError)? + / (10_f64.powi( + g1.len() as i32 + g2.len() as i32 + - match doc.units { + Unit::Inches => 2, + Unit::Millimeters => 3, + }, + ))) + } + Zeroes::Trailing => { + // Trailing + // You must show all zeros to the right of the number and can omit + // all zeros to the left of the number. The CNC-7 will count the number + // of digits you typed and automatically fill in the missing zeros. + Ok(number_str.parse::().change_context(ExcellonError)? + / match doc.units { + Unit::Millimeters => 1000., // Metric is 000.000 + Unit::Inches => 10000., // Inches is 00.0000 + }) + } + } +} + +#[cfg(test)] +mod tests { + use std::fs::File; + + use super::*; + + #[test] + fn parse_excellon_file() { + let excellon = r#"; This line is a comment and is ignored + ; The next line starts the "header": + M48 + ; Units and number format: + INCH,LZ + ; One tool is defined with diameter 0.04 inches, + ; drill rate of 300 inches/minute and 55000 RPM. + T1C.04F300S55 + ; End of header, M95 or %, and beginning of body: + M95 + + ; Use tool 1 defined in the header + T1 + ; Drill at points (123.45, 234.5) and (12.345, 234.5): + X12345Y23450 + X012345Y234500 + ; End of program; + M30"#; + let file = File::open("./FirstPCB-PTH.drl").unwrap(); + let _reader = BufReader::new(file); + + let excellon = parse_excellon(BufReader::new(excellon.as_bytes())); + // let excellon = parse_excellon(reader); + println!("{:#?}", excellon); + } +} diff --git a/src/export/dxf.rs b/src/export/dxf.rs new file mode 100644 index 0000000..b5987f3 --- /dev/null +++ b/src/export/dxf.rs @@ -0,0 +1,17 @@ +use dxf::{ + entities::{Entity, EntityType, Line}, + Drawing, +}; + +use crate::geometry::Geometry; + +pub struct DXFConverter; + +impl DXFConverter { + pub fn export(geometry: &Geometry, file: &str) { + let mut drawing = Drawing::new(); + let added_entity_ref = drawing.add_entity(Entity::new(EntityType::Line(Line::default()))); + // `added_entity_ref` is a reference to the newly added entity + drawing.save_file("./file.dxf").unwrap(); + } +} diff --git a/src/export/mod.rs b/src/export/mod.rs new file mode 100644 index 0000000..baf5bec --- /dev/null +++ b/src/export/mod.rs @@ -0,0 +1,2 @@ +pub mod dxf; +pub mod svg; diff --git a/src/export/svg.rs b/src/export/svg.rs new file mode 100644 index 0000000..4048020 --- /dev/null +++ b/src/export/svg.rs @@ -0,0 +1,77 @@ +use svg::{ + node::element::{path::Data, Circle, Path}, + Document, +}; + +use crate::{application::panels::actions::HOLE_MARK_DIAMETER, outline_geometry::OutlineGeometry}; + +pub struct SVGConverter; + +impl SVGConverter { + pub fn export(geo: &OutlineGeometry, file: &std::path::Path) { + let view_box = geo.bounding_box; + + let data: Vec = geo + .paths() + .iter() + .filter_map(|path| { + if path.len() > 1 { + let mut iter = path.iter().map(|point| (point.x(), -point.y())); + let first = iter.next().unwrap(); + let mut data = Data::new().move_to(first); + for point in iter { + data = data.line_to(point); + } + + data = data.close(); + + Some(data) + } else { + None + } + }) + .collect(); + + let paths = data.into_iter().map(|data| { + Path::new() + .set("fill", "none") + .set("stroke", "black") + .set("stroke-width", geo.stroke) + .set("d", data) + }); + + let points = geo.points().into_iter().map(|p| { + Circle::new() + .set("r", HOLE_MARK_DIAMETER) + .set("cx", p.x) + .set("cy", p.invert_y().y) + .set("fill", "black") + }); + + let document_width = view_box.max.x() - view_box.min.x(); + let document_height = view_box.max.y() - view_box.min.y(); + + let mut document = Document::new() + .set( + "viewBox", + ( + view_box.min.x(), + -view_box.min.y() - document_height, + document_width, + document_height, + ), + ) + .set("width", format!("{document_width}{}", geo.unit)) + .set("height", format!("{document_height}{}", geo.unit)); + + for path in paths { + document = document.add(path); + } + + for point in points { + document = document.add(point); + } + + svg::save(file, &document).unwrap(); + } +} diff --git a/src/geometry/elements/circle.rs b/src/geometry/elements/circle.rs new file mode 100644 index 0000000..bd4b99e --- /dev/null +++ b/src/geometry/elements/circle.rs @@ -0,0 +1,139 @@ +use std::f64::consts::{PI, TAU}; + +use eframe::{ + egui::{remap, Stroke, Ui}, + epaint::CircleShape, +}; +use egui_plot::{PlotPoints, PlotUi, Polygon}; + +use crate::{ + application::CanvasColour, + geometry::{ClipperPath, ClipperPaths}, +}; + +use super::super::{ + helpers::{create_circular_path, CircleSegment}, + point::{convert_to_unit, Point}, + DrawableRaw, Unit, +}; + +#[derive(Debug, Clone)] +pub struct Circle { + pub position: Point, + pub diameter: f64, + pub hole_diameter: Option, + pub outline: ClipperPath, +} + +impl Circle { + pub fn new(position: impl Into, diameter: f64, hole_diameter: Option) -> Self { + let position = position.into(); + Self { + position: position, + diameter, + hole_diameter, + outline: create_circular_path(&position, diameter, CircleSegment::Full).into(), + } + } + pub fn from_aperture_circle(aperture: &gerber_types::Circle, position: Point) -> Self { + Self::new(position, aperture.diameter, aperture.hole_diameter) + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + let position = self.position.to_unit(origin, to); + let diameter = convert_to_unit(self.diameter, origin, to); + Self { + position, + diameter, + outline: create_circular_path(&position, diameter, CircleSegment::Full).into(), + hole_diameter: self.hole_diameter.map(|hd| convert_to_unit(hd, origin, to)), + } + } + + fn draw_circle(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) { + // let n = 512; + // let circle_points: PlotPoints = (0..=n) + // .map(|i| { + // let t = remap(i as f64, 0.0..=(n as f64), 0.0..=TAU); + // let r = self.diameter / 2.; + // [ + // r * t.cos() + self.position.x as f64, + // r * t.sin() + self.position.y as f64, + // ] + // }) + // .collect(); + let circle_points = PlotPoints::from(Self::circle_segment_points( + self.position, + self.diameter, + 1.0, + 0.0, + )); + + ui.polygon( + Polygon::new(circle_points) + .fill_color(colour.to_colour32(selected)) + .stroke(Stroke::NONE), + ); + } + + pub fn circle_segment_points( + position: Point, + diameter: f64, + segment_width: f64, + rotation: f64, + ) -> Vec<[f64; 2]> { + let segment_width = segment_width.clamp(0.0, 1.0); + let n = (512. * segment_width) as i32; + let circle_points = (0..=n) + .map(|i| { + let t = remap(i as f64, 0.0..=(n as f64), 0.0..=TAU * segment_width) + + rotation * (PI / 180.); + let r = diameter / 2.; + [r * t.cos() + position.x, r * t.sin() + position.y] + }) + .collect(); + + circle_points + } +} + +impl DrawableRaw for Circle { + fn canvas_pos(&self) -> Point { + self.position + } + + fn draw_egui(&self, ui: &mut Ui, selected: bool) { + ui.painter().add(CircleShape::filled( + self.position.invert_y().into(), + self.diameter as f32 / 2., + CanvasColour::Copper.to_colour32(selected), + )); + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + // let circle_points: Vec<[f64; 2]> = + // create_circular_path(&self.position, self.diameter, CircleSegment::Full) + // .iter() + // .map(|(x, y)| [*x, *y]) + // .collect(); + + // let polygon = Polygon::new(PlotPoints::from(circle_points)) + // .fill_color(if selected { + // COPPER_COLOR_SELECTED + // } else { + // COPPER_COLOR + // }) + // .stroke(Stroke::new(0., Color32::TRANSPARENT)); + + // ui.polygon(polygon); + self.draw_circle(ui, colour, selected); + } + + fn to_paths(&self) -> ClipperPaths { + ClipperPaths::new(vec![self.outline.clone()]) + } + + fn outline(&self) -> ClipperPath { + self.outline.clone() + } +} diff --git a/src/geometry/elements/linepath.rs b/src/geometry/elements/linepath.rs new file mode 100644 index 0000000..36e8fac --- /dev/null +++ b/src/geometry/elements/linepath.rs @@ -0,0 +1,143 @@ +use std::f64::consts::PI; + +use eframe::{ + egui::{Pos2, Stroke, Ui}, + epaint::{CircleShape, PathShape, PathStroke}, +}; +use egui_plot::{PlotPoints, Polygon}; + +use crate::{ + application::CanvasColour, + geometry::{helpers::semi_circle, ClipperPath, ClipperPaths}, +}; + +use super::super::{ + point::{convert_to_unit, Point}, + DrawableRaw, Unit, +}; + +#[derive(Debug, Clone)] +pub struct LinePath { + pub points: Vec, + diameter: f64, + pub outline: ClipperPaths, +} + +impl LinePath { + pub fn new() -> Self { + Self { + points: Vec::new(), + diameter: 0., + outline: ClipperPaths::new(vec![]), + } + } + + pub fn is_empty(&self) -> bool { + self.points.is_empty() + } + + pub fn add(&mut self, point: Point) { + self.points.push(point); + } + + pub fn set_stroke(&mut self, width: f64) { + self.diameter = width + } + + pub fn finalize(&mut self, stroke_width: f64) { + self.diameter = stroke_width; + self.outline = self.create_outline(); + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + let mut converted = Self { + points: self.points.iter().map(|p| p.to_unit(origin, to)).collect(), + diameter: convert_to_unit(self.diameter, origin, to), + outline: ClipperPaths::new(vec![]), + }; + converted.outline = converted.create_outline(); + + converted + } + + fn create_outline(&self) -> ClipperPaths { + let mut paths: Vec = Vec::new(); + + for (index, point) in self.points.iter().enumerate() { + if index > 0 { + paths.push( + create_outline_between_points(self.points[index - 1], *point, self.diameter) + .into(), + ) + } + } + + ClipperPaths::new(paths) + } +} + +impl DrawableRaw for &LinePath { + fn canvas_pos(&self) -> Point { + if let Some(point) = self.points.first() { + point.to_owned() + } else { + Point::new(0., 0.) + } + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + if let Some(path) = self.outline.get(0) { + let mut points: Vec<[f64; 2]> = path.iter().map(|p| [p.x(), p.y()]).collect(); + points.push(points[0]); + + let poly = Polygon::new(PlotPoints::from(points)) + .fill_color(colour.to_colour32(selected)) + .stroke(Stroke::NONE); + ui.polygon(poly); + } + } + + fn draw_egui(&self, ui: &mut Ui, selected: bool) { + ui.painter().add(PathShape::line( + self.points + .iter() + .map(|v| (*v).invert_y().into()) + .collect::>(), + PathStroke::new( + self.diameter as f32, + CanvasColour::Copper.to_colour32(selected), + ), + )); + + for point in &self.points { + ui.painter().add(CircleShape::filled( + point.invert_y().into(), + self.diameter as f32 / 2., + CanvasColour::Copper.to_colour32(selected), + )); + } + } + + fn to_paths(&self) -> ClipperPaths { + self.outline.clone() + } + + fn outline(&self) -> ClipperPath { + self.outline.first().unwrap().clone() + } +} + +fn create_outline_between_points(point1: Point, point2: Point, width: f64) -> Vec<(f64, f64)> { + let line_vec = point2 - point1; // vector between start and end of line + let normalized = line_vec.normalize(); + + let angle = + (normalized.x).acos() * (180. / PI) * if normalized.y < 0. { -1. } else { 1. } + 90.; + + let mut outline: Vec<(f64, f64)> = Vec::new(); + + outline.append(&mut semi_circle(point1, width, angle)); + outline.append(&mut semi_circle(point2, width, angle + 180.)); + + outline +} diff --git a/src/geometry/elements/mod.rs b/src/geometry/elements/mod.rs new file mode 100644 index 0000000..858228d --- /dev/null +++ b/src/geometry/elements/mod.rs @@ -0,0 +1,79 @@ +use circle::Circle; +use eframe::egui::Ui; +use egui_plot::PlotUi; +use linepath::LinePath; +use obround::Obround; +use rectangle::Rectangle; + +use crate::application::CanvasColour; + +use super::{point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit}; + +pub mod circle; +pub mod linepath; +pub mod obround; +pub mod rectangle; + +#[derive(Debug)] +pub enum Element { + Circle(Circle), + Rectangle(Rectangle), + Line(LinePath), + Obround(Obround), +} + +impl Element { + pub fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool) { + match self { + Element::Circle(c) => c.draw_egui_plot(ui, colour, selected), + Element::Rectangle(r) => r.draw_egui_plot(ui, colour, selected), + Element::Line(l) => l.draw_egui_plot(ui, colour, selected), + Element::Obround(o) => o.draw_egui_plot(ui, colour, selected), + } + } + + pub fn draw_egui(&self, ui: &mut Ui, selected: bool) { + match self { + Element::Circle(c) => c.draw_egui(ui, selected), + Element::Rectangle(r) => r.draw_egui(ui, selected), + Element::Line(l) => l.draw_egui(ui, selected), + Element::Obround(o) => o.draw_egui(ui, selected), + } + } + + pub fn canvas_pos(&self) -> Point { + match self { + Element::Circle(c) => c.canvas_pos(), + Element::Rectangle(r) => r.canvas_pos(), + Element::Line(l) => l.canvas_pos(), + Element::Obround(o) => o.canvas_pos(), + } + } + + pub fn to_paths(&self) -> ClipperPaths { + match self { + Element::Circle(c) => c.to_paths(), + Element::Rectangle(r) => r.to_paths(), + Element::Line(l) => l.to_paths(), + Element::Obround(o) => o.to_paths(), + } + } + + pub fn outline(&self) -> ClipperPath { + match self { + Element::Circle(c) => c.outline(), + Element::Rectangle(r) => r.outline(), + Element::Line(l) => l.outline(), + Element::Obround(o) => o.outline(), + } + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + match self { + Element::Circle(c) => Element::Circle(c.to_unit(origin, to)), + Element::Rectangle(r) => Element::Rectangle(r.to_unit(origin, to)), + Element::Line(l) => Element::Line(l.to_unit(origin, to)), + Element::Obround(o) => Element::Obround(o.to_unit(origin, to)), + } + } +} diff --git a/src/geometry/elements/obround.rs b/src/geometry/elements/obround.rs new file mode 100644 index 0000000..8a75b2a --- /dev/null +++ b/src/geometry/elements/obround.rs @@ -0,0 +1,112 @@ +use eframe::{ + egui::{Rect, Rounding, Stroke, Ui, Vec2}, + epaint::RectShape, +}; +use egui_plot::{PlotPoints, Polygon}; + +use crate::{ + application::CanvasColour, + geometry::{ClipperPath, ClipperPaths}, +}; + +use super::rectangle::Rectangle; +use super::{ + super::{ + helpers::{create_circular_path, semi_circle, CircleSegment}, + point::{convert_to_unit, Point}, + DrawableRaw, Unit, + }, + circle::Circle, +}; + +#[derive(Debug)] +pub struct Obround { + position: Point, + x: f64, + y: f64, + rounding: f64, + outline: ClipperPath, + rectangle: Rectangle, + hole_diameter: Option, +} + +impl Obround { + pub fn new(position: Point, x: f64, y: f64, hole_diameter: Option) -> Self { + let diameter = if x < y { x } else { y }; + let outline = if x == y { + create_circular_path(&position, x, CircleSegment::Full) + } else { + let mut path: Vec<(f64, f64)> = Vec::new(); + // check if obround is round to x or y + if x < y { + // round on y axis + path.append(&mut semi_circle(position - Point::new(0., y / 4.), x, 180.)); + path.append(&mut semi_circle(position + Point::new(0., y / 4.), x, 0.)); + } else { + // TODO round on x axis -> check for correctness!!!!!!! + path.append(&mut semi_circle(position - Point::new(0., x / 4.), y, 270.)); + path.append(&mut semi_circle(position + Point::new(0., x / 4.), y, 90.)); + } + + path + }; + + Self { + position, + x: x, + y: y, + rounding: diameter, + outline: outline.into(), + rectangle: Rectangle::new(position, x, y, hole_diameter), + hole_diameter: hole_diameter, + } + } + + pub fn from_aperture_obround(aperture: &gerber_types::Rectangular, position: Point) -> Self { + Self::new(position, aperture.x, aperture.y, aperture.hole_diameter) + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + Self::new( + self.position.to_unit(origin, to), + convert_to_unit(self.x, origin, to), + convert_to_unit(self.y, origin, to), + self.hole_diameter.map(|d| convert_to_unit(d, origin, to)), + ) + } +} + +impl DrawableRaw for Obround { + fn canvas_pos(&self) -> Point { + self.position + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + let mut points: Vec<[f64; 2]> = self.outline.iter().map(|p| [p.x(), p.y()]).collect(); + points.push(points[0]); + + let poly = Polygon::new(PlotPoints::from(points)) + .fill_color(colour.to_colour32(selected)) + .stroke(Stroke::NONE); + ui.polygon(poly); + } + + fn draw_egui(&self, ui: &mut Ui, selected: bool) { + ui.painter().add(RectShape::filled( + Rect::from_center_size( + self.position.invert_y().into(), + Vec2::new(self.rectangle.width as f32, self.rectangle.height as f32), + ), + Rounding::same(self.rounding as f32 / 2.), + CanvasColour::Copper.to_colour32(selected), + )); + } + + fn to_paths(&self) -> ClipperPaths { + ClipperPaths::new(vec![self.outline.clone()]) + } + + fn outline(&self) -> ClipperPath { + self.outline.clone() + } +} diff --git a/src/geometry/elements/rectangle.rs b/src/geometry/elements/rectangle.rs new file mode 100644 index 0000000..9555947 --- /dev/null +++ b/src/geometry/elements/rectangle.rs @@ -0,0 +1,108 @@ +use eframe::{ + egui::{Rect, Rounding, Stroke, Ui, Vec2}, + epaint::RectShape, +}; +use egui_plot::{PlotPoints, Polygon}; + +use crate::{ + application::CanvasColour, + geometry::{ClipperPath, ClipperPaths}, +}; + +use super::super::{ + point::{convert_to_unit, Point}, + DrawableRaw, Unit, +}; + +#[derive(Debug)] +pub struct Rectangle { + position: Point, + pub width: f64, + pub height: f64, + hole_diameter: Option, + outline: ClipperPath, +} + +impl Rectangle { + pub fn from_aperture_rectangular( + aperture: &gerber_types::Rectangular, + position: Point, + ) -> Self { + Self { + position, + width: aperture.x, + height: aperture.y, + hole_diameter: aperture.hole_diameter, + outline: vec![ + (position.x - aperture.x / 2., position.y - aperture.y / 2.), + (position.x - aperture.x / 2., position.y + aperture.y / 2.), + (position.x + aperture.x / 2., position.y + aperture.y / 2.), + (position.x + aperture.x / 2., position.y - aperture.y / 2.), + (position.x - aperture.x / 2., position.y - aperture.y / 2.), + ] + .into(), + } + } + + pub fn new(position: Point, width: f64, height: f64, hole_diameter: Option) -> Self { + Self { + position, + width, + height, + hole_diameter, + outline: vec![ + (position.x - width / 2., position.y - height / 2.), + (position.x - width / 2., position.y + height / 2.), + (position.x + width / 2., position.y + height / 2.), + (position.x + width / 2., position.y - height / 2.), + ] + .into(), + } + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + Self::new( + self.position.to_unit(origin, to), + convert_to_unit(self.width, origin, to), + convert_to_unit(self.height, origin, to), + self.hole_diameter.map(|d| convert_to_unit(d, origin, to)), + ) + } +} + +impl DrawableRaw for Rectangle { + fn canvas_pos(&self) -> Point { + self.position + .shift_x(self.position.x / 2.) + .shift_y(self.position.y / 2.) + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + let mut points: Vec<[f64; 2]> = self.outline.iter().map(|p| [p.x(), p.y()]).collect(); + points.push(points[0]); + + let poly = Polygon::new(PlotPoints::from(points)) + .fill_color(colour.to_colour32(selected)) + .stroke(Stroke::NONE); + ui.polygon(poly); + } + + fn draw_egui(&self, ui: &mut Ui, selected: bool) { + ui.painter().add(RectShape::filled( + Rect::from_center_size( + self.position.invert_y().into(), + Vec2::new(self.width as f32, self.height as f32), + ), + Rounding::ZERO, + CanvasColour::Copper.to_colour32(selected), + )); + } + + fn to_paths(&self) -> ClipperPaths { + ClipperPaths::new(vec![self.outline.clone()]) + } + + fn outline(&self) -> ClipperPath { + self.outline.clone() + } +} diff --git a/src/geometry/gerber.rs b/src/geometry/gerber.rs new file mode 100644 index 0000000..7b98ff9 --- /dev/null +++ b/src/geometry/gerber.rs @@ -0,0 +1,215 @@ +use clipper2::Paths; +use gerber_types::{ + Aperture, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode, MCode, + Operation, Unit, +}; +use tracing::{debug, error, info}; + +use crate::{ + geometry::{ + elements::{ + circle::Circle, linepath::LinePath, obround::Obround, rectangle::Rectangle, Element, + }, + point::Point, + }, + gerber::doc::GerberDoc, +}; + +use super::{ + union::{union_lines, union_with_apertures}, + Geometry, +}; + +impl From for Geometry { + fn from(gerber: GerberDoc) -> Self { + // working variables + let mut selected_aperture = None; + let mut selected_interpolation_mode = InterpolationMode::Linear; + let mut current_position = Point::new(0., 0.); + let mut active_path: LinePath = LinePath::new(); + let mut path_container: Vec = Vec::new(); + let mut added_apertures: Vec = Vec::new(); + + for command in gerber.commands { + println!("{command:?}"); + match command { + Command::FunctionCode(f) => { + match f { + FunctionCode::DCode(code) => { + match code { + DCode::Operation(op) => match op { + Operation::Interpolate(coordinates, offset) => { + if selected_interpolation_mode == InterpolationMode::Linear + { + // self.add_draw_segment(coord); + let point = Point::try_from(&coordinates); + if active_path.is_empty() { + active_path.add(current_position); + } + match point { + Ok(point) => { + active_path.add(point); + } + Err(e) => error!("{e:?}"), + } + } else { + // TODO + // self.add_arc_segment(coord, offset.as_ref().expect(format!("No offset coord with 'Circular' state\r\n{:#?}", c).as_str())) + } + Self::move_position(&coordinates, &mut current_position); + } + Operation::Move(m) => { + debug!("Move to {:?}, create path.", &m); + // self.create_path_from_data(); + if let Some(Aperture::Circle(c)) = + selected_aperture.as_ref() + { + if !active_path.is_empty() { + active_path.finalize(c.diameter); + path_container.push(active_path); + } + } + + active_path = LinePath::new(); + Geometry::move_position(&m, &mut current_position); + } + Operation::Flash(f) => { + // self.create_path_from_data(); + Self::add_geometry( + &mut added_apertures, + ¤t_position, + &f, + &selected_aperture, + ); + + Self::move_position(&f, &mut current_position); + } + }, + DCode::SelectAperture(ap) => { + // self.create_path_from_data(); + selected_aperture = Some( + gerber + .apertures + .get(&ap) + .unwrap_or_else(|| { + panic!("Unknown aperture id '{}'", ap) + }) + .clone(), + ) + } + } + } + FunctionCode::GCode(code) => match code { + GCode::InterpolationMode(im) => selected_interpolation_mode = im, + GCode::Comment(c) => info!("[COMMENT] \"{}\"", c), + _ => error!("Unsupported GCode:\r\n{:#?}", code), + }, + FunctionCode::MCode(m) => { + // check for end of file + if m == MCode::EndOfFile && !active_path.is_empty() { + // finish current path if one is present + if let Some(Aperture::Circle(c)) = selected_aperture.as_ref() { + active_path.finalize(c.diameter); + path_container.push(active_path); + break; // finish executing commands + } + } + } + } + } + Command::ExtendedCode(_) => {} + } + } + + let mut result = clipper2::Paths::new(vec![]); + // if path_container.len() > 1 { + // let mut clipper = path_container[1] + // .outline + // // .to_paths() + // .to_clipper_subject() + // .add_clip(path_container[2].outline.clone()); + // // .add_clip(path_container[3].outline.clone()) + // // .add_clip(path_container[4].outline.clone()); + + // // for clip in added_apertures.iter().skip(2) { + // // clipper = clipper.add_clip(clip.to_paths()); + // // } + + // // for line in path_container.iter().skip(2) { + // // clipper = clipper.add_clip(line.to_paths()) + // // } + + // result = clipper.union(clipper2::FillRule::default()).unwrap(); + + // result = result + // .to_clipper_subject() + // .add_clip(path_container[3].outline.clone()) + // .add_clip(path_container[4].outline.clone()) + // .union(clipper2::FillRule::default()) + // .unwrap(); + // } + + let mut geo = Paths::new(vec![]); + let conductor_net = union_lines(&path_container); + + for outline in &conductor_net { + println!("{:?}", outline.included_points); + geo.push(outline.outline.clone()); + } + + println!("Number of conductor net paths: {}", geo.len()); + + if let Some(geo) = union_with_apertures(&added_apertures, conductor_net) { + println!("Number of finalized net paths: {}", geo.len()); + result = geo; + } + Self { + outline_union: result, + apertures: added_apertures, + paths: path_container, + units: gerber.units.unwrap_or(Unit::Millimeters).into(), + } + } +} + +impl Geometry { + fn move_position(coord: &Coordinates, position: &mut Point) -> () { + if let Ok(pos) = Point::try_from(coord) { + debug!("Moved position to {pos:?}"); + *position = pos; + }; + } + + fn add_geometry( + geometries: &mut Vec, + position: &Point, + coordinates: &Coordinates, + aperture: &Option, + ) { + let target = match Point::try_from(coordinates) { + Ok(point) => point, + Err(_) => *position, + }; + + match aperture.as_ref().expect("No aperture selected") { + Aperture::Circle(c) => { + geometries.push(Element::Circle(Circle::from_aperture_circle(c, target))); + } + Aperture::Rectangle(r) => { + geometries.push(Element::Rectangle(Rectangle::from_aperture_rectangular( + r, target, + ))); + } + Aperture::Obround(o) => { + // error!("Unsupported Obround aperture:\r\n{:#?}", o); + geometries.push(Element::Obround(Obround::from_aperture_obround(o, target))); + } + Aperture::Polygon(p) => { + error!("Unsupported Polygon aperture:\r\n{:#?}", p); + } + Aperture::Other(o) => { + error!("Unsupported Other aperture:\r\n{:#?}", o); + } + } + } +} diff --git a/src/geometry/helpers.rs b/src/geometry/helpers.rs new file mode 100644 index 0000000..64f5746 --- /dev/null +++ b/src/geometry/helpers.rs @@ -0,0 +1,154 @@ +use std::f64::consts::PI; + +use super::point::Point; + +const CIRCLE_SEGMENTS: u32 = 512; + +pub fn semi_circle(center: Point, diameter: f64, tilt: f64) -> Vec<(f64, f64)> { + (0..CIRCLE_SEGMENTS / 2) + .step_by(1) + .map(|i| { + let angle = (i as f64 / (CIRCLE_SEGMENTS / 2) as f64) * PI + tilt * (PI / 180.); + ( + angle.cos() * diameter / 2. + center.x, + angle.sin() * diameter / 2. + center.y, + ) + }) + .collect() +} + +pub enum CircleSegment { + North, + East, + South, + West, + Full, +} + +pub fn create_circular_path( + position: &Point, + diameter: f64, + segment: CircleSegment, +) -> Vec<(f64, f64)> { + let segments: Vec = match segment { + CircleSegment::North => (CIRCLE_SEGMENTS / 4..CIRCLE_SEGMENTS - CIRCLE_SEGMENTS / 4) + .step_by(1) + .collect(), + CircleSegment::East => (0..CIRCLE_SEGMENTS / 2 + 1).step_by(1).collect(), + CircleSegment::South => (CIRCLE_SEGMENTS - CIRCLE_SEGMENTS / 4..CIRCLE_SEGMENTS) + .step_by(1) + .chain((0..CIRCLE_SEGMENTS / 4).step_by(1)) + .collect(), + CircleSegment::West => (CIRCLE_SEGMENTS / 2 - 1..CIRCLE_SEGMENTS) + .step_by(1) + .collect(), + CircleSegment::Full => (0..CIRCLE_SEGMENTS).step_by(1).collect(), + }; + segments + .iter() + .map(|&i| { + let angle = (i as f64 / CIRCLE_SEGMENTS as f64) * 2.0 * PI; + ( + angle.sin() * diameter / 2. + position.x, + angle.cos() * diameter / 2. + position.y, + ) + }) + .collect() +} + +#[derive(Debug, PartialEq)] +pub enum Orientation { + Clockwise, + CounterClockwise, + CoLinear, +} + +// To find orientation of ordered triplet (p, q, r). +// https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ +pub fn orientation(p: impl Into, q: impl Into, r: impl Into) -> Orientation { + let (p, q, r) = (p.into(), q.into(), r.into()); + + // See https://www.geeksforgeeks.org/orientation-3-ordered-points/ + // for details of below formula. + let val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + + match val { + 0. => Orientation::CoLinear, + x if x > 0. => Orientation::Clockwise, + _ => Orientation::CounterClockwise, + } +} + +// Given three collinear points p, q, r, the function checks if +// point q lies on line segment 'pr' +pub fn on_segment(p: impl Into, q: impl Into, r: impl Into) -> bool { + let (p, q, r) = (p.into(), q.into(), r.into()); + + q.x <= greater_val(p.x, r.x) + && q.x >= lower_val(p.x, r.x) + && q.y <= greater_val(p.y, r.y) + && q.y >= lower_val(p.y, r.y) +} + +fn greater_val(a: f64, b: f64) -> f64 { + if a > b { + a + } else { + b + } +} + +fn lower_val(a: f64, b: f64) -> f64 { + if a < b { + a + } else { + b + } +} + +// The main function that returns true if line segment 'p1q1' +// and 'p2q2' intersect. +// https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ +pub fn do_intersect( + p1: impl Into, + q1: impl Into, + p2: impl Into, + q2: impl Into, +) -> bool { + let (p1, q1, p2, q2) = (p1.into(), q1.into(), p2.into(), q2.into()); + + // Find the four orientations needed for general and + // special cases + let o1 = orientation(p1, q1, p2); + let o2 = orientation(p1, q1, q2); + let o3 = orientation(p2, q2, p1); + let o4 = orientation(p2, q2, q1); + + // General case + if o1 != o2 && o3 != o4 { + return true; + } + + // Special Cases + // p1, q1 and p2 are collinear and p2 lies on segment p1q1 + if o1 == Orientation::CoLinear && on_segment(p1, p2, q1) { + return true; + } + + // p1, q1 and q2 are collinear and q2 lies on segment p1q1 + if o2 == Orientation::CoLinear && on_segment(p1, q2, q1) { + return true; + }; + + // p2, q2 and p1 are collinear and p1 lies on segment p2q2 + if o3 == Orientation::CoLinear && on_segment(p2, p1, q2) { + return true; + }; + + // p2, q2 and q1 are collinear and q1 lies on segment p2q2 + if o4 == Orientation::CoLinear && on_segment(p2, q1, q2) { + return true; + }; + + false // Doesn't fall in any of the above cases +} diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs new file mode 100644 index 0000000..974dd88 --- /dev/null +++ b/src/geometry/mod.rs @@ -0,0 +1,109 @@ +pub mod elements; +pub mod gerber; +mod helpers; +pub mod point; +mod union; + +use std::fmt::{Debug, Display}; + +use clipper2::{Bounds, Path, Paths, PointScaler}; +use eframe::egui::Ui; +use egui_plot::PlotUi; +use elements::{linepath::LinePath, Element}; + +use point::Point; + +use crate::application::CanvasColour; + +pub struct Geometry { + pub outline_union: ClipperPaths, + pub apertures: Vec, + pub paths: Vec, + pub units: Unit, +} + +impl Geometry { + pub fn to_unit(self, to: Unit) -> Self { + Self { + outline_union: self + .outline_union + .iter() + .map(|p| { + p.iter() + .map(|p| (&Point::from(p).to_unit(self.units, to)).into()) + .collect() + }) + .collect(), + apertures: self + .apertures + .iter() + .map(|a| a.to_unit(self.units, to)) + .collect(), + paths: self + .paths + .iter() + .map(|l| l.to_unit(self.units, to)) + .collect(), + units: to, + } + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Unit { + Millimeters, + Inches, +} + +impl Default for Unit { + fn default() -> Self { + Self::Millimeters + } +} + +impl From for Unit { + fn from(value: gerber_types::Unit) -> Self { + match value { + gerber_types::Unit::Inches => Self::Inches, + gerber_types::Unit::Millimeters => Self::Millimeters, + } + } +} + +impl Display for Unit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Unit::Millimeters => "mm", + Unit::Inches => "in", + } + ) + } +} + +/// Scale by 10000. +#[derive(Debug, Copy, Clone, PartialEq, Hash)] +pub struct Micro; + +impl PointScaler for Micro { + const MULTIPLIER: f64 = 10000.0; +} + +pub type ClipperPoint = clipper2::Point; +pub type ClipperPath = Path; +pub type ClipperPaths = Paths; +pub type ClipperBounds = Bounds; + +pub trait DrawableRaw { + fn canvas_pos(&self) -> Point; + fn draw_egui(&self, ui: &mut Ui, selected: bool); + fn draw_egui_plot(&self, ui: &mut PlotUi, colour: CanvasColour, selected: bool); + fn to_paths(&self) -> ClipperPaths; + fn outline(&self) -> ClipperPath; +} + +fn canvas_position_from_gerber(gerber_position: &Point, offset: Point) -> Point { + gerber_position.shift_x(offset.x).shift_y(offset.y) +} diff --git a/src/geometry/point.rs b/src/geometry/point.rs new file mode 100644 index 0000000..3305ff4 --- /dev/null +++ b/src/geometry/point.rs @@ -0,0 +1,197 @@ +use std::ops::{Add, Sub}; + +use eframe::egui::{Pos2, Vec2}; +use egui_plot::PlotPoint; +use gerber_types::Coordinates; + +use super::{ClipperPoint, Unit}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Point { + pub x: f64, + pub y: f64, +} + +impl Point { + pub fn new(x: f64, y: f64) -> Self { + Point { x, y } + } + + pub fn shift_x(&self, shift: f64) -> Self { + Self { + x: self.x + shift, + y: self.y, + } + } + + pub fn shift_y(&self, shift: f64) -> Self { + Self { + x: self.x, + y: self.y + shift, + } + } + + pub fn len(&self) -> f64 { + (self.x.powi(2) + self.y.powi(2)).sqrt() + } + + pub fn normalize(&self) -> Self { + Self { + x: self.x / self.len(), + y: self.y / self.len(), + } + } + + pub fn invert_y(&self) -> Self { + Self { + x: self.x, + y: -self.y, + } + } + + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + Self { + x: convert_to_unit(self.x, origin, to), + y: convert_to_unit(self.y, origin, to), + } + } +} + +impl From for (f64, f64) { + fn from(value: Point) -> Self { + (value.x, value.y) + } +} + +impl From for [f64; 2] { + fn from(value: Point) -> Self { + [value.x, value.y] + } +} + +impl From<[f64; 2]> for Point { + fn from(value: [f64; 2]) -> Self { + Point { + x: value[0], + y: value[1], + } + } +} + +impl Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + x: self.x + other.x, + y: self.y + other.y, + } + } +} + +impl Sub for Point { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + Self { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +impl From for Pos2 { + fn from(value: Point) -> Self { + Self { + x: value.x as f32, + y: value.y as f32, + } + } +} + +impl From<&Point> for Pos2 { + fn from(value: &Point) -> Self { + Self { + x: value.x as f32, + y: value.y as f32, + } + } +} + +impl From for Point { + fn from(value: Pos2) -> Self { + Self { + x: value.x.into(), + y: value.y.into(), + } + } +} + +impl From for Vec2 { + fn from(value: Point) -> Self { + Self { + x: value.x as f32, + y: value.y as f32, + } + } +} + +impl From<&Point> for ClipperPoint { + fn from(value: &Point) -> Self { + Self::new(value.x, value.y) + } +} + +impl From<&ClipperPoint> for Point { + fn from(value: &ClipperPoint) -> Self { + Self::new(value.x(), value.y()) + } +} + +impl From for PlotPoint { + fn from(value: Point) -> Self { + Self::new(value.x, value.y) + } +} + +#[derive(Debug)] +pub enum CoordinateConversionError { + MissingXValue, + MissingYValue, + ParsingError(String), + FormattingError, +} + +impl TryFrom<&Coordinates> for Point { + type Error = CoordinateConversionError; + + fn try_from(value: &Coordinates) -> Result { + let x_coordinate = value + .x + .ok_or(CoordinateConversionError::MissingXValue)? + .gerber(&value.format) + .map_err(|_| CoordinateConversionError::FormattingError)? + .parse::() + .map_err(|e| CoordinateConversionError::ParsingError(e.to_string()))?; + let y_coordinate = value + .y + .ok_or(CoordinateConversionError::MissingYValue)? + .gerber(&value.format) + .map_err(|_| CoordinateConversionError::FormattingError)? + .parse::() + .map_err(|e| CoordinateConversionError::ParsingError(e.to_string()))?; + + Ok(Point::new( + x_coordinate / 10_f64.powi(value.format.decimal as i32), + y_coordinate / 10_f64.powi(value.format.decimal as i32), + )) + } +} + +pub fn convert_to_unit(number: f64, from: Unit, to: Unit) -> f64 { + match (from, to) { + (Unit::Millimeters, Unit::Millimeters) | (Unit::Inches, Unit::Inches) => number, + (Unit::Millimeters, Unit::Inches) => number * 1. / 25.4, + (Unit::Inches, Unit::Millimeters) => number * 25.4, + } +} diff --git a/src/geometry/union.rs b/src/geometry/union.rs new file mode 100644 index 0000000..83be92f --- /dev/null +++ b/src/geometry/union.rs @@ -0,0 +1,189 @@ +use std::collections::HashMap; + +use clipper2::{FillRule, One, Paths, PointInPolygonResult}; + +use crate::geometry::helpers::do_intersect; + +use super::{ + elements::{linepath::LinePath, Element}, + point::Point, + ClipperPaths, +}; + +#[derive(Debug, Clone)] +pub struct ConductorNet { + pub outline: ClipperPaths, + pub included_points: Vec, +} + +pub fn union_lines(lines: &[LinePath]) -> Vec { + let mut intersection_map = HashMap::new(); + println!( + "START LINE UNION of {:?}", + lines + .iter() + .map(|l| l.points.clone()) + .collect::>>() + ); + for (index, line) in lines.iter().enumerate() { + if !line.is_empty() { + // create list of intersecting lines + let mut intersections = Vec::new(); + let (p1, q1) = (line.points[0], line.points[1]); + println!("LINE 1 {p1:?}, {q1:?}"); + for (i, l) in lines.iter().enumerate() { + if !l.is_empty() { + // do not check for intersection with itself + if index != i { + // check for all lines in path + let intersect = l.points.windows(2).any(|w| { + let (p2, q2) = (w[0], w[1]); + println!("LINE 2 {p2:?}, {q2:?}"); + do_intersect(p1, q1, p2, q2) + }); + println!("INTERSECTING: {intersect}"); + if intersect { + // let entry = intersection_map.entry(index).or_insert(Vec::new()); + // entry.push(i); + intersections.push(i); + } + } + } + } + // + intersection_map.insert(index, intersections); + } + } + + println!("{intersection_map:?}"); + let mut final_geo = Vec::new(); + + // go through all line segments + for i in 0..intersection_map.len() { + if let Some(mut intersections) = intersection_map.remove(&i) { + // if no intersections are given, add line as own conductor net + // if intersections.is_empty() { + // if let Some(line) = lines.get(i) { + // final_geo.push(ConductorNet { + // outline: line.outline.clone(), + // included_points: line.points.clone(), + // }) + // } + // } + + println!("--- Taking line segment {i}"); + // get current line path + let mut geo = lines[i].outline.clone(); + let mut included_points = lines[i].points.clone(); + + // union with intersecting lines until done + println!("Intersection points: {intersections:?}"); + while let Some(other) = intersections.pop() { + println!("Intersecting with line # {other:?}"); + // union with intersecting line + let intersecting_line = &lines[other]; + if let Some(union) = union(&geo, &intersecting_line.outline) { + geo = union; + } + // add points of added line to included points + for point in &intersecting_line.points { + if !included_points.contains(point) { + included_points.push(point.to_owned()); // show included points + } + } + + // get intersections of united line and add them if not already in own + if let Some(new_intersections) = intersection_map.remove(&other) { + for o in new_intersections { + // add to original line if not self and not already inside + if o != i // ensure to not intersect with itself + && !intersections.contains(&o) // do not add if already in intersection list + && intersection_map.contains_key(&o) + // check if intersection was already performed + { + intersections.push(o); + } + } + } + } + // create conductor net + final_geo.push(ConductorNet { + outline: geo, + included_points, + }) + } + } + + // if final_geo.is_empty() { + // if let Some(line) = lines.get(0) { + // final_geo.push(ConductorNet { + // outline: line.outline.clone(), + // included_points: line.points.clone(), + // }) + // } + // } + + final_geo +} + +pub fn union_with_apertures( + apertures: &Vec, + conductors: Vec, +) -> Option { + let mut finalized_paths = Vec::new(); // handle apertures without connection + let mut current_conductors = conductors; + + // go through all apertures + for ap in apertures { + // get outline path + let geo = ap.to_paths(); + // create empty set of conductors + let mut new_conductors = Vec::new(); + // create indicator if aperture is isolated + let mut isolated = true; + + // go through all available conductor nets + for conductor in current_conductors { + // check if any point of the conductor net is not outside of the aperture + if conductor + .included_points + .iter() + .any(|c| ap.outline().is_point_inside(c.into()) != PointInPolygonResult::IsOutside) + { + // union aperture with conductor net + let geo = union(&geo, &conductor.outline)?; + let mut cond = conductor; + cond.outline = geo; + isolated = false; + new_conductors.push(cond); + } else { + new_conductors.push(conductor); + } + } + + // add aperture to extra container if isolated + if isolated { + finalized_paths.push(geo); + } + + // update current conductors + current_conductors = new_conductors; + } + + for conductor in current_conductors { + finalized_paths.push(conductor.outline); + } + + finalized_paths.into_iter().reduce(|mut all, paths| { + all.push(paths); + all + }) +} + +fn union(path1: &ClipperPaths, path2: &ClipperPaths) -> Option { + path1 + .to_clipper_subject() + .add_clip(path2.clone()) + .union(FillRule::default()) + .ok() +} diff --git a/src/gerber/aperture_macros.rs b/src/gerber/aperture_macros.rs new file mode 100644 index 0000000..e0e4515 --- /dev/null +++ b/src/gerber/aperture_macros.rs @@ -0,0 +1,76 @@ +use gerber_types::MacroContent; + +pub fn parse(data: &str) -> Option { + todo!() +} + +// def parse_content(self): +// """ +// Creates numerical lists for all primitives in the aperture +// macro (in ``self.raw``) by replacing all variables by their +// values iteratively and evaluating expressions. Results +// are stored in ``self.primitives``. + +// :return: None +// """ +// # Cleanup +// self.raw = self.raw.replace('\n', '').replace('\r', '').strip(" *") +// self.primitives = [] + +// # Separate parts +// parts = self.raw.split('*') + +// #### Every part in the macro #### +// for part in parts: +// ### Comments. Ignored. +// match = ApertureMacro.amcomm_re.search(part) +// if match: +// continue + +// ### Variables +// # These are variables defined locally inside the macro. They can be +// # numerical constant or defind in terms of previously define +// # variables, which can be defined locally or in an aperture +// # definition. All replacements ocurr here. +// match = ApertureMacro.amvar_re.search(part) +// if match: +// var = match.group(1) +// val = match.group(2) + +// # Replace variables in value +// for v in self.locvars: +// val = re.sub(r'\$'+str(v)+r'(?![0-9a-zA-Z])', str(self.locvars[v]), val) + +// # Make all others 0 +// val = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", val) + +// # Change x with * +// val = re.sub(r'[xX]', "*", val) + +// # Eval() and store. +// self.locvars[var] = eval(val) +// continue + +// ### Primitives +// # Each is an array. The first identifies the primitive, while the +// # rest depend on the primitive. All are strings representing a +// # number and may contain variable definition. The values of these +// # variables are defined in an aperture definition. +// match = ApertureMacro.amprim_re.search(part) +// if match: +// ## Replace all variables +// for v in self.locvars: +// part = re.sub(r'\$' + str(v) + r'(?![0-9a-zA-Z])', str(self.locvars[v]), part) + +// # Make all others 0 +// part = re.sub(r'\$[0-9a-zA-Z](?![0-9a-zA-Z])', "0", part) + +// # Change x with * +// part = re.sub(r'[xX]', "*", part) + +// ## Store +// elements = part.split(",") +// self.primitives.append([eval(x) for x in elements]) +// continue + +// log.warning("Unknown syntax of aperture macro part: %s" % str(part)) diff --git a/src/gerber/doc.rs b/src/gerber/doc.rs new file mode 100644 index 0000000..e43143f --- /dev/null +++ b/src/gerber/doc.rs @@ -0,0 +1,83 @@ +use ::std::collections::HashMap; +use gerber_types::{Aperture, ApertureDefinition, Command, CoordinateFormat, ExtendedCode, Unit}; +use std::fmt; +use std::iter::repeat; + +#[derive(Debug, PartialEq)] +// Representation of Gerber document +pub struct GerberDoc { + // unit type, defined once per document + pub units: Option, + // format specification for coordinates, defined once per document + pub format_specification: Option, + /// map of apertures which can be used in draw commands later on in the document. + pub apertures: HashMap, + // Anything else, draw commands, comments, attributes + pub commands: Vec, +} + +impl GerberDoc { + // instantiate a empty gerber document ready for parsing + pub fn new() -> GerberDoc { + GerberDoc { + units: None, + format_specification: None, + apertures: HashMap::new(), + commands: Vec::new(), + } + } + + /// Turns a GerberDoc into a &vec of gerber-types Commands + /// + /// Get a representation of a gerber document *purely* in terms of elements provided + /// in the gerber-types rust crate. Note that aperture definitions will be sorted by code number + /// with lower codes being at the top of the command. This is independent of their order during + /// parsing. + pub fn to_commands(mut self) -> Vec { + let mut gerber_cmds: Vec = Vec::new(); + gerber_cmds.push(ExtendedCode::CoordinateFormat(self.format_specification.unwrap()).into()); + gerber_cmds.push(ExtendedCode::Unit(self.units.unwrap()).into()); + + // we add the apertures to the list, but we sort by code. This means the order of the output + // is reproducible every time. + let mut apertures = self.apertures.into_iter().collect::>(); + apertures.sort_by_key(|tup| tup.0); + for (code, aperture) in apertures { + gerber_cmds.push( + ExtendedCode::ApertureDefinition(ApertureDefinition { + code: code, + aperture: aperture, + }) + .into(), + ) + } + + gerber_cmds.append(&mut self.commands); + // TODO implement for units + gerber_cmds + } +} + +impl fmt::Display for GerberDoc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let int_str: String = "_".repeat(self.format_specification.unwrap().integer as usize); + let dec_str: String = "_".repeat(self.format_specification.unwrap().decimal as usize); + + writeln!(f, "GerberDoc").unwrap(); + writeln!(f, "- units: {:?}", self.units).unwrap(); + writeln!( + f, + "- format spec: {}.{} ({}|{})", + int_str, + dec_str, + self.format_specification.unwrap().integer, + self.format_specification.unwrap().decimal + ) + .unwrap(); + writeln!(f, "- apertures: ").unwrap(); + for code in self.apertures.keys() { + writeln!(f, "\t {}", code).unwrap(); + } + write!(f, "- commands: {}", &self.commands.len()) + } +} diff --git a/src/gerber/mod.rs b/src/gerber/mod.rs new file mode 100644 index 0000000..ab40ca2 --- /dev/null +++ b/src/gerber/mod.rs @@ -0,0 +1,803 @@ +pub mod doc; + +use doc::GerberDoc; +use gerber_types::{ + Aperture, ApertureAttribute, ApertureFunction, Circle, Command, CoordinateFormat, + CoordinateNumber, CoordinateOffset, Coordinates, DCode, ExtendedCode, FiducialScope, + FileAttribute, FileFunction, FilePolarity, FunctionCode, GCode, InterpolationMode, MCode, + Operation, Part, Polarity, Polygon, QuadrantMode, Rectangular, SmdPadType, StepAndRepeat, Unit, +}; +use lazy_regex::{regex, Regex}; +use std::io::{BufRead, BufReader, Read}; +use std::str::Chars; +use tracing::{debug, error}; + +/// Parse a gerber string (in BufReader) to a GerberDoc +/// +/// Take the contents of a Gerber (.gbr) file and parse it to a GerberDoc struct. The parsing does +/// some semantic checking, but is is certainly not exhaustive - so don't rely on it to check if +/// your Gerber file is valid according to the spec. Some of the parsing steps are greedy - they may +/// match something unexpected (rather than panicking) if there is a typo/fault in your file. +pub fn parse_gerber(reader: BufReader) -> GerberDoc { + let mut gerber_doc = GerberDoc::new(); + // The gerber spec allows omission of X or Y statements in D01/2/3 commands, where the omitted + // coordinate is to be taken as whatever was used in the previous coordinate-using command + // By default the 'last coordinate' can be taken to be (0,0) + let mut last_coords = (0i64, 0i64); + + // naively define some regex terms + // TODO see which ones can be done without regex for better performance? + let re_units = regex!(r#"%MO(.*)\*%"#); + let re_comment = regex!(r#"G04 (.*)\*"#); + let re_formatspec = regex!(r#"%FSLAX(.*)Y(.*)\*%"#); + let re_aperture = regex!(r#"%ADD([0-9]+)([A-Z]),(.*)\*%"#); + let re_interpolation = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?I?(-?[0-9]+)?J?(-?[0-9]+)?D01\*"#); + let re_move_or_flash = regex!(r#"X?(-?[0-9]+)?Y?(-?[0-9]+)?D0[2-3]*"#); + // TODO: handle escaped characters for attributes + let re_attributes = regex!(r#"%T[A-Z].([A-Z]+?),?"#); + let re_step_repeat = regex!(r#"%SRX([0-9]+)Y([0-9]+)I(\d+\.?\d*)J(\d+\.?\d*)\*%"#); + + for (index, line) in reader.lines().enumerate() { + let rawline = line.unwrap(); + // TODO combine this with line above + let line = rawline.trim(); + + // Show the line + debug!("{}. {}", index + 1, &line); + + if !line.is_empty() { + let mut linechars = line.chars(); + + match linechars.next().unwrap() { + 'G' => { + match linechars.next().unwrap() { + '0' => match linechars.next().unwrap() { + '1' => gerber_doc.commands.push( + FunctionCode::GCode(GCode::InterpolationMode( + InterpolationMode::Linear, + )) + .into(), + ), // G01 + '2' => gerber_doc.commands.push( + FunctionCode::GCode(GCode::InterpolationMode( + InterpolationMode::ClockwiseCircular, + )) + .into(), + ), // G02 + '3' => gerber_doc.commands.push( + FunctionCode::GCode(GCode::InterpolationMode( + InterpolationMode::CounterclockwiseCircular, + )) + .into(), + ), // G03 + '4' => parse_comment(line, re_comment, &mut gerber_doc), // G04 + _ => line_parse_failure(line, index), + }, + '3' => match linechars.next().unwrap() { + '6' => gerber_doc + .commands + .push(FunctionCode::GCode(GCode::RegionMode(true)).into()), // G36 + '7' => gerber_doc + .commands + .push(FunctionCode::GCode(GCode::RegionMode(false)).into()), // G37 + _ => line_parse_failure(line, index), + }, + '5' => match linechars.next().unwrap() { + // the G54 command (select aperture) is technically part of the Deprecated commands + '4' => { + // select aperture D* + linechars.next_back(); // remove the trailing '*' + linechars.next(); // remove 'D' + parse_aperture_selection(linechars, &mut gerber_doc) + } + _ => line_parse_failure(line, index), + }, + '7' => match linechars.next().unwrap() { + // the G74 command is technically part of the Deprecated commands + '4' => gerber_doc.commands.push( + FunctionCode::GCode(GCode::QuadrantMode(QuadrantMode::Single)) + .into(), + ), // G74 + '5' => gerber_doc.commands.push( + FunctionCode::GCode(GCode::QuadrantMode(QuadrantMode::Multi)) + .into(), + ), // G74 + _ => line_parse_failure(line, index), + }, // G75 + _ => line_parse_failure(line, index), + } + } + '%' => { + match linechars.next().unwrap() { + 'M' => parse_units(line, re_units, &mut gerber_doc), + 'F' => parse_format_spec(line, re_formatspec, &mut gerber_doc), + 'A' => match linechars.next().unwrap() { + 'D' => parse_aperture_defs(line, re_aperture, &mut gerber_doc), // AD + 'M' => { + panic!("Aperture Macros (AM) are not supported yet.") + } // AM + _ => line_parse_failure(line, index), + }, + 'L' => match linechars.next().unwrap() { + 'P' => match linechars.next().unwrap() { + 'D' => gerber_doc + .commands + .push(ExtendedCode::LoadPolarity(Polarity::Dark).into()), // LPD + 'C' => gerber_doc + .commands + .push(ExtendedCode::LoadPolarity(Polarity::Clear).into()), // LPC + _ => line_parse_failure(line, index), + }, // LP + 'M' => parse_load_mirroring(linechars, &mut gerber_doc), // LM + 'R' => { + panic!("Load Mirroring (LM) command not supported yet.") + } // LR + 'S' => { + panic!("Load Scaling (LS) command not supported yet.") + } // LS + _ => line_parse_failure(line, index), + }, + 'T' => match linechars.next().unwrap() { + // TODO Turned off + // 'F' => parse_file_attribute(linechars, &re_attributes, &mut gerber_doc), + 'A' => { + parse_aperture_attribute(linechars, re_attributes, &mut gerber_doc) + } + 'O' => { + parse_object_attribute(linechars, re_attributes, &mut gerber_doc) + } + 'D' => { + parse_delete_attribute(linechars, re_attributes, &mut gerber_doc) + } + _ => line_parse_failure(line, index), + }, + 'S' => match linechars.next().unwrap() { + 'R' => match linechars.next().unwrap() { + 'X' => { + parse_step_repeat_open(line, re_step_repeat, &mut gerber_doc) + } + // a statement %SR*% closes a step repeat command, which has no parameters + '*' => gerber_doc + .commands + .push(ExtendedCode::StepAndRepeat(StepAndRepeat::Close).into()), + _ => line_parse_failure(line, index), + }, + _ => line_parse_failure(line, index), + }, + _ => line_parse_failure(line, index), + } + } + 'X' | 'Y' => { + linechars.next_back(); + match linechars.next_back().unwrap() { + '1' => parse_interpolation( + line, + re_interpolation, + &mut gerber_doc, + &mut last_coords, + ), // D01 + '2' => parse_move_or_flash( + line, + re_move_or_flash, + &mut gerber_doc, + &mut last_coords, + false, + ), // D02 + '3' => parse_move_or_flash( + line, + re_move_or_flash, + &mut gerber_doc, + &mut last_coords, + true, + ), // D03 + _ => line_parse_failure(line, index), + } + } + 'D' => { + // select aperture D* + linechars.next_back(); // remove the trailing '*' + parse_aperture_selection(linechars, &mut gerber_doc) + } + 'M' => gerber_doc + .commands + .push(FunctionCode::MCode(MCode::EndOfFile).into()), + _ => line_parse_failure(line, index), + } + } + } + + // check that we ended with a gerber EOF command + assert_eq!( + gerber_doc.commands.last().unwrap(), + &Command::FunctionCode(FunctionCode::MCode(MCode::EndOfFile)), + "Missing M02 statement at end of file" + ); + + gerber_doc +} + +// print a simple message in case the parser hits a dead end +fn line_parse_failure(line: &str, index: usize) { + error!("Cannot parse line:\n{} | {}", index, line) +} + +// parse a Gerber Comment (e.g. 'G04 This is a comment*') +fn parse_comment(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { + if let Some(regmatch) = re.captures(line) { + let comment = regmatch.get(1).unwrap().as_str(); + gerber_doc + .commands + .push(FunctionCode::GCode(GCode::Comment(comment.to_string())).into()); + } +} + +// parse a Gerber unit statement (e.g. '%MOMM*%') +fn parse_units(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { + // Check that the units are not set yet (that would imply the set unit command is present twice) + if gerber_doc.units.is_some() { + panic! {"Cannot set unit type twice in the same document!"} + } + // Set the unit type + if let Some(regmatch) = re.captures(line) { + let units_str = regmatch.get(1).unwrap().as_str(); + if units_str == "MM" { + gerber_doc.units = Some(Unit::Millimeters); + } else if units_str == "IN" { + gerber_doc.units = Some(Unit::Inches); + } else { + panic!("Incorrect gerber units format") + } + } +} + +// parse a Gerber format spec statement (e.g. '%FSLAX23Y23*%') +fn parse_format_spec(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { + // Ensure that FS was not set before, which would imply two FS statements in the same doc + if gerber_doc.format_specification.is_some() { + panic!("Cannot set format specification twice in the same document!") + } + // Set Format Specification + if let Some(regmatch) = re.captures(line) { + let mut fs_chars = regmatch.get(1).unwrap().as_str().chars(); + let integer: u8 = fs_chars.next().unwrap().to_digit(10).unwrap() as u8; + let decimal: u8 = fs_chars.next().unwrap().to_digit(10).unwrap() as u8; + + // the gerber spec states that the integer value can be at most 6 + assert!( + (1..=6).contains(&integer), + "format spec integer value must be between 1 and 6" + ); + + let fs = CoordinateFormat::new(integer, decimal); + gerber_doc.format_specification = Some(fs); + } +} + +// parse a Gerber aperture definition e.g. '%ADD44R, 2.0X3.0*%') +fn parse_aperture_defs(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { + // aperture definitions + // TODO: prevent the same aperture code being used twice + if let Some(regmatch) = re.captures(line) { + let code = regmatch + .get(1) + .unwrap() + .as_str() + .parse::() + .expect("Failed to parse aperture code"); + assert!( + code > 9, + "Aperture codes 0-9 cannot be used for custom apertures" + ); + + let aperture_type = regmatch.get(2).unwrap().as_str(); + let aperture_args: Vec<&str> = regmatch.get(3).unwrap().as_str().split("X").collect(); + + //println!("The code is {}, and the aperture type is {} with params {:?}", code, aperture_type, aperture_args); + let insert_state = match aperture_type { + "C" => gerber_doc.apertures.insert( + code, + Aperture::Circle(Circle { + diameter: aperture_args[0].trim().parse::().unwrap(), + hole_diameter: if aperture_args.len() > 1 { + Some(aperture_args[1].trim().parse::().unwrap()) + } else { + None + }, + }), + ), + "R" => gerber_doc.apertures.insert( + code, + Aperture::Rectangle(Rectangular { + x: aperture_args[0].trim().parse::().unwrap(), + y: aperture_args[1].trim().parse::().unwrap(), + hole_diameter: if aperture_args.len() > 2 { + Some(aperture_args[2].trim().parse::().unwrap()) + } else { + None + }, + }), + ), + "O" => gerber_doc.apertures.insert( + code, + Aperture::Obround(Rectangular { + x: aperture_args[0].trim().parse::().unwrap(), + y: aperture_args[1].trim().parse::().unwrap(), + hole_diameter: if aperture_args.len() > 2 { + Some(aperture_args[2].trim().parse::().unwrap()) + } else { + None + }, + }), + ), + // note that for polygon we HAVE TO specify rotation if we want to add a hole + "P" => gerber_doc.apertures.insert( + code, + Aperture::Polygon(Polygon { + diameter: aperture_args[0].trim().parse::().unwrap(), + vertices: aperture_args[1].trim().parse::().unwrap(), + rotation: if aperture_args.len() > 2 { + Some(aperture_args[2].trim().parse::().unwrap()) + } else { + None + }, + hole_diameter: if aperture_args.len() > 3 { + Some(aperture_args[3].trim().parse::().unwrap()) + } else { + None + }, + }), + ), + _ => { + panic!("Encountered unknown aperture definition statement") + } + }; + + // the insert state will be None if the key (i.e. aperture code) was not present yet, + // or a Some(Aperture) value if the key was already in use (see behaviour of HashMap.insert) + // If a key is already present we have to throw an error, as this is invalid + if insert_state.is_some() { + panic!("Cannot use the aperture code {} more than once!", code) + } + } +} + +fn parse_aperture_selection(linechars: Chars, gerber_doc: &mut GerberDoc) { + let aperture_code = linechars + .as_str() + .parse::() + .expect("Failed to parse aperture selection"); + assert!( + gerber_doc.apertures.contains_key(&aperture_code), + "Cannot select an aperture that is not defined" + ); + gerber_doc + .commands + .push(FunctionCode::DCode(DCode::SelectAperture(aperture_code)).into()); +} + +// TODO clean up the except statements a bit +// parse a Gerber interpolation command (e.g. 'X2000Y40000I300J50000D01*') +fn parse_interpolation( + line: &str, + re: &Regex, + gerber_doc: &mut GerberDoc, + last_coords: &mut (i64, i64), +) { + if let Some(regmatch) = re.captures(line) { + let x_coord = match regmatch.get(1) { + Some(x) => { + let new_x = x.as_str().trim().parse::().unwrap(); + last_coords.0 = new_x; + new_x + } + None => last_coords.0, // if match is None, then the coordinate must have been implicit + }; + let y_coord = match regmatch.get(2) { + Some(y) => { + let new_y = y.as_str().trim().parse::().unwrap(); + last_coords.1 = new_y; + new_y + } + None => last_coords.1, // if match is None, then the coordinate must have been implicit + }; + + if regmatch.get(3).is_some() { + // we have X,Y,I,J parameters and we are doing circular interpolation + let i_offset = regmatch + .get(3) + .expect("Unable to match I offset") + .as_str() + .trim() + .parse::() + .unwrap(); + let j_offset = regmatch + .get(4) + .expect("Unable to match J offset") + .as_str() + .trim() + .parse::() + .unwrap(); + + gerber_doc.commands.push( + FunctionCode::DCode(DCode::Operation(Operation::Interpolate( + coordinates_from_gerber( + x_coord, + y_coord, + gerber_doc + .format_specification + .expect("Operation statement called before format specification"), + ), + Some(coordinates_offset_from_gerber( + i_offset, + j_offset, + gerber_doc.format_specification.unwrap(), + )), + ))) + .into(), + ); + } else { + // linear interpolation, only X,Y parameters + gerber_doc.commands.push( + FunctionCode::DCode(DCode::Operation(Operation::Interpolate( + coordinates_from_gerber( + x_coord, + y_coord, + gerber_doc + .format_specification + .expect("Operation statement called before format specification"), + ), + None, + ))) + .into(), + ); + } + } else { + panic!("Unable to parse D01 (interpolate) command: {}", line) + } +} + +// TODO clean up the except statements a bit +// parse a Gerber move or flash command (e.g. 'X2000Y40000D02*') +fn parse_move_or_flash( + line: &str, + re: &Regex, + gerber_doc: &mut GerberDoc, + last_coords: &mut (i64, i64), + flash: bool, +) { + if let Some(regmatch) = re.captures(line) { + let x_coord = match regmatch.get(1) { + Some(x) => { + let new_x = x.as_str().trim().parse::().unwrap(); + last_coords.0 = new_x; + new_x + } + None => last_coords.0, // if match is None, then the coordinate must have been implicit + }; + let y_coord = match regmatch.get(2) { + Some(y) => { + let new_y = y.as_str().trim().parse::().unwrap(); + last_coords.1 = new_y; + new_y + } + None => last_coords.1, // if match is None, then the coordinate must have been implicit + }; + + if flash { + gerber_doc.commands.push( + FunctionCode::DCode(DCode::Operation(Operation::Flash(coordinates_from_gerber( + x_coord, + y_coord, + gerber_doc + .format_specification + .expect("Operation statement called before format specification"), + )))) + .into(), + ); + } else { + gerber_doc.commands.push( + FunctionCode::DCode(DCode::Operation(Operation::Move(coordinates_from_gerber( + x_coord, + y_coord, + gerber_doc + .format_specification + .expect("Operation statement called before format specification"), + )))) + .into(), + ); + } + } else { + panic!("Unable to parse D02 (move) or D03 (flash) command") + } +} + +fn parse_load_mirroring(mut linechars: Chars, gerber_doc: &mut GerberDoc) { + // match linechars.next().unwrap() { + // 'N' => gerber_doc.commands.push(value), //LMN + // 'Y' => gerber_doc.commands.push(value), // LMY + // 'X' => match linechars.next() { + // Some('Y') => {} //LMXY + // None => {} // LMX + // _ => panic!("Invalid load mirroring (LM) command: {}", linechars.as_str()) + // } + // _ => panic!("Invalid load mirroring (LM) command: {}", linechars.as_str()) + // } + panic!("Load Mirroring (LM) command not supported yet.") +} + +// a step and repeat open statement has four (required) parameters that we need to extract +// X (pos int) Y (pos int), I (decimal), J (decimal) +fn parse_step_repeat_open(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { + println!("SR line: {}", &line); + if let Some(regmatch) = re.captures(line) { + gerber_doc.commands.push( + ExtendedCode::StepAndRepeat(StepAndRepeat::Open { + repeat_x: regmatch + .get(1) + .unwrap() + .as_str() + .trim() + .parse::() + .unwrap(), + repeat_y: regmatch + .get(2) + .unwrap() + .as_str() + .trim() + .parse::() + .unwrap(), + distance_x: regmatch + .get(3) + .unwrap() + .as_str() + .trim() + .parse::() + .unwrap(), + distance_y: regmatch + .get(4) + .unwrap() + .as_str() + .trim() + .parse::() + .unwrap(), + }) + .into(), + ); + } else { + panic!("Unable to parse Step and Repeat opening command") + } +} + +/// Parse an Aperture Attribute (%TF.[,]*%) into Command +/// +/// For now we consider two types of TA statements: +/// 1. Aperture Function (AperFunction) with field: String +/// 2. Drill tolerance (DrillTolerance) with fields: [1] num [2] num +/// +/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse! +/// +/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage! +fn parse_file_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { + let attr_args = get_attr_args(line); + if attr_args.len() >= 2 { + // we must have at least 1 field + //println!("TF args are: {:?}", attr_args); + let file_attr: FileAttribute = match attr_args[0] { + "Part" => match attr_args[1] { + "Single" => FileAttribute::Part(Part::Single), + "Array" => FileAttribute::Part(Part::Array), + "FabricationPanel" => FileAttribute::Part(Part::FabricationPanel), + "Coupon" => FileAttribute::Part(Part::Coupon), + "Other" => FileAttribute::Part(Part::Other(attr_args[2].to_string())), + _ => panic!("Unsupported Part type '{}' in TF statement", attr_args[1]), + }, + // TODO do FileFunction properly, but needs changes in gerber-types + "FileFunction" => { + FileAttribute::FileFunction(FileFunction::Other(attr_args[1].to_string())) + } + "FilePolarity" => match attr_args[1] { + "Positive" => FileAttribute::FilePolarity(FilePolarity::Positive), + "Negative" => FileAttribute::FilePolarity(FilePolarity::Negative), + _ => panic!( + "Unsupported Polarity type '{}' in TF statement", + attr_args[1] + ), + }, + "Md5" => FileAttribute::Md5(attr_args[1].to_string()), + _ => panic!( + "The AttributeName '{}' is currently not supported for File Attributes", + attr_args[0] + ), + }; + gerber_doc + .commands + .push(ExtendedCode::FileAttribute(file_attr).into()) + } else { + panic!("Unable to parse file attribute (TF)") + } +} + +/// Parse an Aperture Attribute (%TA.[,]*%) into Command +/// +/// For now we consider two types of TA statements: +/// 1. Aperture Function (AperFunction) with field: String +/// 2. Drill tolerance (DrillTolerance) with fields: [1] num [2] num +/// +/// ⚠️ Any other Attributes (which seem to be valid within the gerber spec) we will **fail** to parse! +/// +/// ⚠️ This parsing statement needs a lot of tests and validation at the current stage! +fn parse_aperture_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { + let attr_args = get_attr_args(line); + if attr_args.len() >= 2 { + // we must have at least 1 field + //println!("TA args are: {:?}", attr_args); + match attr_args[0] { + "AperFunction" => { + let aperture_func: ApertureFunction = match attr_args[1] { + "ViaDrill" => ApertureFunction::ViaDrill, + "BackDrill" => ApertureFunction::BackDrill, + "ComponentDrill" => ApertureFunction::ComponentDrill { press_fit: None }, // TODO parse this + "CastellatedDrill" => ApertureFunction::CastellatedDrill, + "MechanicalDrill" => ApertureFunction::MechanicalDrill { function: None }, // TODO parse this + "Slot" => ApertureFunction::Slot, + "CutOut" => ApertureFunction::CutOut, + "Cavity" => ApertureFunction::Cavity, + "OtherDrill" => ApertureFunction::OtherDrill(attr_args[2].to_string()), + "ComponentPad" => ApertureFunction::ComponentPad { press_fit: None }, // TODO parse this + "SmdPad" => match attr_args[2] { + "CopperDefined" => ApertureFunction::SmdPad(SmdPadType::CopperDefined), + "SoldermaskDefined" => { + ApertureFunction::SmdPad(SmdPadType::SoldermaskDefined) + } + _ => panic!("Unsupported SmdPad type in TA statement"), + }, + "BgaPad" => match attr_args[2] { + "CopperDefined" => ApertureFunction::BgaPad(SmdPadType::CopperDefined), + "SoldermaskDefined" => { + ApertureFunction::BgaPad(SmdPadType::SoldermaskDefined) + } + _ => panic!("Unsupported SmdPad type in TA statement"), + }, + "HeatsinkPad" => ApertureFunction::HeatsinkPad, + "TestPad" => ApertureFunction::TestPad, + "CastellatedPad" => ApertureFunction::CastellatedPad, + "FiducialPad" => match attr_args[2] { + "Global" => ApertureFunction::FiducialPad(FiducialScope::Global), + "Local" => ApertureFunction::FiducialPad(FiducialScope::Local), + _ => panic!("Unsupported FiducialPad type in TA statement"), + }, + "ThermalReliefPad" => ApertureFunction::ThermalReliefPad, + "WasherPad" => ApertureFunction::WasherPad, + "AntiPad" => ApertureFunction::AntiPad, + "OtherPad" => ApertureFunction::OtherPad(attr_args[2].to_string()), + "Conductor" => ApertureFunction::Conductor, + "NonConductor" => ApertureFunction::NonConductor, + "CopperBalancing" => ApertureFunction::CopperBalancing, + "Border" => ApertureFunction::Border, + "OtherCopper" => ApertureFunction::OtherCopper(attr_args[2].to_string()), + "Profile" => ApertureFunction::Profile, + "NonMaterial" => ApertureFunction::NonMaterial, + "Material" => ApertureFunction::Material, + "Other" => ApertureFunction::Other(attr_args[2].to_string()), + _ => panic!( + "The Aperture Function '{}' is currently not supported (/known)", + attr_args[1] + ), + }; + gerber_doc.commands.push( + ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction( + aperture_func, + )) + .into(), + ) + } + "DrillTolerance" => gerber_doc.commands.push( + ExtendedCode::ApertureAttribute(ApertureAttribute::DrillTolerance { + plus: attr_args[1].parse::().unwrap(), + minus: attr_args[2].parse::().unwrap(), + }) + .into(), + ), + _ => panic!( + "The AttributeName '{}' is currently not supported for Aperture Attributes", + attr_args[0] + ), + } + } else { + panic!("Unable to parse aperture attribute (TA)") + } +} + +fn parse_object_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { + let attr_args = get_attr_args(line); + if attr_args.len() >= 2 { + // gerber_doc.commands.push( + // ExtendedCode::ObjectAttribute(ObjectAttribute { + // attribute_name: attr_args[0].to_string(), + // values: attr_args[1..] + // .into_iter() + // .map(|val| val.to_string()) + // .collect(), + // }) + // .into(), + // ) + } else if attr_args.len() == 1 { + panic!("Unable to add Object Attribute (TO) - TO statements need at least 1 field value on top of the name: '{}'", attr_args[0]); + } else { + panic!("Unable to parse object attribute (TO)"); + } +} + +fn parse_delete_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { + let attr_args = get_attr_args(line); + match attr_args.len() { + 1 => gerber_doc + .commands + .push(ExtendedCode::DeleteAttribute(attr_args[0].to_string()).into()), + x if x > 1 => panic!( + "Unable to parse delete attribute (TD) - TD should not have any fields, got field '{}'", + attr_args[0] + ), + _ => panic!("Unable to parse delete attribute (TD)"), + } +} + +/// Extract the individual elements (AttributeName and Fields) from Chars +/// +/// The arguments of the attribute statement can have whitespace as this will be trimmed. +/// `attribute_chars` argument must be the **trimmed line** from the gerber file +/// with the **first three characters removed**. E.g. ".Part,single*%" not "%TF.Part,single*%" +/// ``` +/// # use gerber_parser::parser::get_attr_args; +/// let attribute_chars = ".DrillTolerance, 0.02, 0.01 *%".chars(); +/// +/// let arguments = get_attr_args(attribute_chars); +/// assert_eq!(arguments, vec!["DrillTolerance","0.02","0.01"]) +/// ``` +pub fn get_attr_args(mut attribute_chars: Chars) -> Vec<&str> { + attribute_chars.next_back().unwrap(); + attribute_chars.next_back().unwrap(); + if attribute_chars.next().is_some() { + attribute_chars + .as_str() + .split(",") + .map(|el| el.trim()) + .collect() + } else { + vec![""] + } +} + +pub fn coordinates_from_gerber( + mut x_as_int: i64, + mut y_as_int: i64, + fs: CoordinateFormat, +) -> Coordinates { + // we have the raw gerber string as int but now have to convert it to nano precision format + // (i.e. 6 decimal precision) as this is what CoordinateNumber uses internally + let factor = (6u8 - fs.decimal) as u32; + x_as_int *= 10i64.pow(factor); + y_as_int *= 10i64.pow(factor); + Coordinates::new( + CoordinateNumber::new(x_as_int), + CoordinateNumber::new(y_as_int), + fs, + ) +} + +pub fn coordinates_offset_from_gerber( + mut x_as_int: i64, + mut y_as_int: i64, + fs: CoordinateFormat, +) -> CoordinateOffset { + // we have the raw gerber string as int but now have to convert it to nano precision format + // (i.e. 6 decimal precision) as this is what CoordinateNumber uses internally + let factor = (6u8 - fs.decimal) as u32; + x_as_int *= 10i64.pow(factor); + y_as_int *= 10i64.pow(factor); + CoordinateOffset::new( + CoordinateNumber::new(x_as_int), + CoordinateNumber::new(y_as_int), + fs, + ) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d23b413 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +mod application; +mod excellon; +mod export; +mod geometry; +mod gerber; +mod outline_geometry; + +use application::Application; + +use eframe::egui; +use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer}; + +const APP_NAME: &str = "Outlinify"; + +fn main() -> eframe::Result { + let stdout_log = tracing_subscriber::fmt::layer() + .pretty() + .with_filter(filter::filter_fn(|metadata| { + metadata.target().starts_with(env!("CARGO_CRATE_NAME")) + })) + .with_filter(filter::LevelFilter::DEBUG); + + // Register subscriptions + tracing_subscriber::registry().with(stdout_log).init(); + + let application = Application::new(); + + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]), + ..Default::default() + }; + eframe::run_native( + APP_NAME, + options, + Box::new(|cc| { + // This gives us image support: + // egui_extras::install_image_loaders(&cc.egui_ctx); + + Ok(Box::new(application)) + }), + ) +} diff --git a/src/outline_geometry/mod.rs b/src/outline_geometry/mod.rs new file mode 100644 index 0000000..eae6afd --- /dev/null +++ b/src/outline_geometry/mod.rs @@ -0,0 +1,214 @@ +use clipper2::{Bounds, EndType, JoinType, One, Paths}; +use eframe::{ + egui::{Rect, Shape, Stroke}, + epaint::{PathShape, PathStroke}, +}; +use egui_plot::{PlotBounds, PlotItem, PlotPoint, Polygon}; + +use crate::{ + application::CanvasColour, + geometry::{elements::circle::Circle, point::Point, ClipperBounds, ClipperPaths, Unit}, +}; + +#[derive(Debug, Clone)] +pub enum GeometryType { + Paths(ClipperPaths), + Points(Vec), +} + +#[derive(Debug)] +pub struct OutlineGeometry { + // pub path: ClipperPaths, + // pub points: Vec, + items: GeometryType, + pub stroke: f32, + pub unit: Unit, + pub bounds_from: String, + pub bounding_box: ClipperBounds, +} + +impl OutlineGeometry { + pub fn new( + outline: &ClipperPaths, + stroke: f32, + unit: Unit, + bounds_from: &str, + bounds: ClipperBounds, + ) -> Self { + // inflate given path + let outline = outline + .clone() + .inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.0) + .simplify(0.01, false); + Self { + // path: outline, + // points: Vec::new(), + items: GeometryType::Paths(outline), + stroke, + unit, + bounds_from: bounds_from.into(), + bounding_box: bounds, + } + } + + pub fn new_no_inflate( + outline: &ClipperPaths, + stroke: f32, + unit: Unit, + bounds_from: &str, + bounds: ClipperBounds, + ) -> Self { + Self { + // path: outline.clone(), + // points: Vec::new(), + items: GeometryType::Paths(outline.clone()), + stroke, + unit, + bounds_from: bounds_from.into(), + bounding_box: bounds, + } + } + + pub fn point_marker( + points: Vec, + stroke: f32, + unit: Unit, + bounds_from: &str, + bounds: ClipperBounds, + ) -> Self { + Self { + // path: Paths::new(vec![]), + // points, + items: GeometryType::Points(points), + stroke, + unit, + bounds_from: bounds_from.into(), + bounding_box: bounds, + } + } + + pub fn paths(&self) -> ClipperPaths { + match &self.items { + GeometryType::Paths(paths) => paths.clone(), + GeometryType::Points(_) => ClipperPaths::new(vec![]), + } + } + + pub fn points(&self) -> Vec { + match &self.items { + GeometryType::Paths(_) => vec![], + GeometryType::Points(p) => p.clone(), + } + } +} + +pub struct OutlineShape { + stroke: f32, + selected: bool, + colour: CanvasColour, + items: GeometryType, + bounds: ClipperBounds, +} + +// impl OutlineShape { +// pub fn new_from_geometry( +// geometry: &OutlineGeometry, +// colour: CanvasColour, +// selected: bool, +// ) -> Self { +// Self { +// stroke: geometry.stroke, +// selected, +// items: geometry.items.clone(), +// bounds: geometry.bounding_box, +// colour, +// } +// } +// } + +// impl PlotItem for OutlineShape { +// fn shapes( +// &self, +// _ui: &eframe::egui::Ui, +// transform: &egui_plot::PlotTransform, +// shapes: &mut Vec, +// ) { +// match &self.items { +// GeometryType::Paths(paths) => { +// for path in paths.iter() { +// let values_tf: Vec<_> = path +// .iter() +// .map(|v| transform.position_from_point(&PlotPoint::from(Point::from(v)))) +// .collect(); + +// let shape = PathShape::closed_line( +// values_tf.clone(), +// PathStroke::new(20., self.colour.to_colour32(self.selected)), +// ); + +// shapes.push(shape.into()); +// } +// } +// GeometryType::Points(points) => { +// for point in points { +// // draw outline of hole +// let points = Circle::circle_segment_points( +// Point::from(transform.position_from_point(&PlotPoint::from(*point))), +// self.stroke.into(), +// 1., +// 0., +// ) +// .into_iter() +// .map(|p| Point::from(p).into()) +// .collect(); + +// let polygon = Shape::convex_polygon( +// points, +// self.colour.to_colour32(self.selected), +// Stroke::NONE, +// ); +// shapes.push(polygon); +// } +// // TODO draw point circles +// } +// } + +// // todo!() +// } + +// fn initialize(&mut self, x_range: std::ops::RangeInclusive) { +// {} +// } + +// fn name(&self) -> &str { +// "Test" +// } + +// fn color(&self) -> eframe::egui::Color32 { +// self.colour.to_colour32(self.selected) +// } + +// fn highlight(&mut self) { +// {} +// } + +// fn highlighted(&self) -> bool { +// false +// } + +// fn allow_hover(&self) -> bool { +// false +// } + +// fn geometry(&self) -> egui_plot::PlotGeometry<'_> { +// todo!() +// } + +// fn bounds(&self) -> PlotBounds { +// PlotBounds::from_min_max(self.bounds.min.into(), self.bounds.max.into()) +// } + +// fn id(&self) -> Option { +// None +// } +// }