diff --git a/.vscode/settings.json b/.vscode/settings.json index 61b2eca..234d2b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ + "centered", + "cmds", "color", "Color", "consts", @@ -8,11 +10,14 @@ "egui", "emath", "epaint", + "evalexpr", "excellon", "Excellon", "excellons", "gerbers", "Heatsink", + "itertools", + "Itertools", "linepath", "obround", "Obround", @@ -21,6 +26,7 @@ "rect", "Rect", "regmatch", - "Soldermask" + "Soldermask", + "winresource" ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f7321ee..87abfc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.18" @@ -182,6 +188,12 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arboard" version = "3.4.0" @@ -197,6 +209,17 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "arrayref" version = "0.3.8" @@ -531,6 +554,41 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational 0.4.2", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.5.3" @@ -546,6 +604,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -558,6 +622,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bitstream-io" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" + [[package]] name = "block" version = "0.1.6" @@ -633,6 +703,12 @@ dependencies = [ "piper", ] +[[package]] +name = "built" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" + [[package]] name = "bumpalo" version = "3.16.0" @@ -745,6 +821,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -975,6 +1061,12 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -997,6 +1089,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deflate" version = "0.8.6" @@ -1069,7 +1167,7 @@ dependencies = [ "encoding_rs", "enum_primitive", "image 0.23.14", - "itertools", + "itertools 0.10.5", "num", "uuid 0.8.2", "xmltree", @@ -1173,6 +1271,23 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_extras" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb783d9fa348f69ed5c340aa25af78b5472043090e8b809040e30960cc2a746" +dependencies = [ + "ahash", + "egui", + "ehttp", + "enum-map", + "image 0.25.2", + "log", + "mime_guess2", + "resvg", + "serde", +] + [[package]] name = "egui_glow" version = "0.28.1" @@ -1202,6 +1317,20 @@ dependencies = [ "serde", ] +[[package]] +name = "ehttp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876" +dependencies = [ + "document-features", + "js-sys", + "ureq", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "either" version = "1.13.0" @@ -1233,6 +1362,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "enum_primitive" version = "0.1.1" @@ -1323,6 +1473,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "evalexpr" +version = "11.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b41cb9dd076076058a4523f009c900c582279536d0b2e45a29aa930e083cc5" + [[package]] name = "event-listener" version = "2.5.3" @@ -1361,6 +1517,22 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.7.4", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1395,6 +1567,21 @@ dependencies = [ "miniz_oxide 0.7.4", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1533,8 +1720,6 @@ dependencies = [ [[package]] name = "gerber-types" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecf78a269d7b4f73953f30174b18f52c1bb44f7d58f4d830a76c0ea023ad8bc" dependencies = [ "chrono", "conv", @@ -1574,6 +1759,16 @@ dependencies = [ "weezl", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gl_generator" version = "0.14.0" @@ -1714,6 +1909,16 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1739,6 +1944,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1825,14 +2036,14 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "gif", - "jpeg-decoder", + "gif 0.11.4", + "jpeg-decoder 0.1.22", "num-iter", "num-rational 0.3.2", "num-traits 0.2.19", "png 0.16.8", "scoped_threadpool", - "tiff", + "tiff 0.6.1", ] [[package]] @@ -1843,10 +2054,43 @@ checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", "byteorder-lite", + "color_quant", + "exr", + "gif 0.13.1", + "image-webp", "num-traits 0.2.19", "png 0.17.13", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff 0.9.1", + "zune-core", + "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.6" @@ -1866,6 +2110,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1886,6 +2141,24 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.21.1" @@ -1926,6 +2199,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -1952,6 +2231,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "lazy-regex" version = "3.1.0" @@ -1981,12 +2269,29 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -2052,6 +2357,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2061,6 +2375,15 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2109,6 +2432,28 @@ dependencies = [ "paste", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.3.7" @@ -2190,6 +2535,12 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.26.4" @@ -2221,6 +2572,22 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2275,6 +2642,17 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2559,14 +2937,19 @@ dependencies = [ "clipper2", "dxf", "eframe", + "egui_extras", "egui_plot", "error-stack", + "evalexpr", "gerber-types", + "image 0.25.2", + "itertools 0.13.0", "lazy-regex", "rfd", "svg", "tracing", "tracing-subscriber", + "winresource", ] [[package]] @@ -2625,6 +3008,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -2761,6 +3150,34 @@ name = "profiling" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.72", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" @@ -2810,6 +3227,55 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.12.1", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits 0.2.19", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rgb", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -2842,6 +3308,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2904,6 +3376,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + [[package]] name = "rfd" version = "0.14.1" @@ -2927,6 +3413,36 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rgb" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2969,6 +3485,38 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3046,6 +3594,15 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3081,6 +3638,30 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -3185,6 +3766,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -3205,6 +3795,15 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "subtle" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5" [[package]] name = "svg" @@ -3212,6 +3811,16 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "700efb40f3f559c23c18b446e8ed62b08b56b2bb3197b36d57e0470b4102779e" +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -3234,6 +3843,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 0.8.16", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.10.1" @@ -3291,11 +3919,22 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" dependencies = [ - "jpeg-decoder", + "jpeg-decoder 0.1.22", "miniz_oxide 0.4.4", "weezl", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder 0.3.1", + "weezl", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3307,6 +3946,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png 0.17.13", "tiny-skia-path", ] @@ -3336,11 +3976,38 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.17", +] + [[package]] name = "toml_datetime" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3349,8 +4016,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -3361,7 +4030,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.18", ] [[package]] @@ -3453,6 +4135,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -3492,6 +4183,28 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +dependencies = [ + "base64 0.22.1", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.2" @@ -3510,6 +4223,50 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64 0.21.7", + "log", + "pico-args", + "usvg-parser", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] + [[package]] name = "uuid" version = "0.8.2" @@ -3526,12 +4283,29 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits 0.2.19", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -3798,6 +4572,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" @@ -4260,6 +5043,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + +[[package]] +name = "winresource" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329" +dependencies = [ + "toml 0.7.8", + "version_check", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -4351,6 +5153,12 @@ dependencies = [ "xml-rs 0.7.0", ] +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zbus" version = "3.15.2" @@ -4499,6 +5307,36 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 9d7b2f9..77eeb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,24 @@ edition = "2021" [dependencies] eframe = "0.28.1" egui_plot = { version = "0.28.1", features = ["serde"] } +egui_extras = { version = "0.28.1", features = ["all_loaders"] } rfd = "0.14" +image = "0.25" clipper2 = "0.4.1" -gerber-types = "0.3" +# gerber-types = "0.3" +gerber-types = { path = "./gerber-types-rs" } svg = "0.17" dxf = "0.5" lazy-regex = "3.1.0" +itertools = "0.13" +evalexpr = "11.3.0" tracing = "0.1" tracing-subscriber = "0.3" error-stack = "0.5" + +[build-dependencies] +winresource = "0.1" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..cfdddc7 --- /dev/null +++ b/build.rs @@ -0,0 +1,14 @@ +use { + std::{env, io}, + winresource::WindowsResource, +}; + +fn main() -> io::Result<()> { + if env::var_os("CARGO_CFG_WINDOWS").is_some() { + WindowsResource::new() + // This path can be absolute, or relative to your crate root. + .set_icon("resources/icon.ico") + .compile()?; + } + Ok(()) +} diff --git a/resources/excellon_file.afdesign b/resources/excellon_file.afdesign new file mode 100644 index 0000000..535dd1f Binary files /dev/null and b/resources/excellon_file.afdesign differ diff --git a/resources/excellon_file.png b/resources/excellon_file.png new file mode 100644 index 0000000..7e605ef Binary files /dev/null and b/resources/excellon_file.png differ diff --git a/resources/geometry_file.afdesign b/resources/geometry_file.afdesign new file mode 100644 index 0000000..9067699 Binary files /dev/null and b/resources/geometry_file.afdesign differ diff --git a/resources/geometry_file.png b/resources/geometry_file.png new file mode 100644 index 0000000..a176ba8 Binary files /dev/null and b/resources/geometry_file.png differ diff --git a/resources/gerber_file.afdesign b/resources/gerber_file.afdesign new file mode 100644 index 0000000..f97ee2a Binary files /dev/null and b/resources/gerber_file.afdesign differ diff --git a/resources/gerber_file.png b/resources/gerber_file.png new file mode 100644 index 0000000..6da4d2f Binary files /dev/null and b/resources/gerber_file.png differ diff --git a/resources/icon.ico b/resources/icon.ico new file mode 100644 index 0000000..77fa480 Binary files /dev/null and b/resources/icon.ico differ diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000..b0a5ce7 Binary files /dev/null and b/resources/icon.png differ diff --git a/src/application/canvas/excellons.rs b/src/application/canvas/excellons.rs index 025f068..6cdfc58 100644 --- a/src/application/canvas/excellons.rs +++ b/src/application/canvas/excellons.rs @@ -1,15 +1,8 @@ -use eframe::{ - egui::{Color32, Ui}, - epaint::CircleShape, -}; use egui_plot::PlotUi; -use crate::{ - application::Application, - geometry::{elements::Element, DrawableRaw}, -}; +use crate::{application::Application, geometry::elements::Element}; -use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; +use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer}; pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) { for (file_name, (_, excellon)) in &app.excellons { @@ -18,7 +11,6 @@ pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) { for circle in excellon.holes.iter() { draw_on_plot_canvas( ui, - app, PlotDrawer::Drawable(( &Element::Circle(circle.to_owned()), CanvasColour::Excellon, @@ -28,29 +20,3 @@ pub fn draw_excellons_(ui: &mut PlotUi, app: &mut Application) { } } } - -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 index 6f2d580..2b56d1b 100644 --- a/src/application/canvas/geometries.rs +++ b/src/application/canvas/geometries.rs @@ -1,7 +1,4 @@ -use eframe::{ - egui::{Color32, Pos2, Ui}, - epaint::{CircleShape, PathShape, PathStroke}, -}; +use eframe::egui::Pos2; use egui_plot::{Line, PlotPoint, PlotPoints, PlotUi}; use crate::{ @@ -9,7 +6,7 @@ use crate::{ geometry::{elements::circle::Circle, DrawableRaw}, }; -use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; +use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer}; pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) { for (file_name, geo) in &app.outlines { @@ -19,7 +16,6 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) { for path in geo.paths().iter() { draw_on_plot_canvas( ui, - app, PlotDrawer::Closure(&|ui| { // draw outline path let mut points = path @@ -40,7 +36,7 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) { points.push(points[1]); let line = Line::new(PlotPoints::from(points)) .width(width) - .color(CanvasColour::Outline.to_colour32(selected)); + .color(CanvasColour::Outline.as_colour32(selected)); ui.line(line) }), @@ -51,99 +47,11 @@ pub fn draw_geometries_(ui: &mut PlotUi, app: &mut Application) { 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 index a73691e..7572c2e 100644 --- a/src/application/canvas/gerbers.rs +++ b/src/application/canvas/gerbers.rs @@ -1,12 +1,11 @@ -use eframe::{ - egui::{Color32, Pos2, Ui}, - epaint::{PathShape, PathStroke}, -}; use egui_plot::{Line, PlotPoints, PlotUi}; -use crate::{application::Application, geometry::DrawableRaw}; +use crate::{ + application::Application, + geometry::{ClipperPaths, DrawableRaw}, +}; -use super::{draw_floating_area_on_canvas, draw_on_plot_canvas, CanvasColour, Drawer, PlotDrawer}; +use super::{draw_on_plot_canvas, CanvasColour, PlotDrawer}; pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) { for (file_name, (_, geo)) in &app.gerbers { @@ -16,7 +15,6 @@ pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) { for geometry in geo.apertures.iter() { draw_on_plot_canvas( ui, - app, PlotDrawer::Drawable((geometry, CanvasColour::Copper, selected)), ); } @@ -25,13 +23,25 @@ pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) { for line in geo.paths.iter() { draw_on_plot_canvas( ui, - app, PlotDrawer::Closure(&|ui| line.draw_egui_plot(ui, CanvasColour::Copper, selected)), ); } - // draw outline union path - for path in geo.outline_union.iter() { + // draw conductor outlines + for net in &geo.united_nets.conductors { + draw_paths_outline(ui, &net.outline, selected); + } + + // draw isolated aperture outlines + for element in &geo.united_nets.isolated_apertures { + draw_paths_outline(ui, &element.to_paths(), selected); + } + } +} + +fn draw_paths_outline(ui: &mut PlotUi, paths: &ClipperPaths, selected: bool) { + for path in paths.iter() { + if !path.is_empty() { let mut points = path .iter() .map(|p| [p.x(), p.y()]) @@ -39,63 +49,9 @@ pub fn draw_gerbers_(ui: &mut PlotUi, app: &mut Application) { points.push(points[0]); let line = Line::new(PlotPoints::from(points)) - .color(CanvasColour::CopperOutline.to_colour32(selected)); + .color(CanvasColour::CopperOutline.as_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 deleted file mode 100644 index 0e62b3e..0000000 --- a/src/application/canvas/live_position.rs +++ /dev/null @@ -1,63 +0,0 @@ -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 index 054a817..61cd8d1 100644 --- a/src/application/canvas/mod.rs +++ b/src/application/canvas/mod.rs @@ -1,19 +1,12 @@ 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 eframe::egui::{Color32, Ui}; 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 excellons::draw_excellons_; +use geometries::draw_geometries_; +use gerbers::draw_gerbers_; use crate::geometry::elements::Element; @@ -28,6 +21,7 @@ const EXCELLON_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(65, 42 const OUTLINE_COLOR: Color32 = Color32::DARK_BLUE; const OUTLINE_COLOR_SELECTED: Color32 = Color32::from_rgba_premultiplied(0, 0, 139, 128); +#[derive(Debug, Clone, Copy)] pub enum CanvasColour { Copper, CopperOutline, @@ -36,7 +30,7 @@ pub enum CanvasColour { } impl CanvasColour { - pub fn to_colour32(&self, selected: bool) -> Color32 { + pub fn as_colour32(&self, selected: bool) -> Color32 { match (self, selected) { (CanvasColour::Copper, true) => COPPER_COLOR_SELECTED, (CanvasColour::Copper, false) => COPPER_COLOR, @@ -59,48 +53,6 @@ pub fn draw_canvas(ui: &mut Ui, app: &mut Application) { 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> { @@ -108,7 +60,7 @@ pub enum PlotDrawer<'a> { Closure(&'a dyn Fn(&mut PlotUi)), } -fn draw_on_plot_canvas(ui: &mut PlotUi, app: &Application, drawer: PlotDrawer) { +fn draw_on_plot_canvas(ui: &mut PlotUi, 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 index 5771a68..566d2e2 100644 --- a/src/application/egui.rs +++ b/src/application/egui.rs @@ -1,7 +1,4 @@ -use eframe::{ - egui::{self, Vec2}, - emath::TSTransform, -}; +use eframe::egui; use super::{ canvas::draw_canvas, @@ -9,230 +6,17 @@ use super::{ Application, }; -impl eframe::App for Application { +impl<'a> eframe::App for Application<'a> { 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/errors.rs b/src/application/errors.rs new file mode 100644 index 0000000..b1cc8b5 --- /dev/null +++ b/src/application/errors.rs @@ -0,0 +1,20 @@ +use std::fmt; + +use error_stack::{Context, Report}; + +#[derive(Debug)] +pub struct FileError; + +impl FileError { + pub fn new(text: &str) -> Report { + Report::new(Self).attach_printable(text.to_string()) + } +} + +impl fmt::Display for FileError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str("Error opening file") + } +} + +impl Context for FileError {} diff --git a/src/application/mod.rs b/src/application/mod.rs index a8af8ad..9d49072 100644 --- a/src/application/mod.rs +++ b/src/application/mod.rs @@ -1,30 +1,24 @@ mod canvas; mod egui; +mod errors; 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, + resources::ResourceLoader, }; pub use canvas::CanvasColour; -pub struct Application { +pub struct Application<'a> { + resources: ResourceLoader<'a>, gerbers: HashMap, outlines: HashMap, excellons: HashMap, - // geometry: Geometry, - transform: TSTransform, - test_transform: TSTransform, - canvas: (Id, Rect), selection: String, variables: Variables, } @@ -35,19 +29,13 @@ pub struct Variables { units: Unit, } -impl Application { +impl<'a> Application<'a> { pub fn new() -> Self { Self { + resources: ResourceLoader::new(), 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 index 46763a7..0cc1d58 100644 --- a/src/application/panels/actions/excellon_actions.rs +++ b/src/application/panels/actions/excellon_actions.rs @@ -1,51 +1,51 @@ +use std::collections::hash_map::Entry; + use eframe::egui::Ui; -use crate::{ - application::Application, - geometry::{ClipperPath, ClipperPaths, DrawableRaw}, - outline_geometry::OutlineGeometry, -}; +use crate::{application::Application, geometry::ClipperPath, 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 + if app.excellons.contains_key(&app.selection) { + match app.excellons.entry(app.selection.to_string()) { + Entry::Occupied(c) => { + let mut deletion_request = false; + let (name, drill) = c.get(); + if ui.button("Delete").clicked() { + deletion_request = true; + } + ui.horizontal(|ui| { + if ui.button("Generate cut out").clicked() { + let path = drill .holes .iter() .map(|hole| hole.outline.clone()) - .collect::>(), - ) - .bounds(), - ), - ); + .collect::>() + .into(); + app.outlines.insert( + format!("{name}-CutOut"), + OutlineGeometry::new_no_inflate(&path, 0.1, app.variables.units, name), + ); + } + if ui.button("Generate hole mark").clicked() { + app.outlines.insert( + format!("{name}-HoleMark"), + OutlineGeometry::drill_marker( + drill, + HOLE_MARK_DIAMETER, + app.variables.units, + name, + ), + ); + } + }); + + if deletion_request { + c.remove(); + } + } + Entry::Vacant(_) => unreachable!(), } } } diff --git a/src/application/panels/actions/geometry_actions.rs b/src/application/panels/actions/geometry_actions.rs index 0bed6d5..a150d7d 100644 --- a/src/application/panels/actions/geometry_actions.rs +++ b/src/application/panels/actions/geometry_actions.rs @@ -1,34 +1,59 @@ +use std::collections::hash_map::Entry; + 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 app.outlines.contains_key(&app.selection) { + match app.outlines.entry(app.selection.to_string()) { + Entry::Occupied(mut c) => { + let mut deletion_request = false; + let isolated_tracks = c.get_mut(); + + if ui.button("Delete").clicked() { + deletion_request = true; + } + + ui.horizontal(|ui| { + ui.label("BoundingBox"); + let id = ui.make_persistent_id("BoundingBoxSelection"); + ComboBox::new(id, "") + .selected_text(format!("{:?}", isolated_tracks.bounds_from)) + .show_ui(ui, |ui| { + for (key, (name, _)) in app.gerbers.iter() { + if ui + .selectable_value( + &mut isolated_tracks.bounds_from, + name.into(), + name, + ) + .clicked() + { + if let Some((_, box_geo)) = app.gerbers.get(key) { + isolated_tracks.bounding_box = box_geo.united_nets.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(isolated_tracks, &path); } } - } - }); + }); - 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); + if deletion_request { + c.remove(); + } } + Entry::Vacant(_) => unreachable!(), } } } diff --git a/src/application/panels/actions/gerber_actions.rs b/src/application/panels/actions/gerber_actions.rs index 2495bcf..21f3648 100644 --- a/src/application/panels/actions/gerber_actions.rs +++ b/src/application/panels/actions/gerber_actions.rs @@ -1,28 +1,45 @@ +use std::collections::hash_map::Entry; + 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 app.gerbers.contains_key(&app.selection) { + match app.gerbers.entry(app.selection.to_string()) { + Entry::Occupied(c) => { + let mut deletion_request = false; + let (name, gerber) = c.get(); - 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(), - ), - ); + if ui.button("Delete").clicked() { + deletion_request = true; + } + ui.horizontal(|ui| { + ui.add( + DragValue::new(&mut app.variables.laser_line_width) + .range(0.1..=10.) + .speed(0.1) + .suffix(gerber.units), + ); + + if ui.button("Generate Isolation").clicked() { + app.outlines.insert( + format!("{name}-Iso"), + OutlineGeometry::new( + &gerber.united_nets, + app.variables.laser_line_width, + gerber.units, + name, + ), + ); + } + }); + + if deletion_request { + c.remove(); + } } - }); + Entry::Vacant(_) => unreachable!(), + } } } diff --git a/src/application/panels/header.rs b/src/application/panels/header.rs index 4242db8..010de46 100644 --- a/src/application/panels/header.rs +++ b/src/application/panels/header.rs @@ -1,62 +1,192 @@ -use std::{fs::File, io::BufReader}; +use std::{ + fs::File, + io::{BufReader, Read}, +}; use eframe::egui; +use error_stack::{Report, ResultExt}; +use tracing::error; use crate::{ - application::Application, - excellon::{drills::Drills, parse_excellon}, + application::{errors::FileError, Application}, + excellon::{doc::ExcellonDoc, drills::Drills, parse_excellon}, geometry::Geometry, - gerber::parse_gerber, + gerber::{doc::GerberDoc, parse_gerber}, }; +const GERBER_EXTENSIONS: &[&str] = &["GBR ", "gbr", "GB", "geb"]; +const EXCELLON_EXTENSIONS: &[&str] = &["DRL ", "drl"]; + 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 - }; + ui.horizontal_centered(|ui| { + if let Some(opened_files) = ui + .menu_button("📝 Open File", open_file_menu) + .inner + .flatten() + { + match opened_files { + Ok(files) => { + for (path, name, content) in files { + match content { + AppFileContent::Gerber(gerber) => { + app.gerbers.insert( + path, + ( + name, + Geometry::from(gerber) + .into_unit(app.variables.units), + ), + ); + } + AppFileContent::Excellon(excellon) => { + let drills: Drills = excellon.into(); + app.excellons.insert( + path, + (name, drills.into_unit(app.variables.units)), + ); + } + } + } + } + Err(e) => { + // TODO Show error + error!("{e:?}"); } } - } - - 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 - }; - } - } - } + }; }) }); } + +type FileOpenResponse = Result, Report>; + +fn open_file_menu(ui: &mut egui::Ui) -> Option { + let mut response = None; + ui.set_max_width(200.0); // To make sure we wrap long text + + if ui.button("Gerber").clicked() { + response = Some(open_file(AppFileType::Gerber)); + ui.close_menu(); + } + + if ui.button("Excellon").clicked() { + ui.close_menu(); + response = Some(open_file(AppFileType::Excellon)); + } + + // ui.menu_button("SubMenu", |ui| { + // ui.menu_button("SubMenu", |ui| { + // if ui.button("Open…").clicked() { + // ui.close_menu(); + // } + // let _ = ui.button("Item"); + // }); + // ui.menu_button("SubMenu", |ui| { + // if ui.button("Open…").clicked() { + // ui.close_menu(); + // } + // let _ = ui.button("Item"); + // }); + // let _ = ui.button("Item"); + // if ui.button("Open…").clicked() { + // ui.close_menu(); + // } + // }); + // ui.menu_button("SubMenu", |ui| { + // let _ = ui.button("Item1"); + // let _ = ui.button("Item2"); + // let _ = ui.button("Item3"); + // let _ = ui.button("Item4"); + // if ui.button("Open…").clicked() { + // ui.close_menu(); + // } + // }); + // let _ = ui.button("Very long text for this item that should be wrapped"); + + response +} + +#[derive(Debug, Clone, Copy)] +enum AppFileType { + Gerber, + Excellon, +} + +impl std::fmt::Display for AppFileType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + AppFileType::Gerber => "Gerber", + AppFileType::Excellon => "Excellon", + } + ) + } +} + +impl AppFileType { + pub fn extensions(&self) -> &[&str] { + match self { + AppFileType::Gerber => GERBER_EXTENSIONS, + AppFileType::Excellon => EXCELLON_EXTENSIONS, + } + } +} + +pub enum AppFileContent { + Gerber(GerberDoc), + Excellon(ExcellonDoc), +} + +impl AppFileContent { + fn from_file( + file_type: AppFileType, + file: BufReader, + ) -> Result> { + Ok(match file_type { + AppFileType::Gerber => Self::Gerber(parse_gerber(file)), + AppFileType::Excellon => Self::Excellon( + parse_excellon(file) + .change_context(FileError) + .attach_printable("Could not parse Excellon file")?, + ), + }) + } +} + +fn open_file( + file_type: AppFileType, +) -> Result, Report> { + let mut result = Vec::new(); + if let Some(paths) = rfd::FileDialog::new() + .add_filter(file_type.to_string(), file_type.extensions()) + .pick_files() + { + for path in paths { + // get file content + let file = File::open(&path).change_context(FileError)?; + // parse file format + let content = AppFileContent::from_file(file_type, BufReader::new(file))?; + // get file path + let path_str = path + .to_str() + .ok_or(FileError::new("Could not get file path string"))?; + // get file name + let name_str = path + .file_name() + .ok_or(FileError::new( + "Could not determine file name from selection", + ))? + .to_str() + .ok_or(FileError::new("Could not convert OS String"))?; + + result.push((path_str.into(), name_str.into(), content)); + } + } + + Ok(result) +} diff --git a/src/application/panels/sidebar.rs b/src/application/panels/sidebar.rs index c271470..c4d6395 100644 --- a/src/application/panels/sidebar.rs +++ b/src/application/panels/sidebar.rs @@ -1,4 +1,4 @@ -use eframe::egui::{self, CollapsingHeader}; +use eframe::egui::{self, RichText}; use crate::{application::Application, APP_NAME}; @@ -6,30 +6,74 @@ pub fn draw_sidebar(ctx: &egui::Context, app: &mut Application) { egui::SidePanel::left("left_panel") .exact_width(230.) .show(ctx, |ui| { + ui.add_space(6.0); + ui.heading(APP_NAME); - CollapsingHeader::new("Gerber") - .default_open(true) - .show(ui, |ui| { + + ui.add_space(12.0); + + let id = ui.make_persistent_id("GerberContainer"); + egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true) + .show_header(ui, |ui| { + ui.horizontal_centered(|ui| { + ui.image(app.resources.icons().gerber_file.clone()); + ui.label( + RichText::new("Gerber") + .extra_letter_spacing(1.2) + .size(17.) + .strong(), + ); + }); + }) + .body(|ui| { for (key, (name, _)) in app.gerbers.iter() { - ui.selectable_value(&mut app.selection, key.to_string(), name); + ui.selectable_value( + &mut app.selection, + key.to_string(), + RichText::new(name).size(11.), + ); } }); - CollapsingHeader::new("Excellon") - .default_open(true) - .show(ui, |ui| { + let id = ui.make_persistent_id("ExcellonContainer"); + egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true) + .show_header(ui, |ui| { + ui.horizontal_centered(|ui| { + ui.image(app.resources.icons().excellon_file.clone()); + ui.label( + RichText::new("Excellon") + .extra_letter_spacing(1.2) + .size(17.) + .strong(), + ); + }); + }) + .body(|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| { + let id = ui.make_persistent_id("GeometryContainer"); + egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, true) + .show_header(ui, |ui| { + ui.horizontal_centered(|ui| { + ui.image(app.resources.icons().geometry_file.clone()); + ui.label( + RichText::new("Geometry") + .extra_letter_spacing(1.2) + .size(17.) + .strong(), + ); + }); + }) + .body(|ui| { for (key, _) in app.outlines.iter() { ui.selectable_value(&mut app.selection, key.to_string(), key); } }); + + // allocate rest of the container let (id, rect) = ui.allocate_space(ui.available_size()); let response = ui.interact(rect, id, egui::Sense::click_and_drag()); if response.clicked() { diff --git a/src/excellon/drills.rs b/src/excellon/drills.rs index 88beca8..2343d39 100644 --- a/src/excellon/drills.rs +++ b/src/excellon/drills.rs @@ -41,13 +41,13 @@ impl From for Drills { } impl Drills { - pub fn to_unit(&self, unit: Unit) -> Self { + pub fn into_unit(self, unit: Unit) -> Self { Self { units: unit, holes: self .holes - .iter() - .map(|hole| hole.to_unit(self.units, unit)) + .into_iter() + .map(|hole| hole.into_unit(self.units, unit)) .collect(), } } diff --git a/src/excellon/mod.rs b/src/excellon/mod.rs index 88c4306..0e9ff60 100644 --- a/src/excellon/mod.rs +++ b/src/excellon/mod.rs @@ -1,4 +1,4 @@ -mod doc; +pub mod doc; pub mod drills; mod errors; @@ -271,8 +271,6 @@ mod tests { 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); + let _excellon = parse_excellon(BufReader::new(excellon.as_bytes())); } } diff --git a/src/export/dxf.rs b/src/export/dxf.rs index b5987f3..c5d8ce6 100644 --- a/src/export/dxf.rs +++ b/src/export/dxf.rs @@ -8,9 +8,9 @@ use crate::geometry::Geometry; pub struct DXFConverter; impl DXFConverter { - pub fn export(geometry: &Geometry, file: &str) { + 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()))); + 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/svg.rs b/src/export/svg.rs index 4048020..29b0989 100644 --- a/src/export/svg.rs +++ b/src/export/svg.rs @@ -43,8 +43,8 @@ impl SVGConverter { 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("cx", p.x()) + .set("cy", p.invert_y().y()) .set("fill", "black") }); diff --git a/src/geometry/elements/circle.rs b/src/geometry/elements/circle.rs index bd4b99e..e9c737b 100644 --- a/src/geometry/elements/circle.rs +++ b/src/geometry/elements/circle.rs @@ -1,20 +1,21 @@ use std::f64::consts::{PI, TAU}; -use eframe::{ - egui::{remap, Stroke, Ui}, - epaint::CircleShape, -}; +use eframe::egui::{remap, Stroke}; use egui_plot::{PlotPoints, PlotUi, Polygon}; +use gerber_types::CirclePrimitive; use crate::{ application::CanvasColour, geometry::{ClipperPath, ClipperPaths}, }; -use super::super::{ - helpers::{create_circular_path, CircleSegment}, - point::{convert_to_unit, Point}, - DrawableRaw, Unit, +use super::{ + super::{ + helpers::{create_circular_path, CircleSegment}, + point::{convert_to_unit, Point}, + DrawableRaw, Unit, + }, + macro_decimal_to_f64, }; #[derive(Debug, Clone)] @@ -29,7 +30,7 @@ impl Circle { pub fn new(position: impl Into, diameter: f64, hole_diameter: Option) -> Self { let position = position.into(); Self { - position: position, + position, diameter, hole_diameter, outline: create_circular_path(&position, diameter, CircleSegment::Full).into(), @@ -39,8 +40,19 @@ impl Circle { 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); + pub fn from_macro(ap_macro: &CirclePrimitive, variables: &[f64], target: Point) -> Self { + Self::new( + Point::new( + macro_decimal_to_f64(&ap_macro.center.0, variables) + target.x(), + macro_decimal_to_f64(&ap_macro.center.1, variables) + target.y(), + ), + macro_decimal_to_f64(&ap_macro.diameter, variables), + None, + ) + } + + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + let position = self.position.into_unit(origin, to); let diameter = convert_to_unit(self.diameter, origin, to); Self { position, @@ -51,17 +63,6 @@ impl Circle { } 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, @@ -71,7 +72,7 @@ impl Circle { ui.polygon( Polygon::new(circle_points) - .fill_color(colour.to_colour32(selected)) + .fill_color(colour.as_colour32(selected)) .stroke(Stroke::NONE), ); } @@ -84,16 +85,15 @@ impl Circle { ) -> 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) + + (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] + [r * t.cos() + position.x(), r * t.sin() + position.y()] }) - .collect(); - - circle_points + .collect() } } @@ -101,31 +101,7 @@ 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); } diff --git a/src/geometry/elements/composite.rs b/src/geometry/elements/composite.rs new file mode 100644 index 0000000..e1e16c3 --- /dev/null +++ b/src/geometry/elements/composite.rs @@ -0,0 +1,117 @@ +use std::collections::HashMap; + +use clipper2::PointInPolygonResult; +use itertools::Itertools; + +use crate::{ + application::CanvasColour, + geometry::{point::Point, union::union_function, ClipperPath, ClipperPaths, DrawableRaw, Unit}, +}; + +use super::Element; + +#[derive(Debug, Clone)] +pub struct Composite { + elements: Vec, + outline: ClipperPaths, +} + +impl Composite { + pub fn new() -> Self { + Self { + elements: Vec::new(), + outline: ClipperPaths::new(vec![]), + } + } + + pub fn add(&mut self, element: Element) { + self.elements.push(element); + } + + pub fn finalize(&mut self) { + let mut intersection_map: HashMap> = HashMap::new(); + + for part in self.elements.iter().enumerate().combinations(2) { + let (index1, element1) = part[0]; + let (index2, element2) = part[1]; + + // check if element1 and element2 intersect + if element1 + .outline() + .iter() + .any(|p| element2.outline().is_point_inside(*p) != PointInPolygonResult::IsOutside) + { + // add intersection result for both indices + let entry1 = intersection_map.entry(index1).or_default(); + entry1.push(index2); + let entry2 = intersection_map.entry(index2).or_default(); + entry2.push(index1); + } + } + + // go through all aperture components + for i in 0..intersection_map.len() { + // remove component from map + if let Some(mut intersections) = intersection_map.remove(&i) { + // take outline of element as base + let mut geo = self.elements[i].to_paths(); + + // union with intersecting aperture components until done + while let Some(other) = intersections.pop() { + // union with intersecting aperture component + let intersecting_element = &self.elements[other]; + if let Some(union) = union_function(&geo, &intersecting_element.to_paths()) { + geo = union; + } + + // get intersections of united aperture component 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); + } + } + } + } + self.outline.push(geo); + } + } + } + + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + Self { + elements: self + .elements + .into_iter() + .map(|el| el.into_unit(origin, to)) + .collect(), + outline: self.outline, + } + } +} + +impl DrawableRaw for Composite { + fn canvas_pos(&self) -> Point { + // self.position + todo!() + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + for element in &self.elements { + element.draw_egui_plot(ui, colour, selected); + } + } + + fn to_paths(&self) -> ClipperPaths { + self.outline.clone() + } + + fn outline(&self) -> ClipperPath { + self.outline.first().unwrap().clone() + } +} diff --git a/src/geometry/elements/linepath.rs b/src/geometry/elements/linepath.rs index 36e8fac..6fc5369 100644 --- a/src/geometry/elements/linepath.rs +++ b/src/geometry/elements/linepath.rs @@ -1,14 +1,12 @@ use std::f64::consts::PI; -use eframe::{ - egui::{Pos2, Stroke, Ui}, - epaint::{CircleShape, PathShape, PathStroke}, -}; +use clipper2::PointInPolygonResult; +use eframe::egui::Stroke; use egui_plot::{PlotPoints, Polygon}; use crate::{ application::CanvasColour, - geometry::{helpers::semi_circle, ClipperPath, ClipperPaths}, + geometry::{helpers::semi_circle, ClipperPath, ClipperPaths, ClipperPoint}, }; use super::super::{ @@ -47,11 +45,16 @@ impl LinePath { pub fn finalize(&mut self, stroke_width: f64) { self.diameter = stroke_width; self.outline = self.create_outline(); + println!("Line with diameter: {stroke_width}"); } - pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { let mut converted = Self { - points: self.points.iter().map(|p| p.to_unit(origin, to)).collect(), + points: self + .points + .iter() + .map(|p| p.into_unit(origin, to)) + .collect(), diameter: convert_to_unit(self.diameter, origin, to), outline: ClipperPaths::new(vec![]), }; @@ -60,6 +63,14 @@ impl LinePath { converted } + pub fn outline_includes_points(&self, points: &[Point]) -> bool { + self.outline.iter().any(|path| { + points.iter().any(|point| { + path.is_point_inside(ClipperPoint::from(point)) != PointInPolygonResult::IsOutside + }) + }) + } + fn create_outline(&self) -> ClipperPaths { let mut paths: Vec = Vec::new(); @@ -91,33 +102,12 @@ impl DrawableRaw for &LinePath { points.push(points[0]); let poly = Polygon::new(PlotPoints::from(points)) - .fill_color(colour.to_colour32(selected)) + .fill_color(colour.as_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() } @@ -132,7 +122,7 @@ fn create_outline_between_points(point1: Point, point2: Point, width: f64) -> Ve let normalized = line_vec.normalize(); let angle = - (normalized.x).acos() * (180. / PI) * if normalized.y < 0. { -1. } else { 1. } + 90.; + (normalized.x()).acos() * (180. / PI) * if normalized.y() < 0. { -1. } else { 1. } + 90.; let mut outline: Vec<(f64, f64)> = Vec::new(); diff --git a/src/geometry/elements/mod.rs b/src/geometry/elements/mod.rs index 858228d..07a71a1 100644 --- a/src/geometry/elements/mod.rs +++ b/src/geometry/elements/mod.rs @@ -1,79 +1,126 @@ use circle::Circle; -use eframe::egui::Ui; +use composite::Composite; use egui_plot::PlotUi; +use evalexpr::eval_number; +use gerber_types::MacroDecimal; +use lazy_regex::regex; use linepath::LinePath; use obround::Obround; +use polygon::Polygon; use rectangle::Rectangle; +use tracing::error; use crate::application::CanvasColour; use super::{point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit}; pub mod circle; +pub mod composite; pub mod linepath; pub mod obround; +pub mod polygon; pub mod rectangle; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Element { Circle(Circle), + Composite(Composite), Rectangle(Rectangle), - Line(LinePath), + _Line(LinePath), Obround(Obround), + Polygon(Polygon), } 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::Composite(co) => co.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::_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), + Element::Polygon(p) => p.draw_egui_plot(ui, colour, selected), } } pub fn canvas_pos(&self) -> Point { match self { Element::Circle(c) => c.canvas_pos(), + Element::Composite(c) => c.canvas_pos(), Element::Rectangle(r) => r.canvas_pos(), - Element::Line(l) => l.canvas_pos(), + Element::_Line(l) => l.canvas_pos(), Element::Obround(o) => o.canvas_pos(), + Element::Polygon(p) => p.canvas_pos(), } } pub fn to_paths(&self) -> ClipperPaths { match self { Element::Circle(c) => c.to_paths(), + Element::Composite(c) => c.to_paths(), Element::Rectangle(r) => r.to_paths(), - Element::Line(l) => l.to_paths(), + Element::_Line(l) => l.to_paths(), Element::Obround(o) => o.to_paths(), + Element::Polygon(p) => p.to_paths(), } } pub fn outline(&self) -> ClipperPath { match self { Element::Circle(c) => c.outline(), + Element::Composite(c) => c.outline(), Element::Rectangle(r) => r.outline(), - Element::Line(l) => l.outline(), + Element::_Line(l) => l.outline(), Element::Obround(o) => o.outline(), + Element::Polygon(p) => p.outline(), } } - pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + pub fn into_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)), + Element::Circle(c) => Element::Circle(c.into_unit(origin, to)), + Element::Composite(c) => Element::Composite(c.into_unit(origin, to)), + Element::Rectangle(r) => Element::Rectangle(r.into_unit(origin, to)), + Element::_Line(l) => Element::_Line(l.into_unit(origin, to)), + Element::Obround(o) => Element::Obround(o.into_unit(origin, to)), + Element::Polygon(p) => Element::Polygon(p.into_unit(origin, to)), + } + } +} + +pub fn macro_decimal_to_f64(macro_decimal: &MacroDecimal, variables: &[f64]) -> f64 { + let re_units = regex!(r#"(\$\d+)"#); + + match macro_decimal { + MacroDecimal::Value(v) => *v, + MacroDecimal::Variable(v) => *variables.get(*v as usize).unwrap_or(&0.), + MacroDecimal::Expression(ex) => { + // search for variables (eg. $1) + if let Some(captures) = re_units.captures(ex) { + // copy expression + let mut expr = ex.clone(); + // go through all found variable names + for c in captures.iter().skip(1).flatten() { + // get variable name + let matched = c.as_str(); + // create index out of variable + let index = matched.replace('$', "").parse::().unwrap(); + // replace variable name with value + expr = expr.replace( + matched, + &variables.get(index - 1).unwrap_or(&0.).to_string(), + ); + } + match eval_number(&expr) { + Ok(res) => res, + Err(e) => { + error!("{e:?}"); + 0. + } + } + } else { + 0. + } } } } diff --git a/src/geometry/elements/obround.rs b/src/geometry/elements/obround.rs index 8a75b2a..5fd917a 100644 --- a/src/geometry/elements/obround.rs +++ b/src/geometry/elements/obround.rs @@ -1,7 +1,4 @@ -use eframe::{ - egui::{Rect, Rounding, Stroke, Ui, Vec2}, - epaint::RectShape, -}; +use eframe::egui::Stroke; use egui_plot::{PlotPoints, Polygon}; use crate::{ @@ -9,24 +6,21 @@ use crate::{ 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, +use super::super::{ + helpers::{create_circular_path, semi_circle, CircleSegment}, + point::{convert_to_unit, Point}, + DrawableRaw, Unit, }; +use super::rectangle::Rectangle; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Obround { position: Point, x: f64, y: f64, - rounding: f64, + _rounding: f64, outline: ClipperPath, - rectangle: Rectangle, + _rectangle: Rectangle, hole_diameter: Option, } @@ -40,12 +34,27 @@ impl Obround { // 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.)); + path.append(&mut semi_circle( + position - Point::new(0., y / 2. - x / 2.), + x, + 180., + )); + path.append(&mut semi_circle( + position + Point::new(0., y / 2. - x / 2.), + 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.append(&mut semi_circle( + position + Point::new(x / 2. - y / 2., 0.), + y, + 270., + )); + path.append(&mut semi_circle( + position - Point::new(x / 2. - y / 2., 0.), + y, + 90., + )); } path @@ -53,12 +62,12 @@ impl Obround { Self { position, - x: x, - y: y, - rounding: diameter, + x, + y, + _rounding: diameter, outline: outline.into(), - rectangle: Rectangle::new(position, x, y, hole_diameter), - hole_diameter: hole_diameter, + _rectangle: Rectangle::new(position, x, y, hole_diameter), + hole_diameter, } } @@ -66,9 +75,9 @@ impl Obround { Self::new(position, aperture.x, aperture.y, aperture.hole_diameter) } - pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { Self::new( - self.position.to_unit(origin, to), + self.position.into_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)), @@ -86,22 +95,11 @@ impl DrawableRaw for Obround { points.push(points[0]); let poly = Polygon::new(PlotPoints::from(points)) - .fill_color(colour.to_colour32(selected)) + .fill_color(colour.as_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()]) } diff --git a/src/geometry/elements/polygon.rs b/src/geometry/elements/polygon.rs new file mode 100644 index 0000000..e3dc3da --- /dev/null +++ b/src/geometry/elements/polygon.rs @@ -0,0 +1,72 @@ +use eframe::egui::Stroke; +use gerber_types::OutlinePrimitive; + +use crate::{ + application::CanvasColour, + geometry::{ + elements::macro_decimal_to_f64, point::Point, ClipperPath, ClipperPaths, DrawableRaw, Unit, + }, +}; + +#[derive(Debug, Clone)] +pub struct Polygon { + points: Vec, +} + +impl Polygon { + pub fn new(points: &[Point]) -> Self { + Self { + points: points.to_vec(), + } + } + + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + Self { + points: self + .points + .iter() + .map(|p| p.into_unit(origin, to)) + .collect(), + } + } + + pub fn from_macro(ap_macro: &OutlinePrimitive, variables: &[f64], target: Point) -> Self { + Self { + points: ap_macro + .points + .iter() + .map(|(x, y)| { + Point::new( + macro_decimal_to_f64(x, variables), + macro_decimal_to_f64(y, variables), + ) + target + }) + .collect(), + } + } +} + +impl DrawableRaw for Polygon { + fn canvas_pos(&self) -> Point { + // self.position + todo!() + } + + fn draw_egui_plot(&self, ui: &mut egui_plot::PlotUi, colour: CanvasColour, selected: bool) { + let points: Vec<[f64; 2]> = self.points.iter().map(|p| [p.x(), p.y()]).collect(); + + ui.polygon( + egui_plot::Polygon::new(points) + .fill_color(colour.as_colour32(selected)) + .stroke(Stroke::NONE), + ); + } + + fn to_paths(&self) -> ClipperPaths { + ClipperPaths::new(vec![self.outline()]) + } + + fn outline(&self) -> ClipperPath { + ClipperPath::new(self.points.iter().map(|p| p.into()).collect()) + } +} diff --git a/src/geometry/elements/rectangle.rs b/src/geometry/elements/rectangle.rs index 9555947..f70851e 100644 --- a/src/geometry/elements/rectangle.rs +++ b/src/geometry/elements/rectangle.rs @@ -1,20 +1,21 @@ -use eframe::{ - egui::{Rect, Rounding, Stroke, Ui, Vec2}, - epaint::RectShape, -}; +use eframe::egui::Stroke; use egui_plot::{PlotPoints, Polygon}; +use gerber_types::{CenterLinePrimitive, VectorLinePrimitive}; use crate::{ application::CanvasColour, geometry::{ClipperPath, ClipperPaths}, }; -use super::super::{ - point::{convert_to_unit, Point}, - DrawableRaw, Unit, +use super::{ + super::{ + point::{convert_to_unit, Point}, + DrawableRaw, Unit, + }, + macro_decimal_to_f64, }; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Rectangle { position: Point, pub width: f64, @@ -34,11 +35,26 @@ impl Rectangle { 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.), + ( + 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(), } @@ -51,18 +67,66 @@ impl Rectangle { 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.), + (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 { + pub fn from_macro_vector_line( + ap_macro: &VectorLinePrimitive, + variables: &[f64], + target: Point, + ) -> Self { + // TODO angle + let x_start = macro_decimal_to_f64(&ap_macro.start.0, variables); + let x_end = macro_decimal_to_f64(&ap_macro.end.0, variables); + let y_start = macro_decimal_to_f64(&ap_macro.start.1, variables); + let y_end = macro_decimal_to_f64(&ap_macro.end.1, variables); + + let height = if x_end - x_start == 0. { + macro_decimal_to_f64(&ap_macro.width, variables) + } else { + x_end - x_start + }; + let width = if y_end - y_start == 0. { + macro_decimal_to_f64(&ap_macro.width, variables) + } else { + y_end - y_start + }; + Self::new( - self.position.to_unit(origin, to), + Point::new(x_start, y_start) + + (Point::new(x_end, y_end) - Point::new(x_start, y_start)).div(2.) + + target, + height, + width, + None, + ) + } + pub fn from_macro_center_line( + ap_macro: &CenterLinePrimitive, + variables: &[f64], + target: Point, + ) -> Self { + // TODO angle + Self::new( + Point::new( + macro_decimal_to_f64(&ap_macro.center.0, variables), + macro_decimal_to_f64(&ap_macro.center.1, variables), + ) + target, + macro_decimal_to_f64(&ap_macro.dimensions.0, variables), + macro_decimal_to_f64(&ap_macro.dimensions.1, variables), + None, + ) + } + + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + Self::new( + self.position.into_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)), @@ -73,8 +137,8 @@ impl Rectangle { impl DrawableRaw for Rectangle { fn canvas_pos(&self) -> Point { self.position - .shift_x(self.position.x / 2.) - .shift_y(self.position.y / 2.) + .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) { @@ -82,22 +146,11 @@ impl DrawableRaw for Rectangle { points.push(points[0]); let poly = Polygon::new(PlotPoints::from(points)) - .fill_color(colour.to_colour32(selected)) + .fill_color(colour.as_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()]) } diff --git a/src/geometry/gerber.rs b/src/geometry/gerber.rs index a5b815d..7a78620 100644 --- a/src/geometry/gerber.rs +++ b/src/geometry/gerber.rs @@ -1,9 +1,10 @@ -use clipper2::Paths; +use std::collections::HashMap; + use gerber_types::{ - Aperture, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode, MCode, - Operation, Unit, + Aperture, ApertureMacro, Command, Coordinates, DCode, FunctionCode, GCode, InterpolationMode, + MCode, MacroContent, Operation, Unit, }; -use tracing::{debug, error, info}; +use tracing::{error, info}; use crate::{ geometry::{ @@ -16,6 +17,7 @@ use crate::{ }; use super::{ + elements::{composite::Composite, polygon::Polygon}, union::{union_lines, union_with_apertures}, Geometry, }; @@ -32,7 +34,6 @@ impl From for Geometry { // create geometries by applying all gerber commands for command in gerber.commands { - println!("{command:?}"); match command { Command::FunctionCode(f) => { match f { @@ -83,6 +84,7 @@ impl From for Geometry { Operation::Flash(f) => { Self::add_geometry( &mut added_apertures, + &gerber.aperture_macros, ¤t_position, &f, &selected_aperture, @@ -93,6 +95,19 @@ impl From for Geometry { }, // select an aperture DCode::SelectAperture(ap) => { + // check if a aperture is selected and if it's circular + if let Some(Aperture::Circle(c)) = selected_aperture.as_ref() { + // check if a path is currently active + if !active_path.is_empty() { + // finish active path if there is an active one before selecting new aperture + active_path.finalize(c.diameter); + path_container.push(active_path); + + // create new active path + active_path = LinePath::new(); + } + } + selected_aperture = Some( gerber .apertures @@ -127,53 +142,13 @@ impl From for Geometry { } } - // create empty path set - 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![]); - // union all drawn lines into nets of conductors let conductor_net = union_lines(&path_container); + // union conductors with apertures + let united_nets = union_with_apertures(&added_apertures, conductor_net).unwrap(); - // 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, + united_nets, apertures: added_apertures, paths: path_container, units: gerber.units.unwrap_or(Unit::Millimeters).into(), @@ -182,7 +157,7 @@ impl From for Geometry { } impl Geometry { - fn move_position(coord: &Coordinates, position: &mut Point) -> () { + fn move_position(coord: &Coordinates, position: &mut Point) { if let Ok(pos) = Point::try_from(coord) { *position = pos; }; @@ -190,6 +165,7 @@ impl Geometry { fn add_geometry( geometries: &mut Vec, + aperture_macros: &HashMap, position: &Point, coordinates: &Coordinates, aperture: &Option, @@ -216,7 +192,53 @@ impl Geometry { error!("Unsupported Polygon aperture:\r\n{:#?}", p); } Aperture::Other(o) => { - error!("Unsupported Other aperture:\r\n{:#?}", o); + // split at '/' -> name/arguments + if let Some((name, args)) = o.split_once("/") { + // parse variables from args + let variables: Vec = args + .split(",") + .map(|s| s.parse::().unwrap_or(0.)) + .collect(); + + if let Some(ap_macro) = aperture_macros.get(name) { + let mut composite = Composite::new(); + // add elements from macro + for aperture in &ap_macro.content { + match aperture { + MacroContent::Circle(c) => composite.add(Element::Circle( + Circle::from_macro(c, &variables, target), + )), + MacroContent::VectorLine(vl) => composite.add(Element::Rectangle( + Rectangle::from_macro_vector_line(vl, &variables, target), + )), + MacroContent::CenterLine(cl) => composite.add(Element::Rectangle( + Rectangle::from_macro_center_line(cl, &variables, target), + )), + MacroContent::Outline(ol) => composite.add(Element::Polygon( + Polygon::from_macro(ol, &variables, target), + )), + MacroContent::Polygon(p) => { + error!("Polygon Not implemented yet") + } + MacroContent::Moire(m) => { + error!("Moire Not implemented yet") + } + MacroContent::Thermal(t) => { + error!("Thermal Not implemented yet") + } + MacroContent::VariableDefinition(_) => {} + MacroContent::Comment(_) => {} + }; + } + + composite.finalize(); + geometries.push(Element::Composite(composite)); + } else { + error!("Unsupported Other aperture:\r\n{:#?}", o); + } + } else { + error!("Unsupported Other aperture:\r\n{:#?}", o); + } } } } diff --git a/src/geometry/helpers.rs b/src/geometry/helpers.rs index 64f5746..6b0b04b 100644 --- a/src/geometry/helpers.rs +++ b/src/geometry/helpers.rs @@ -10,13 +10,14 @@ pub fn semi_circle(center: Point, diameter: f64, tilt: f64) -> Vec<(f64, f64)> { .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, + angle.cos() * diameter / 2. + center.x(), + angle.sin() * diameter / 2. + center.y(), ) }) .collect() } +#[allow(unused)] pub enum CircleSegment { North, East, @@ -49,106 +50,106 @@ pub fn create_circular_path( .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, + angle.sin() * diameter / 2. + position.x(), + angle.cos() * diameter / 2. + position.y(), ) }) .collect() } -#[derive(Debug, PartialEq)] -pub enum Orientation { - Clockwise, - CounterClockwise, - CoLinear, -} +// #[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()); +// // 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); +// // 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, - } -} +// 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()); +// // 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) -} +// 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 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 - } -} +// 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()); +// // 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); +// // 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; - } +// // 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; - } +// // 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; - }; +// // 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 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; - }; +// // 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 -} +// false // Doesn't fall in any of the above cases +// } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index 974dd88..1550bf2 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -2,47 +2,39 @@ pub mod elements; pub mod gerber; mod helpers; pub mod point; -mod union; +pub 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 union::UnitedNets; use crate::application::CanvasColour; pub struct Geometry { - pub outline_union: ClipperPaths, + pub united_nets: UnitedNets, pub apertures: Vec, pub paths: Vec, pub units: Unit, } impl Geometry { - pub fn to_unit(self, to: Unit) -> Self { + pub fn into_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(), + united_nets: self.united_nets.into_unit(self.units, to), apertures: self .apertures - .iter() - .map(|a| a.to_unit(self.units, to)) + .into_iter() + .map(|a| a.into_unit(self.units, to)) .collect(), paths: self .paths - .iter() - .map(|l| l.to_unit(self.units, to)) + .into_iter() + .map(|l| l.into_unit(self.units, to)) .collect(), units: to, } @@ -98,12 +90,7 @@ 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 index 3305ff4..4ec9ff2 100644 --- a/src/geometry/point.rs +++ b/src/geometry/point.rs @@ -1,80 +1,115 @@ -use std::ops::{Add, Sub}; +use std::{ + fmt, + marker::PhantomData, + ops::{Add, Sub}, +}; +use clipper2::PointScaler; use eframe::egui::{Pos2, Vec2}; use egui_plot::PlotPoint; use gerber_types::Coordinates; -use super::{ClipperPoint, Unit}; +use super::{ClipperPoint, Micro, Unit}; -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Point { - pub x: f64, - pub y: f64, +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct Point { + x: i64, + y: i64, + phantom_data: PhantomData

, } -impl Point { +impl fmt::Debug for Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Point") + .field("x", &self.x()) + .field("y", &self.y()) + .finish() + } +} + +impl Point

{ pub fn new(x: f64, y: f64) -> Self { - Point { x, y } + // Point { x, y } + Self { + x: P::scale(x) as i64, + y: P::scale(y) as i64, + phantom_data: PhantomData, + } } pub fn shift_x(&self, shift: f64) -> Self { Self { - x: self.x + shift, + x: self.x + P::scale(shift) as i64, y: self.y, + phantom_data: PhantomData, } } pub fn shift_y(&self, shift: f64) -> Self { Self { x: self.x, - y: self.y + shift, + y: self.y + P::scale(shift) as i64, + phantom_data: PhantomData, + } + } + + pub fn div(&self, divisor: f64) -> Self { + Self { + x: (self.x as f64 / divisor) as i64, + y: (self.y as f64 / divisor) as i64, + phantom_data: PhantomData, } } pub fn len(&self) -> f64 { - (self.x.powi(2) + self.y.powi(2)).sqrt() + P::descale(((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()) } pub fn normalize(&self) -> Self { - Self { - x: self.x / self.len(), - y: self.y / self.len(), - } + Self::new(self.x() / self.len(), self.y() / self.len()) } pub fn invert_y(&self) -> Self { Self { x: self.x, y: -self.y, + phantom_data: PhantomData, } } - 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), - } + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + Self::new( + convert_to_unit(self.x(), origin, to), + convert_to_unit(self.y(), origin, to), + ) + } + + /// Returns the x coordinate of the point. + pub fn x(&self) -> f64 { + P::descale(self.x as f64) + } + + /// Returns the y coordinate of the point. + pub fn y(&self) -> f64 { + P::descale(self.y as f64) } } impl From for (f64, f64) { fn from(value: Point) -> Self { - (value.x, value.y) + (value.x(), value.y()) } } impl From for [f64; 2] { fn from(value: Point) -> Self { - [value.x, value.y] + [value.x(), value.y()] } } impl From<[f64; 2]> for Point { fn from(value: [f64; 2]) -> Self { - Point { - x: value[0], - y: value[1], - } + Self::new(value[0], value[1]) } } @@ -85,6 +120,7 @@ impl Add for Point { Self { x: self.x + other.x, y: self.y + other.y, + phantom_data: PhantomData, } } } @@ -96,6 +132,7 @@ impl Sub for Point { Self { x: self.x - other.x, y: self.y - other.y, + phantom_data: PhantomData, } } } @@ -103,8 +140,8 @@ impl Sub for Point { impl From for Pos2 { fn from(value: Point) -> Self { Self { - x: value.x as f32, - y: value.y as f32, + x: value.x() as f32, + y: value.y() as f32, } } } @@ -112,33 +149,30 @@ impl From for Pos2 { impl From<&Point> for Pos2 { fn from(value: &Point) -> Self { Self { - x: value.x as f32, - y: value.y as f32, + 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(), - } + Self::new(value.x.into(), value.y.into()) } } impl From for Vec2 { fn from(value: Point) -> Self { Self { - x: value.x as f32, - y: value.y as f32, + 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) + Self::from_scaled(value.x, value.y) } } @@ -150,7 +184,7 @@ impl From<&ClipperPoint> for Point { impl From for PlotPoint { fn from(value: Point) -> Self { - Self::new(value.x, value.y) + Self::new(value.x(), value.y()) } } diff --git a/src/geometry/union.rs b/src/geometry/union.rs index e1feb88..0f30c3d 100644 --- a/src/geometry/union.rs +++ b/src/geometry/union.rs @@ -1,30 +1,110 @@ use std::collections::HashMap; -use clipper2::{FillRule, One, Paths, PointInPolygonResult}; - -use crate::geometry::helpers::do_intersect; +use clipper2::{FillRule, PointInPolygonResult}; +use itertools::Itertools; use super::{ elements::{linepath::LinePath, Element}, point::Point, - ClipperPaths, + ClipperBounds, ClipperPaths, ClipperPoint, Unit, }; +#[derive(Debug, Clone, Default)] +pub struct UnitedNets { + pub conductors: Vec, + pub isolated_apertures: Vec, +} + +impl UnitedNets { + pub fn into_unit(self, origin: Unit, to: Unit) -> Self { + Self { + conductors: self + .conductors + .iter() + .map(|net| net.to_unit(origin, to)) + .collect(), + isolated_apertures: self + .isolated_apertures + .into_iter() + .map(|el| el.into_unit(origin, to)) + .collect(), + } + } + + pub fn bounds(&self) -> ClipperBounds { + let conductor_net = self + .conductors + .iter() + .map(|net| net.outline.clone()) + .reduce(|mut acc, v| { + acc.push(v); + acc + }); + let isolated_apertures_bound = ClipperPaths::new( + self.isolated_apertures + .iter() + .map(|net| net.outline()) + .collect(), + ) + .bounds(); + + if let Some(net) = conductor_net { + let net_bounds = net.bounds(); + + ClipperBounds { + min: ClipperPoint::new( + net_bounds.min.x().min(isolated_apertures_bound.min.x()), + net_bounds.min.y().min(isolated_apertures_bound.min.y()), + ), + max: ClipperPoint::new( + net_bounds.max.x().max(isolated_apertures_bound.max.x()), + net_bounds.max.y().max(isolated_apertures_bound.max.y()), + ), + } + } else { + isolated_apertures_bound + } + } +} + #[derive(Debug, Clone)] pub struct ConductorNet { pub outline: ClipperPaths, pub included_points: Vec, } +impl ConductorNet { + pub fn to_unit(&self, origin: Unit, to: Unit) -> Self { + Self { + outline: self + .outline + .iter() + .map(|p| { + p.iter() + .map(|p| (&Point::from(p).into_unit(origin, to)).into()) + .collect() + }) + .collect(), + included_points: self + .included_points + .iter() + .map(|el| el.into_unit(origin, to)) + .collect(), + } + } +} + 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::>>() - ); + + // TODO prevent double checking same combination + let combinations = lines.iter().enumerate().combinations(2); + + for co in combinations { + let (index, line) = co[0]; + // print!("{} mit {};", combi[0].0, combi[1].0); + } + for (index, line) in lines.iter().enumerate() { if !line.is_empty() { // create list of intersecting lines @@ -36,16 +116,28 @@ pub fn union_lines(lines: &[LinePath]) -> Vec { // 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) - }); + // 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) + // line.outline + // .get(0) + // .unwrap() + // .is_point_inside(ClipperPoint::from(&p2)) + // != PointInPolygonResult::IsOutside + // || line + // .outline + // .get(0) + // .unwrap() + // .is_point_inside(ClipperPoint::from(&q2)) + // != PointInPolygonResult::IsOutside + // }); + let intersect = line.outline_includes_points(&l.points); // println!("INTERSECTING: {intersect}"); if intersect { // let entry = intersection_map.entry(index).or_insert(Vec::new()); // entry.push(i); - println!("INTERSECTING {:?} and {:?}", line.points, l.points); + // println!("INTERSECTING {:?} and {:?}", line.points, l.points); intersections.push(i); } } @@ -56,34 +148,20 @@ pub fn union_lines(lines: &[LinePath]) -> Vec { } } - 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) { + if let Some(union) = union_function(&geo, &intersecting_line.outline) { geo = union; } // add points of added line to included points @@ -115,23 +193,16 @@ pub fn union_lines(lines: &[LinePath]) -> Vec { } } - // 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 +) -> Option { + let mut isolated_apertures = Vec::new(); + + // let mut finalized_paths = Vec::new(); // handle apertures without connection let mut current_conductors = conductors; // go through all apertures @@ -152,7 +223,7 @@ pub fn union_with_apertures( .any(|c| ap.outline().is_point_inside(c.into()) != PointInPolygonResult::IsOutside) { // union aperture with conductor net - let geo = union(&geo, &conductor.outline)?; + let geo = union_function(&geo, &conductor.outline)?; let mut cond = conductor; cond.outline = geo; isolated = false; @@ -164,24 +235,21 @@ pub fn union_with_apertures( // add aperture to extra container if isolated if isolated { - finalized_paths.push(geo); + // finalized_paths.push(geo); + isolated_apertures.push(ap.clone()); } // 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 + Some(UnitedNets { + conductors: current_conductors, + isolated_apertures, }) } -fn union(path1: &ClipperPaths, path2: &ClipperPaths) -> Option { +pub fn union_function(path1: &ClipperPaths, path2: &ClipperPaths) -> Option { path1 .to_clipper_subject() .add_clip(path2.clone()) diff --git a/src/gerber/aperture_macros.rs b/src/gerber/aperture_macros.rs index e0e4515..c63591e 100644 --- a/src/gerber/aperture_macros.rs +++ b/src/gerber/aperture_macros.rs @@ -1,7 +1,170 @@ -use gerber_types::MacroContent; +use std::str::Chars; -pub fn parse(data: &str) -> Option { - todo!() +use gerber_types::{ + ApertureMacro, CenterLinePrimitive, CirclePrimitive, MacroContent, MacroDecimal, + OutlinePrimitive, PolygonPrimitive, VectorLinePrimitive, +}; + +use super::doc::GerberDoc; + +pub fn start_macro(doc: &mut GerberDoc, macro_lines: &mut Option>, line: &str) { + *macro_lines = Some(vec![line.replacen("%AM", "", 1)]); + if line.ends_with("*%") { + if let Some(aperture_macro) = parse(macro_lines.as_ref()) { + doc.aperture_macros + .insert(aperture_macro.name.to_string(), aperture_macro); + }; + *macro_lines = None; + } +} + +pub fn continue_macro(doc: &mut GerberDoc, macro_lines: &mut Option>, line: &str) { + if let Some(lines) = macro_lines { + lines.push(line.to_string()); + if line.ends_with("*%") { + if let Some(aperture_macro) = parse(macro_lines.as_ref()) { + doc.aperture_macros + .insert(aperture_macro.name.to_string(), aperture_macro); + }; + *macro_lines = None; + } + } else { + start_macro(doc, macro_lines, line); + } +} + +pub fn parse(data: Option<&Vec>) -> Option { + let mut name = ""; + let mut macro_content = Vec::new(); + let lines = data?; + + // Go through every very part in the macro + for (index, line) in lines.iter().enumerate() { + // get name + if index == 0 { + name = line.strip_suffix("*")?; + continue; + } + + let mut chars = line.chars(); + // remove % char of last line + if index == lines.len() - 1 { + chars.next_back(); + } + + match chars.next()? { + // Comment + '0' => macro_content.push(MacroContent::Comment(line.to_string())), + // Circle + '1' => { + let args = get_attr_args(chars)?; + + macro_content.push(MacroContent::Circle(CirclePrimitive { + exposure: args.first()? == &MacroDecimal::Value(1.0), + diameter: args.get(1)?.clone(), + center: (args.get(2)?.clone(), args.get(3)?.clone()), + angle: match args.get(4) { + Some(arg) => Some(arg.clone()), + None => Some(MacroDecimal::Value(0.)), + }, + })); + } + '2' => { + match chars.next()? { + // Vector Line + '0' => { + let args = get_attr_args(chars)?; + + macro_content.push(MacroContent::VectorLine(VectorLinePrimitive { + exposure: args.first()? == &MacroDecimal::Value(1.0), + end: (args.get(4)?.clone(), args.get(5)?.clone()), + width: args.get(1)?.clone(), + start: (args.get(2)?.clone(), args.get(3)?.clone()), + angle: match args.get(6) { + Some(arg) => arg.clone(), + None => MacroDecimal::Value(0.), + }, + })); + } + // Center Line + '1' => { + let args = get_attr_args(chars)?; + + macro_content.push(MacroContent::CenterLine(CenterLinePrimitive { + exposure: args.first()? == &MacroDecimal::Value(1.0), + dimensions: (args.get(1)?.clone(), args.get(2)?.clone()), + center: (args.get(3)?.clone(), args.get(4)?.clone()), + angle: match args.get(6) { + Some(arg) => arg.clone(), + None => MacroDecimal::Value(0.), + }, + })); + } + _ => panic!("Unknown command in macro line:\n{} | {}", index, line), + } + } + // Outline + '4' => { + let args = get_attr_args(chars)?; + + macro_content.push(MacroContent::Outline(OutlinePrimitive { + exposure: args.first()? == &MacroDecimal::Value(1.0), + points: (args) + .chunks_exact(2) + .map(|c| (c[0].clone(), c[1].clone())) + .collect(), + angle: args.last()?.clone(), + })); + } + // Polygon + '5' => { + let args = get_attr_args(chars)?; + + macro_content.push(MacroContent::Polygon(PolygonPrimitive { + exposure: args.first()? == &MacroDecimal::Value(1.0), + vertices: match args.get(1)? { + MacroDecimal::Value(v) => *v as u8, + _ => 0, + }, + center: (args.get(2)?.clone(), args.get(3)?.clone()), + diameter: args.get(4)?.clone(), + angle: match args.get(5) { + Some(arg) => arg.clone(), + None => MacroDecimal::Value(0.), + }, + })); + } + '6' => todo!(), // Moiré + '7' => todo!(), // Thermal + '$' => todo!(), // Define variable + _ => panic!("Unknown command in macro line:\n{} | {}", index, line), + } + } + + Some(ApertureMacro { + name: name.to_string(), + content: macro_content, + }) +} + +fn get_attr_args(mut attribute_chars: Chars) -> Option> { + attribute_chars.next_back()?; + attribute_chars.next()?; + + Some( + attribute_chars + .as_str() + .split(",") + .map(|el| el.trim()) + .map(|el| { + if el.starts_with("$") { + MacroDecimal::Expression(el.to_string()) + } else { + MacroDecimal::Value(el.parse::().unwrap()) + } + }) + .collect(), + ) } // def parse_content(self): diff --git a/src/gerber/doc.rs b/src/gerber/doc.rs index e43143f..1fab86c 100644 --- a/src/gerber/doc.rs +++ b/src/gerber/doc.rs @@ -1,7 +1,8 @@ use ::std::collections::HashMap; -use gerber_types::{Aperture, ApertureDefinition, Command, CoordinateFormat, ExtendedCode, Unit}; +use gerber_types::{ + Aperture, ApertureDefinition, ApertureMacro, Command, CoordinateFormat, ExtendedCode, Unit, +}; use std::fmt; -use std::iter::repeat; #[derive(Debug, PartialEq)] // Representation of Gerber document @@ -12,6 +13,8 @@ pub struct GerberDoc { pub format_specification: Option, /// map of apertures which can be used in draw commands later on in the document. pub apertures: HashMap, + /// map of aperture macro which can be used to create aperture definitions + pub aperture_macros: HashMap, // Anything else, draw commands, comments, attributes pub commands: Vec, } @@ -23,6 +26,7 @@ impl GerberDoc { units: None, format_specification: None, apertures: HashMap::new(), + aperture_macros: HashMap::new(), commands: Vec::new(), } } @@ -33,26 +37,24 @@ impl GerberDoc { /// 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 { + pub fn into_commands(self) -> Vec { + let mut gerber_doc = self; 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()); + gerber_cmds + .push(ExtendedCode::CoordinateFormat(gerber_doc.format_specification.unwrap()).into()); + gerber_cmds.push(ExtendedCode::Unit(gerber_doc.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::>(); + let mut apertures = gerber_doc.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(), + ExtendedCode::ApertureDefinition(ApertureDefinition { code, aperture }).into(), ) } - gerber_cmds.append(&mut self.commands); + gerber_cmds.append(&mut gerber_doc.commands); // TODO implement for units gerber_cmds } diff --git a/src/gerber/mod.rs b/src/gerber/mod.rs index ab40ca2..b52a613 100644 --- a/src/gerber/mod.rs +++ b/src/gerber/mod.rs @@ -1,5 +1,7 @@ +mod aperture_macros; pub mod doc; +use aperture_macros::{continue_macro, start_macro}; use doc::GerberDoc; use gerber_types::{ Aperture, ApertureAttribute, ApertureFunction, Circle, Command, CoordinateFormat, @@ -25,12 +27,14 @@ pub fn parse_gerber(reader: BufReader) -> GerberDoc { // By default the 'last coordinate' can be taken to be (0,0) let mut last_coords = (0i64, 0i64); + let mut aperture_macro: Option> = None; + // 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_aperture = regex!(r#"%ADD([0-9]+)(\w*),(.*)\*%"#); 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 @@ -46,6 +50,12 @@ pub fn parse_gerber(reader: BufReader) -> GerberDoc { debug!("{}. {}", index + 1, &line); if !line.is_empty() { + // continue aperture macro if one is active + if aperture_macro.is_some() { + continue_macro(&mut gerber_doc, &mut aperture_macro, line); + continue; + } + let mut linechars = line.chars(); match linechars.next().unwrap() { @@ -114,7 +124,8 @@ pub fn parse_gerber(reader: BufReader) -> GerberDoc { '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.") + start_macro(&mut gerber_doc, &mut aperture_macro, line); + // panic!("Aperture Macros (AM) are not supported yet.") } // AM _ => line_parse_failure(line, index), }, @@ -348,7 +359,14 @@ fn parse_aperture_defs(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { }), ), _ => { - panic!("Encountered unknown aperture definition statement") + if let Some(ap_macro) = gerber_doc.aperture_macros.get(aperture_type) { + gerber_doc.apertures.insert( + code, + Aperture::Other(format!("{}/{}", ap_macro.name, aperture_args.join(","))), + ) + } else { + panic!("Encountered unknown aperture definition statement") + } } }; @@ -511,7 +529,7 @@ fn parse_move_or_flash( } } -fn parse_load_mirroring(mut linechars: Chars, gerber_doc: &mut GerberDoc) { +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 @@ -528,7 +546,7 @@ fn parse_load_mirroring(mut linechars: Chars, gerber_doc: &mut GerberDoc) { // 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); + // println!("SR line: {}", &line); if let Some(regmatch) = re.captures(line) { gerber_doc.commands.push( ExtendedCode::StepAndRepeat(StepAndRepeat::Open { @@ -577,7 +595,7 @@ fn parse_step_repeat_open(line: &str, re: &Regex, gerber_doc: &mut GerberDoc) { /// ⚠️ 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) { +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 @@ -626,7 +644,7 @@ fn parse_file_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { /// ⚠️ 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) { +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 @@ -708,7 +726,7 @@ fn parse_aperture_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) } } -fn parse_object_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { +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( @@ -728,7 +746,7 @@ fn parse_object_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { } } -fn parse_delete_attribute(line: Chars, re: &Regex, gerber_doc: &mut GerberDoc) { +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 diff --git a/src/main.rs b/src/main.rs index d23b413..742319c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,13 +6,15 @@ mod export; mod geometry; mod gerber; mod outline_geometry; +mod resources; use application::Application; -use eframe::egui; +use eframe::egui::{self, IconData}; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer}; const APP_NAME: &str = "Outlinify"; +const ICON: &[u8] = include_bytes!("../resources/icon.png"); fn main() -> eframe::Result { let stdout_log = tracing_subscriber::fmt::layer() @@ -28,17 +30,38 @@ fn main() -> eframe::Result { let application = Application::new(); let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size([900.0, 700.0]), + viewport: egui::ViewportBuilder::default() + .with_inner_size([900.0, 700.0]) + .with_icon(load_icon()), ..Default::default() }; + eframe::run_native( APP_NAME, options, Box::new(|cc| { // This gives us image support: - // egui_extras::install_image_loaders(&cc.egui_ctx); + egui_extras::install_image_loaders(&cc.egui_ctx); Ok(Box::new(application)) }), ) } + +fn load_icon() -> IconData { + let (icon_rgba, icon_width, icon_height) = { + let icon = ICON; + let image = image::load_from_memory(icon) + .expect("Failed to open icon path") + .into_rgba8(); + let (width, height) = image.dimensions(); + let rgba = image.into_raw(); + (rgba, width, height) + }; + + IconData { + rgba: icon_rgba, + width: icon_width, + height: icon_height, + } +} diff --git a/src/outline_geometry/mod.rs b/src/outline_geometry/mod.rs index 6a0bf63..6a73285 100644 --- a/src/outline_geometry/mod.rs +++ b/src/outline_geometry/mod.rs @@ -1,13 +1,11 @@ -use clipper2::{Bounds, EndType, JoinType, One, Paths}; -use eframe::{ - egui::{Rect, Shape, Stroke}, - epaint::{PathShape, PathStroke}, -}; -use egui_plot::{PlotBounds, PlotItem, PlotPoint, Polygon}; +use clipper2::{EndType, JoinType}; use crate::{ - application::CanvasColour, - geometry::{elements::circle::Circle, point::Point, ClipperBounds, ClipperPaths, Unit}, + excellon::drills::Drills, + geometry::{ + point::Point, union::UnitedNets, ClipperBounds, ClipperPath, ClipperPaths, DrawableRaw, + Unit, + }, }; #[derive(Debug, Clone)] @@ -16,10 +14,8 @@ pub enum GeometryType { Points(Vec), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct OutlineGeometry { - // pub path: ClipperPaths, - // pub points: Vec, items: GeometryType, pub stroke: f32, pub unit: Unit, @@ -28,26 +24,31 @@ pub struct OutlineGeometry { } 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::Miter, EndType::Polygon, 30.) - .simplify(0.01, false); + pub fn new(united_nets: &UnitedNets, stroke: f32, unit: Unit, bounds_from: &str) -> Self { + let mut outline_paths = ClipperPaths::new(vec![]); + // inflate conductor net paths + for net in &united_nets.conductors { + outline_paths.push( + net.outline + .inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.) + .simplify(0.001, false), + ) + } + // inflate isolated apertures + for ap in &united_nets.isolated_apertures { + outline_paths.push( + ap.to_paths() + .inflate((stroke / 2.).into(), JoinType::Round, EndType::Polygon, 2.) + .simplify(0.001, false), + ); + } + Self { - // path: outline, - // points: Vec::new(), - items: GeometryType::Paths(outline), + items: GeometryType::Paths(outline_paths), stroke, unit, bounds_from: bounds_from.into(), - bounding_box: bounds, + bounding_box: united_nets.bounds(), } } @@ -56,34 +57,30 @@ impl OutlineGeometry { 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, + bounding_box: outline.bounds(), } } - pub fn point_marker( - points: Vec, - stroke: f32, - unit: Unit, - bounds_from: &str, - bounds: ClipperBounds, - ) -> Self { + pub fn drill_marker(drills: &Drills, stroke: f32, unit: Unit, bounds_from: &str) -> Self { Self { - // path: Paths::new(vec![]), - // points, - items: GeometryType::Points(points), + items: GeometryType::Points(drills.holes.iter().map(|c| c.canvas_pos()).collect()), stroke, unit, bounds_from: bounds_from.into(), - bounding_box: bounds, + bounding_box: ClipperPaths::from( + drills + .holes + .iter() + .map(|hole| hole.outline.clone()) + .collect::>(), + ) + .bounds(), } } @@ -101,114 +98,3 @@ impl OutlineGeometry { } } } - -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 -// } -// } diff --git a/src/resources/errors.rs b/src/resources/errors.rs new file mode 100644 index 0000000..7afec46 --- /dev/null +++ b/src/resources/errors.rs @@ -0,0 +1,20 @@ +use std::fmt; + +use error_stack::{Context, Report}; + +#[derive(Debug)] +pub struct ResourceError; + +impl ResourceError { + pub fn new(text: &str) -> Report { + Report::new(Self).attach_printable(text.to_string()) + } +} + +impl fmt::Display for ResourceError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str("Error loading resource") + } +} + +impl Context for ResourceError {} diff --git a/src/resources/fonts.rs b/src/resources/fonts.rs new file mode 100755 index 0000000..8fe180e --- /dev/null +++ b/src/resources/fonts.rs @@ -0,0 +1,37 @@ +use eframe::egui::{FontData, FontDefinitions}; +// const FONT_ROBOTO: &[u8] = include_bytes!("../../resources/Roboto-Light.ttf"); +// const FONT_ROBOTO_MONO: &[u8] = include_bytes!("../../resources/RobotoMono-Light.ttf"); +const FONT_OPEN_SANS: &[u8] = include_bytes!("../../resources/OpenSans-Light.ttf"); + +// pub fn register_fonts(style: &mut Style) { +// use eframe::epaint::FontFamily::{Monospace, Proportional}; + +// style.text_styles = [ +// (TextStyle::Body, FontId::new(21.0, Proportional)), +// (TextStyle::Button, FontId::new(18.0, Monospace)), +// (TextStyle::Monospace, FontId::new(18.0, Monospace)), +// (TextStyle::Small, FontId::new(16.0, Proportional)), +// (TextStyle::Heading, FontId::new(48.0, Proportional)), +// ] +// .into(); +// } + +pub fn load_fonts() -> FontDefinitions { + use eframe::epaint::FontFamily::{Monospace, Proportional}; + + let mut fonts = FontDefinitions::default(); + // let roboto = FontData::from_static(FONT_ROBOTO); + // let roboto_mono = FontData::from_static(FONT_ROBOTO_MONO); + let open_sans = FontData::from_static(FONT_OPEN_SANS); + + // fonts.font_data.insert("roboto".into(), roboto); + // fonts.font_data.insert("roboto_mono".into(), roboto_mono); + fonts.font_data.insert("OpenSans".into(), open_sans); + + // fonts.families.insert(Monospace, vec!["roboto_mono".into()]); + // fonts.families.insert(Monospace, vec!["roboto".into()]); + fonts.families.insert(Proportional, vec!["OpenSans".into()]); + fonts.families.insert(Monospace, vec!["OpenSans".into()]); + + fonts +} diff --git a/src/resources/icons.rs b/src/resources/icons.rs new file mode 100644 index 0000000..f28d235 --- /dev/null +++ b/src/resources/icons.rs @@ -0,0 +1,17 @@ +use eframe::egui::{include_image, ImageSource}; + +pub struct Icons<'a> { + pub gerber_file: ImageSource<'a>, + pub excellon_file: ImageSource<'a>, + pub geometry_file: ImageSource<'a>, +} + +impl<'a> Icons<'a> { + pub fn preload() -> Self { + Self { + gerber_file: include_image!("../../resources/gerber_file.png"), + excellon_file: include_image!("../../resources/excellon_file.png"), + geometry_file: include_image!("../../resources/geometry_file.png"), + } + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100755 index 0000000..2ba0b1f --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1,25 @@ +// mod fonts; +mod errors; +mod icons; + +pub use self::icons::Icons; + +pub struct ResourceLoader<'a> { + icons: Icons<'a>, +} + +impl<'a> ResourceLoader<'a> { + pub fn new() -> Self { + Self { + icons: Icons::preload(), + } + } + + // pub fn fonts(&self) -> FontDefinitions { + // fonts::load_fonts() + // } + + pub fn icons(&self) -> &Icons { + &self.icons + } +}