From 17efc2b2b2073e3b4f5802333fb1702c5c815935 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 15 May 2023 10:44:16 +0200 Subject: [PATCH] Remove deprecated crates Those have been moved to the fornjot-extra repository: https://github.com/hannobraun/fornjot-extra --- Cargo.lock | 891 +------------------- Cargo.toml | 19 +- crates/fj-app/Cargo.toml | 39 - crates/fj-app/src/args.rs | 67 -- crates/fj-app/src/config.rs | 25 - crates/fj-app/src/main.rs | 98 --- crates/fj-app/src/path.rs | 139 --- crates/fj-host/Cargo.toml | 23 - crates/fj-host/src/host.rs | 87 -- crates/fj-host/src/host_thread.rs | 147 ---- crates/fj-host/src/lib.rs | 33 - crates/fj-host/src/model.rs | 354 -------- crates/fj-host/src/parameters.rs | 40 - crates/fj-host/src/platform.rs | 50 -- crates/fj-host/src/watcher.rs | 73 -- crates/fj-operations/Cargo.toml | 19 - crates/fj-operations/src/difference_2d.rs | 99 --- crates/fj-operations/src/group.rs | 32 - crates/fj-operations/src/lib.rs | 109 --- crates/fj-operations/src/shape_processor.rs | 68 -- crates/fj-operations/src/sketch.rs | 148 ---- crates/fj-operations/src/sweep.rs | 41 - crates/fj-operations/src/transform.rs | 32 - crates/fj-proc/Cargo.toml | 26 - crates/fj-proc/src/expand.rs | 185 ---- crates/fj-proc/src/lib.rs | 131 --- crates/fj-proc/src/parse.rs | 388 --------- crates/fj/Cargo.toml | 28 - crates/fj/build.rs | 103 --- crates/fj/src/abi/context.rs | 78 -- crates/fj/src/abi/ffi_safe.rs | 400 --------- crates/fj/src/abi/host.rs | 48 -- crates/fj/src/abi/metadata.rs | 141 ---- crates/fj/src/abi/mod.rs | 215 ----- crates/fj/src/abi/model.rs | 117 --- crates/fj/src/angle.rs | 130 --- crates/fj/src/group.rs | 39 - crates/fj/src/lib.rs | 54 -- crates/fj/src/models/context.rs | 16 - crates/fj/src/models/host.rs | 40 - crates/fj/src/models/metadata.rs | 232 ----- crates/fj/src/models/mod.rs | 16 - crates/fj/src/models/model.rs | 23 - crates/fj/src/shape_2d.rs | 259 ------ crates/fj/src/sweep.rs | 48 -- crates/fj/src/syntax.rs | 133 --- crates/fj/src/transform.rs | 46 - crates/fj/src/version.rs | 68 -- justfile | 2 +- models/cuboid/Cargo.toml | 7 - models/cuboid/README.md | 10 - models/cuboid/cuboid.png | Bin 63874 -> 0 bytes models/cuboid/src/lib.rs | 18 - models/spacer/Cargo.toml | 7 - models/spacer/README.md | 10 - models/spacer/spacer.png | Bin 75170 -> 0 bytes models/spacer/src/lib.rs | 16 - models/star/Cargo.toml | 7 - models/star/README.md | 10 - models/star/src/lib.rs | 40 - models/star/star.png | Bin 104154 -> 0 bytes models/test/Cargo.toml | 11 - models/test/README.md | 10 - models/test/src/lib.rs | 95 --- models/test/test.png | Bin 128331 -> 0 bytes tools/cross-compiler/src/main.rs | 23 +- 66 files changed, 12 insertions(+), 5851 deletions(-) delete mode 100644 crates/fj-app/Cargo.toml delete mode 100644 crates/fj-app/src/args.rs delete mode 100644 crates/fj-app/src/config.rs delete mode 100644 crates/fj-app/src/main.rs delete mode 100644 crates/fj-app/src/path.rs delete mode 100644 crates/fj-host/Cargo.toml delete mode 100644 crates/fj-host/src/host.rs delete mode 100644 crates/fj-host/src/host_thread.rs delete mode 100644 crates/fj-host/src/lib.rs delete mode 100644 crates/fj-host/src/model.rs delete mode 100644 crates/fj-host/src/parameters.rs delete mode 100644 crates/fj-host/src/platform.rs delete mode 100644 crates/fj-host/src/watcher.rs delete mode 100644 crates/fj-operations/Cargo.toml delete mode 100644 crates/fj-operations/src/difference_2d.rs delete mode 100644 crates/fj-operations/src/group.rs delete mode 100644 crates/fj-operations/src/lib.rs delete mode 100644 crates/fj-operations/src/shape_processor.rs delete mode 100644 crates/fj-operations/src/sketch.rs delete mode 100644 crates/fj-operations/src/sweep.rs delete mode 100644 crates/fj-operations/src/transform.rs delete mode 100644 crates/fj-proc/Cargo.toml delete mode 100644 crates/fj-proc/src/expand.rs delete mode 100644 crates/fj-proc/src/lib.rs delete mode 100644 crates/fj-proc/src/parse.rs delete mode 100644 crates/fj/Cargo.toml delete mode 100644 crates/fj/build.rs delete mode 100644 crates/fj/src/abi/context.rs delete mode 100644 crates/fj/src/abi/ffi_safe.rs delete mode 100644 crates/fj/src/abi/host.rs delete mode 100644 crates/fj/src/abi/metadata.rs delete mode 100644 crates/fj/src/abi/mod.rs delete mode 100644 crates/fj/src/abi/model.rs delete mode 100644 crates/fj/src/angle.rs delete mode 100644 crates/fj/src/group.rs delete mode 100644 crates/fj/src/lib.rs delete mode 100644 crates/fj/src/models/context.rs delete mode 100644 crates/fj/src/models/host.rs delete mode 100644 crates/fj/src/models/metadata.rs delete mode 100644 crates/fj/src/models/mod.rs delete mode 100644 crates/fj/src/models/model.rs delete mode 100644 crates/fj/src/shape_2d.rs delete mode 100644 crates/fj/src/sweep.rs delete mode 100644 crates/fj/src/syntax.rs delete mode 100644 crates/fj/src/transform.rs delete mode 100644 crates/fj/src/version.rs delete mode 100644 models/cuboid/Cargo.toml delete mode 100644 models/cuboid/README.md delete mode 100644 models/cuboid/cuboid.png delete mode 100644 models/cuboid/src/lib.rs delete mode 100644 models/spacer/Cargo.toml delete mode 100644 models/spacer/README.md delete mode 100644 models/spacer/spacer.png delete mode 100644 models/spacer/src/lib.rs delete mode 100644 models/star/Cargo.toml delete mode 100644 models/star/README.md delete mode 100644 models/star/src/lib.rs delete mode 100644 models/star/star.png delete mode 100644 models/test/Cargo.toml delete mode 100644 models/test/README.md delete mode 100644 models/test/src/lib.rs delete mode 100644 models/test/test.png diff --git a/Cargo.lock b/Cargo.lock index c5ccd6a8e..466a8b1d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,30 +65,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android-activity" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" -dependencies = [ - "android-properties", - "bitflags", - "cc", - "jni-sys", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "num_enum", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -177,12 +153,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - [[package]] name = "arrayvec" version = "0.7.2" @@ -195,7 +165,7 @@ version = "0.37.2+1.3.238" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" dependencies = [ - "libloading 0.7.4", + "libloading", ] [[package]] @@ -305,15 +275,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] - [[package]] name = "atomic_refcell" version = "0.1.10" @@ -407,25 +368,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-sys" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" -dependencies = [ - "objc-sys", -] - -[[package]] -name = "block2" -version = "0.2.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" -dependencies = [ - "block-sys", - "objc2-encode", -] - [[package]] name = "bumpalo" version = "3.12.1" @@ -470,19 +412,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "calloop" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192" -dependencies = [ - "log", - "nix 0.25.1", - "slotmap", - "thiserror", - "vec_map", -] - [[package]] name = "camino" version = "1.1.4" @@ -520,9 +449,6 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -530,12 +456,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "chrono" version = "0.4.24" @@ -672,19 +592,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.1.1" @@ -713,16 +620,6 @@ dependencies = [ "anyhow", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.15" @@ -742,13 +639,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cuboid" -version = "0.1.0" -dependencies = [ - "fj", -] - [[package]] name = "cxx" version = "1.0.94" @@ -800,7 +690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" dependencies = [ "bitflags", - "libloading 0.7.4", + "libloading", "winapi", ] @@ -859,15 +749,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" -[[package]] -name = "dlib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" -dependencies = [ - "libloading 0.7.4", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -898,7 +779,6 @@ dependencies = [ "ahash 0.8.3", "epaint", "nohash-hasher", - "tracing", ] [[package]] @@ -914,19 +794,6 @@ dependencies = [ "wgpu", ] -[[package]] -name = "egui-winit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab43597ba41f0ce39a364ad83185594578bfd8b3409b99dbcbb01df23afc3dbb" -dependencies = [ - "android-activity", - "egui", - "instant", - "tracing", - "winit", -] - [[package]] name = "either" version = "1.8.1" @@ -1065,63 +932,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "figment" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" -dependencies = [ - "atomic", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - -[[package]] -name = "filetime" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.2.16", - "windows-sys 0.48.0", -] - -[[package]] -name = "fj" -version = "0.46.0" -dependencies = [ - "anyhow", - "backtrace", - "fj-proc", - "serde", - "serde_json", -] - -[[package]] -name = "fj-app" -version = "0.46.0" -dependencies = [ - "anyhow", - "clap", - "figment", - "fj", - "fj-export", - "fj-host", - "fj-interop", - "fj-kernel", - "fj-math", - "fj-operations", - "fj-viewer", - "fj-window", - "serde", - "tracing-subscriber", -] - [[package]] name = "fj-export" version = "0.46.0" @@ -1134,21 +944,6 @@ dependencies = [ "wavefront_rs", ] -[[package]] -name = "fj-host" -version = "0.46.0" -dependencies = [ - "cargo_metadata", - "crossbeam-channel", - "fj", - "fj-interop", - "fj-operations", - "libloading 0.8.0", - "notify", - "thiserror", - "tracing", -] - [[package]] name = "fj-interop" version = "0.46.0" @@ -1186,28 +981,6 @@ dependencies = [ "robust 1.0.0", ] -[[package]] -name = "fj-operations" -version = "0.46.0" -dependencies = [ - "fj", - "fj-interop", - "fj-kernel", - "fj-math", - "itertools", - "thiserror", -] - -[[package]] -name = "fj-proc" -version = "0.46.0" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.15", -] - [[package]] name = "fj-viewer" version = "0.46.0" @@ -1229,22 +1002,6 @@ dependencies = [ "wgpu", ] -[[package]] -name = "fj-window" -version = "0.46.0" -dependencies = [ - "crossbeam-channel", - "egui-winit", - "fj-host", - "fj-interop", - "fj-operations", - "fj-viewer", - "futures", - "thiserror", - "tracing", - "winit", -] - [[package]] name = "flate2" version = "1.0.26" @@ -1285,15 +1042,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futures" version = "0.3.28" @@ -1527,7 +1275,7 @@ dependencies = [ "bitflags", "com-rs", "libc", - "libloading 0.7.4", + "libloading", "thiserror", "widestring", "winapi", @@ -1735,32 +1483,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - [[package]] name = "instant" version = "0.1.12" @@ -1768,9 +1490,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -1823,21 +1542,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -1874,30 +1578,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" dependencies = [ "libc", - "libloading 0.7.4", + "libloading", "pkg-config", ] -[[package]] -name = "kqueue" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1920,16 +1604,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libloading" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "libm" version = "0.2.6" @@ -1985,15 +1659,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c2efbd1385acc8dad8a2e56558e58d949d777741fe110f2ddf3472671dbe3e6" -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "matrixmultiply" version = "0.3.7" @@ -2010,15 +1675,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.6.5" @@ -2048,12 +1704,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[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.6.2" @@ -2150,35 +1800,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "ndk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" -dependencies = [ - "jni-sys", -] - [[package]] name = "nix" version = "0.23.2" @@ -2192,75 +1813,12 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags", - "cfg-if", - "libc", - "memoffset", -] - -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "libc", - "memoffset", -] - [[package]] name = "nohash-hasher" 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 = "notify" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" -dependencies = [ - "bitflags", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "mio", - "walkdir", - "windows-sys 0.42.0", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -2333,27 +1891,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "objc" version = "0.2.7" @@ -2375,32 +1912,6 @@ dependencies = [ "objc_id", ] -[[package]] -name = "objc-sys" -version = "0.2.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" - -[[package]] -name = "objc2" -version = "0.3.0-beta.3.patch-leaks.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" -dependencies = [ - "block2", - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-encode" -version = "2.0.0-pre.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" -dependencies = [ - "objc-sys", -] - [[package]] name = "objc_exception" version = "0.1.2" @@ -2521,15 +2032,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc" -[[package]] -name = "orbclient" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "221d488cd70617f1bd599ed8ceb659df2147d9393717954d82a0f5e8032a6ab1" -dependencies = [ - "redox_syscall 0.3.5", -] - [[package]] name = "ordered-stream" version = "0.0.1" @@ -2559,12 +2061,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owned_ttf_parser" version = "0.19.0" @@ -2651,29 +2147,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" -[[package]] -name = "pear" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.15", -] - [[package]] name = "pem" version = "1.1.1" @@ -2823,19 +2296,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", - "version_check", - "yansi", -] - [[package]] name = "profiling" version = "1.0.8" @@ -2946,24 +2406,9 @@ checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.1", + "regex-syntax", ] -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.7.1" @@ -3168,15 +2613,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "schannel" version = "0.1.21" @@ -3186,12 +2622,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.1.0" @@ -3214,19 +2644,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sctk-adwaita" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia", -] - [[package]] name = "secrecy" version = "0.8.0" @@ -3355,15 +2772,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3428,25 +2836,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "smithay-client-toolkit" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" -dependencies = [ - "bitflags", - "calloop", - "dlib", - "lazy_static", - "log", - "memmap2", - "nix 0.24.3", - "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols", -] - [[package]] name = "snafu" version = "0.7.4" @@ -3480,13 +2869,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spacer" -version = "0.1.0" -dependencies = [ - "fj", -] - [[package]] name = "spade" version = "2.2.0" @@ -3515,13 +2897,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "star" -version = "0.1.0" -dependencies = [ - "fj", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -3537,12 +2912,6 @@ dependencies = [ "byteorder 0.4.2", ] -[[package]] -name = "strict-num" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" - [[package]] name = "strsim" version = "0.10.0" @@ -3593,13 +2962,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "test" -version = "0.1.0" -dependencies = [ - "fj", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -3620,16 +2982,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "threemf" version = "0.4.0" @@ -3680,31 +3032,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-skia" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "png", - "tiny-skia-path", -] - -[[package]] -name = "tiny-skia-path" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -3803,15 +3130,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.6.1" @@ -3908,36 +3226,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -3977,15 +3265,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "uncased" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.13" @@ -4049,24 +3328,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -4079,16 +3346,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.0" @@ -4183,79 +3440,6 @@ version = "2.0.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2f237e2271c3f9ccc633ee16918789514fd5a823bf62ddce21f8a730a1d9930" -[[package]] -name = "wayland-client" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.24.3", - "scoped-tls", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" -dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-cursor" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" -dependencies = [ - "nix 0.24.3", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" -dependencies = [ - "bitflags", - "wayland-client", - "wayland-commons", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", -] - -[[package]] -name = "wayland-sys" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" -dependencies = [ - "dlib", - "lazy_static", - "pkg-config", -] - [[package]] name = "web-sys" version = "0.3.61" @@ -4337,7 +3521,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.7.4", + "libloading", "log", "metal", "naga", @@ -4578,41 +3762,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "winit" -version = "0.28.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94c9651471cd576737671fbf7081edfea43de3e06846dd9bd4e49ea803c9f55f" -dependencies = [ - "android-activity", - "bitflags", - "cfg_aliases", - "core-foundation", - "core-graphics", - "dispatch", - "instant", - "libc", - "log", - "mio", - "ndk", - "objc2", - "once_cell", - "orbclient", - "percent-encoding", - "raw-window-handle", - "redox_syscall 0.3.5", - "sctk-adwaita", - "smithay-client-toolkit", - "wasm-bindgen", - "wayland-client", - "wayland-commons", - "wayland-protocols", - "wayland-scanner", - "web-sys", - "windows-sys 0.45.0", - "x11-dl", -] - [[package]] name = "winnow" version = "0.4.6" @@ -4631,32 +3780,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "xcursor" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" -dependencies = [ - "nom", -] - -[[package]] -name = "xml-rs" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699d0104bcdd7e7af6d093d6c6e2d0c479b8a129ee0d1023b31d2e0c71bfdda2" - [[package]] name = "yansi" version = "0.5.1" @@ -4687,7 +3810,7 @@ dependencies = [ "futures-util", "hex", "lazy_static", - "nix 0.23.2", + "nix", "once_cell", "ordered-stream", "rand", diff --git a/Cargo.toml b/Cargo.toml index a2d84eb2d..25bc1b8f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,12 @@ [workspace] resolver = "2" members = [ - "crates/fj", - "crates/fj-app", "crates/fj-export", - "crates/fj-host", "crates/fj-interop", "crates/fj-kernel", "crates/fj-math", - "crates/fj-operations", - "crates/fj-proc", "crates/fj-viewer", - "crates/fj-window", - - "models/cuboid", - "models/spacer", - "models/star", - "models/test", + # "crates/fj-window", "tools/autolib", "tools/automator", @@ -25,17 +15,12 @@ members = [ "tools/release-operator", ] default-members = [ - "crates/fj", - "crates/fj-app", "crates/fj-export", - "crates/fj-host", "crates/fj-interop", "crates/fj-kernel", "crates/fj-math", - "crates/fj-operations", - "crates/fj-proc", "crates/fj-viewer", - "crates/fj-window", + # "crates/fj-window", ] diff --git a/crates/fj-app/Cargo.toml b/crates/fj-app/Cargo.toml deleted file mode 100644 index ae9c82124..000000000 --- a/crates/fj-app/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "fj-app" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[dependencies] -anyhow = "1.0.71" -fj.workspace = true -fj-export.workspace = true -fj-host.workspace = true -fj-interop.workspace = true -fj-kernel.workspace = true -fj-math.workspace = true -fj-operations.workspace = true -fj-viewer.workspace = true -fj-window.workspace = true - -[dependencies.clap] -version = "4.2.7" -features = ["derive", "string"] - -[dependencies.figment] -version = "0.10.8" -features = ["env", "toml"] - -[dependencies.serde] -version = "1.0.162" -features = ["derive"] - -[dependencies.tracing-subscriber] -version = "0.3.17" -features = ["env-filter", "fmt"] diff --git a/crates/fj-app/src/args.rs b/crates/fj-app/src/args.rs deleted file mode 100644 index b8ab53e0a..000000000 --- a/crates/fj-app/src/args.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{path::PathBuf, str::FromStr as _}; - -use anyhow::anyhow; -use fj_host::Parameters; -use fj_kernel::algorithms::approx::Tolerance; -use fj_math::Scalar; - -/// Fornjot - Experimental CAD System -#[derive(clap::Parser)] -#[command(version = fj::version::VERSION_FULL.to_string())] -pub struct Args { - /// The model to open - pub model: Option, - - /// Export model to this path - #[arg(short, long, value_name = "PATH")] - pub export: Option, - - /// Parameters for the model, each in the form `key=value` - #[arg(short, long, value_parser = parse_parameters)] - pub parameters: Option, - - /// Model deviation tolerance - #[arg(short, long, value_parser = parse_tolerance)] - pub tolerance: Option, -} - -impl Args { - /// Parse the command-line arguments - /// - /// Convenience method that saves the caller from having to import the - /// `clap::Parser` trait. - pub fn parse() -> Self { - ::parse() - } -} - -fn parse_parameters(input: &str) -> anyhow::Result { - let mut parameters = Parameters::empty(); - - for parameter in input.split(',') { - let mut parameter = parameter.splitn(2, '='); - - let key = parameter - .next() - .ok_or_else(|| anyhow!("Expected model parameter key"))? - .trim() - .to_owned(); - let value = parameter - .next() - .ok_or_else(|| anyhow!("Expected model parameter value"))? - .trim() - .to_owned(); - - parameters.0.insert(key, value); - } - - Ok(parameters) -} - -fn parse_tolerance(input: &str) -> anyhow::Result { - let tolerance = f64::from_str(input)?; - let tolerance = Scalar::from_f64(tolerance); - let tolerance = Tolerance::from_scalar(tolerance)?; - - Ok(tolerance) -} diff --git a/crates/fj-app/src/config.rs b/crates/fj-app/src/config.rs deleted file mode 100644 index a2d7ba359..000000000 --- a/crates/fj-app/src/config.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::path::PathBuf; - -use anyhow::Context as _; -use figment::{ - providers::{Env, Format as _, Toml}, - Figment, -}; -use serde::Deserialize; - -#[derive(Debug, Deserialize)] -pub struct Config { - pub default_path: Option, - pub default_model: Option, - pub invert_zoom: Option, -} - -impl Config { - pub fn load() -> Result { - Figment::new() - .merge(Toml::file("fj.toml")) - .merge(Env::prefixed("FJ_")) - .extract() - .context("Error loading configuration") - } -} diff --git a/crates/fj-app/src/main.rs b/crates/fj-app/src/main.rs deleted file mode 100644 index 1b4c51128..000000000 --- a/crates/fj-app/src/main.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! # Fornjot Application -//! -//! This library is part of the [Fornjot] ecosystem. Fornjot is an open-source, -//! code-first CAD application; and collection of libraries that make up the CAD -//! application, but can be used independently. -//! -//! Together with the [`fj`] library, this application forms the part of Fornjot -//! that is relevant to end users. Please refer to the [Fornjot repository] for -//! usage examples. -//! -//! [Fornjot]: https://www.fornjot.app/ -//! [`fj`]: https://crates.io/crates/fj -//! [Fornjot repository]: https://github.com/hannobraun/Fornjot - -mod args; -mod config; -mod path; - -use std::{env, error::Error}; - -use anyhow::{anyhow, Context}; -use fj_export::export; -use fj_host::Parameters; -use fj_operations::shape_processor::ShapeProcessor; -use fj_window::run::run; -use path::ModelPath; -use tracing_subscriber::fmt::format; -use tracing_subscriber::EnvFilter; - -use crate::{args::Args, config::Config}; - -fn main() -> anyhow::Result<()> { - // Respect `RUST_LOG`. If that's not defined, log warnings and above. Fail if it's erroneous. - tracing_subscriber::fmt() - .with_env_filter(try_default_env_filter()?) - .event_format(format().pretty()) - .init(); - - let args = Args::parse(); - let config = Config::load()?; - let model_path = ModelPath::from_args_and_config(&args, &config); - let parameters = args.parameters.unwrap_or_else(Parameters::empty); - let shape_processor = ShapeProcessor { - tolerance: args.tolerance, - }; - - let model = model_path.map(|m| m.load_model(parameters)).transpose()?; - - if let Some(export_path) = args.export { - // export only mode. just load model, process, export and exit - - let evaluation = model.with_context(no_model_error)?.evaluate()?; - let shape = shape_processor.process(&evaluation.shape)?; - - export(&shape.mesh, &export_path)?; - - return Ok(()); - } - - let invert_zoom = config.invert_zoom.unwrap_or(false); - run(model, shape_processor, invert_zoom)?; - - Ok(()) -} - -fn no_model_error() -> anyhow::Error { - anyhow!( - "You must specify a model to start Fornjot in export only mode.\n\ - - Pass a model as a command-line argument. See `fj-app --help`.\n\ - - Specify a default model in the configuration file." - ) -} - -fn try_default_env_filter() -> anyhow::Result { - let env_filter = EnvFilter::try_from_default_env(); - - match env_filter { - Ok(env_filter) => Ok(env_filter), - - Err(err) => { - if let Some(kind) = err.source() { - if let Some(env::VarError::NotPresent) = - kind.downcast_ref::() - { - return Ok(EnvFilter::new("WARN")); - } - } else { - // `tracing_subscriber::filter::FromEnvError` currently returns a source - // in all cases. - unreachable!() - } - - Err(anyhow!( - "There was an error parsing the RUST_LOG environment variable." - )) - } - } -} diff --git a/crates/fj-app/src/path.rs b/crates/fj-app/src/path.rs deleted file mode 100644 index b68964c44..000000000 --- a/crates/fj-app/src/path.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::{ - fmt::{self, Write}, - path::{Path, PathBuf}, -}; - -use anyhow::Context; -use fj_host::{Model, Parameters}; - -use crate::{args::Args, config::Config}; - -pub struct ModelPath { - default_path: Option, - model_path: ModelPathSource, -} - -impl ModelPath { - pub fn from_args_and_config(args: &Args, config: &Config) -> Option { - let default_path = config.default_path.clone(); - - let model_path_from_args = args - .model - .as_ref() - .map(|model| ModelPathSource::Args(model.clone())); - let model_path_from_config = config - .default_model - .as_ref() - .map(|model| ModelPathSource::Config(model.clone())); - let model_path = model_path_from_args.or(model_path_from_config)?; - - Some(Self { - default_path, - model_path, - }) - } - - pub fn load_model(&self, parameters: Parameters) -> anyhow::Result { - let default_path = self - .default_path - .as_ref() - .map(|path| -> anyhow::Result<_> { - let rel = path; - let abs = path.canonicalize().with_context(|| { - format!( - "Converting `default-path` from `fj.toml` (`{}`) into \ - absolute path", - path.display(), - ) - })?; - Ok((rel, abs)) - }) - .transpose()?; - - let path = default_path - .clone() - .map(|(_, abs)| abs) - .unwrap_or_else(PathBuf::new) - .join(self.model_path.path()); - - let model = Model::new(&path, parameters).with_context(|| { - load_error_context(default_path, &self.model_path, path) - })?; - Ok(model) - } -} - -enum ModelPathSource { - Args(PathBuf), - Config(PathBuf), -} - -impl ModelPathSource { - fn path(&self) -> &Path { - match self { - Self::Args(path) => path, - Self::Config(path) => path, - } - } -} - -fn load_error_context( - default_path: Option<(&PathBuf, PathBuf)>, - model_path: &ModelPathSource, - path: PathBuf, -) -> String { - load_error_context_inner(default_path, model_path, path) - .expect("Expected `write!` to `String` to never fail") -} - -fn load_error_context_inner( - default_path: Option<(&PathBuf, PathBuf)>, - model_path: &ModelPathSource, - path: PathBuf, -) -> Result { - let mut error = String::new(); - write!( - error, - "Failed to load model: `{}`", - model_path.path().display() - )?; - match model_path { - ModelPathSource::Args(_) => { - write!(error, "\n- Passed via command-line argument")?; - } - ModelPathSource::Config(_) => { - write!(error, "\n- Specified as default model in configuration")?; - } - } - write!(error, "\n- Path of model: {}", path.display())?; - - let mut suggestions = String::new(); - write!(suggestions, "Suggestions:")?; - write!( - suggestions, - "\n- Did you mis-type the model path `{}`?", - model_path.path().display() - )?; - - if let Some((default_path_rel, default_path_abs)) = &default_path { - write!( - error, - "\n- Searching inside default path from configuration: {}", - default_path_abs.display(), - )?; - - write!( - suggestions, - "\n- Did you mis-type the default path `{}`?", - default_path_rel.display() - )?; - write!( - suggestions, - "\n- Did you accidentally pick up a local configuration file?" - )?; - } - - let context = format!("{error}\n\n{suggestions}"); - - Ok(context) -} diff --git a/crates/fj-host/Cargo.toml b/crates/fj-host/Cargo.toml deleted file mode 100644 index 41b25090b..000000000 --- a/crates/fj-host/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "fj-host" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - - -[dependencies] -cargo_metadata = "0.15.4" -crossbeam-channel = "0.5.8" -fj.workspace = true -fj-interop.workspace = true -fj-operations.workspace = true -libloading = "0.8.0" -notify = "5.1.0" -thiserror = "1.0.40" -tracing = "0.1.37" diff --git a/crates/fj-host/src/host.rs b/crates/fj-host/src/host.rs deleted file mode 100644 index 7789739d6..000000000 --- a/crates/fj-host/src/host.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::thread::JoinHandle; - -use crossbeam_channel::Sender; -use fj_operations::shape_processor::ShapeProcessor; - -use crate::{EventLoopClosed, HostThread, Model, ModelEvent}; - -/// A host for watching models and responding to model updates -pub struct Host { - command_tx: Sender, - host_thread: Option>>, - model_loaded: bool, -} - -impl Host { - /// Create a host with a shape processor and a send channel to the event - /// loop. - pub fn new( - shape_processor: ShapeProcessor, - model_event_tx: Sender, - ) -> Self { - let (command_tx, host_thread) = - HostThread::spawn(shape_processor, model_event_tx); - - Self { - command_tx, - host_thread: Some(host_thread), - model_loaded: false, - } - } - - /// Send a model to the host for evaluation and processing. - pub fn load_model(&mut self, model: Model) { - self.command_tx - .try_send(HostCommand::LoadModel(model)) - .expect("Host channel disconnected unexpectedly"); - self.model_loaded = true; - } - - /// Whether a model has been sent to the host yet - pub fn is_model_loaded(&self) -> bool { - self.model_loaded - } - - /// Check if the host thread has exited with a panic. This method runs at - /// each tick of the event loop. Without an explicit check, an operation - /// will appear to hang forever (e.g. processing a model). An error - /// will be printed to the terminal, but the gui will not notice until - /// a new `HostCommand` is issued on the disconnected channel. - /// - /// # Panics - /// - /// This method panics on purpose so the main thread can exit on an - /// unrecoverable error. - pub fn propagate_panic(&mut self) { - if self.host_thread.is_none() { - unreachable!("Constructor requires host thread") - } - if let Some(host_thread) = &self.host_thread { - // The host thread should not finish while this handle holds the - // `command_tx` channel open, so an exit means the thread panicked. - if host_thread.is_finished() { - let host_thread = self.host_thread.take().unwrap(); - match host_thread.join() { - Ok(_) => { - unreachable!( - "Host thread cannot exit until host handle disconnects" - ) - } - // The error value has already been reported by the panic - // in the host thread, so just ignore it here. - Err(_) => { - panic!("Host thread panicked") - } - } - } - } - } -} - -/// Commands that can be sent to a host -pub enum HostCommand { - /// Load a model to be evaluated and processed - LoadModel(Model), - /// Used by a `Watcher` to trigger evaluation when a model is edited - TriggerEvaluation, -} diff --git a/crates/fj-host/src/host_thread.rs b/crates/fj-host/src/host_thread.rs deleted file mode 100644 index 4cd7937ca..000000000 --- a/crates/fj-host/src/host_thread.rs +++ /dev/null @@ -1,147 +0,0 @@ -use std::thread::{self, JoinHandle}; - -use crossbeam_channel::{self, Receiver, Sender}; -use fj_interop::processed_shape::ProcessedShape; -use fj_operations::shape_processor::ShapeProcessor; - -use crate::{Error, HostCommand, Model, Watcher}; - -// Use a zero-sized error type to silence `#[warn(clippy::result_large_err)]`. -// The only error from `EventLoopProxy::send_event` is `EventLoopClosed`, -// so we don't need the actual value. We just need to know there was an error. -pub(crate) struct EventLoopClosed; - -pub(crate) struct HostThread { - shape_processor: ShapeProcessor, - model_event_tx: Sender, - command_tx: Sender, - command_rx: Receiver, -} - -impl HostThread { - // Spawn a background thread that will process models for an event loop. - pub(crate) fn spawn( - shape_processor: ShapeProcessor, - event_loop_proxy: Sender, - ) -> (Sender, JoinHandle>) { - let (command_tx, command_rx) = crossbeam_channel::unbounded(); - let command_tx_2 = command_tx.clone(); - - let host_thread = Self { - shape_processor, - model_event_tx: event_loop_proxy, - command_tx, - command_rx, - }; - - let join_handle = host_thread.spawn_thread(); - - (command_tx_2, join_handle) - } - - fn spawn_thread(mut self) -> JoinHandle> { - thread::Builder::new() - .name("host".to_string()) - .spawn(move || -> Result<(), EventLoopClosed> { - let mut model: Option = None; - let mut _watcher: Option = None; - - while let Ok(command) = self.command_rx.recv() { - match command { - HostCommand::LoadModel(new_model) => { - // Right now, `fj-app` will only load a new model - // once. The gui does not have a feature to load a - // new model after the initial load. If that were - // to change, there would be a race condition here - // if the prior watcher sent `TriggerEvaluation` - // before it and the model were replaced. - match Watcher::watch_model( - new_model.watch_path(), - self.command_tx.clone(), - ) { - Ok(watcher) => { - _watcher = Some(watcher); - self.send_event(ModelEvent::StartWatching)?; - } - - Err(err) => { - self.send_event(ModelEvent::Error(err))?; - continue; - } - } - self.process_model(&new_model)?; - model = Some(new_model); - } - HostCommand::TriggerEvaluation => { - self.send_event(ModelEvent::ChangeDetected)?; - if let Some(model) = &model { - self.process_model(model)?; - } - } - } - } - - Ok(()) - }) - .expect("Cannot create OS thread for host") - } - - // Evaluate and process a model. - fn process_model(&mut self, model: &Model) -> Result<(), EventLoopClosed> { - let evaluation = match model.evaluate() { - Ok(evaluation) => evaluation, - - Err(err) => { - self.send_event(ModelEvent::Error(err))?; - return Ok(()); - } - }; - - self.send_event(ModelEvent::Evaluated)?; - - if let Some(warn) = evaluation.warning { - self.send_event(ModelEvent::Warning(warn))?; - } - - match self.shape_processor.process(&evaluation.shape) { - Ok(shape) => self.send_event(ModelEvent::ProcessedShape(shape))?, - - Err(err) => { - self.send_event(ModelEvent::Error(err.into()))?; - } - } - - Ok(()) - } - - // Send a message to the event loop. - fn send_event(&mut self, event: ModelEvent) -> Result<(), EventLoopClosed> { - self.model_event_tx - .send(event) - .map_err(|_| EventLoopClosed)?; - - Ok(()) - } -} - -/// An event emitted by the host thread -#[derive(Debug)] -pub enum ModelEvent { - /// A new model is being watched - StartWatching, - - /// A change in the model has been detected - ChangeDetected, - - /// The model has been evaluated - Evaluated, - - /// The model has been processed - ProcessedShape(ProcessedShape), - - /// A warning - Warning(String), - - /// An error - Error(Error), -} diff --git a/crates/fj-host/src/lib.rs b/crates/fj-host/src/lib.rs deleted file mode 100644 index 9c4558b1a..000000000 --- a/crates/fj-host/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! # Fornjot Model Host -//! -//! This library is part of the [Fornjot] ecosystem. Fornjot is an open-source, -//! code-first CAD application; and collection of libraries that make up the CAD -//! application, but can be used independently. -//! -//! This library is an internal component of Fornjot. It is not relevant to end -//! users that just want to create CAD models. -//! -//! The purpose of this library is to load Fornjot models and watch them for -//! changes. Fornjot models are basically plugins that can be loaded into a CAD -//! application. This library is the host for these model plugins. -//! -//! [Fornjot]: https://www.fornjot.app/ - -#![warn(missing_docs)] - -mod host; -mod host_thread; -mod model; -mod parameters; -mod platform; -mod watcher; - -pub(crate) use self::host_thread::{EventLoopClosed, HostThread}; - -pub use self::{ - host::{Host, HostCommand}, - host_thread::ModelEvent, - model::{Error, Evaluation, Model}, - parameters::Parameters, - watcher::Watcher, -}; diff --git a/crates/fj-host/src/model.rs b/crates/fj-host/src/model.rs deleted file mode 100644 index 64672a50d..000000000 --- a/crates/fj-host/src/model.rs +++ /dev/null @@ -1,354 +0,0 @@ -use std::{ - io, - path::{Path, PathBuf}, - process::Command, - str, -}; - -use fj::{abi, version::Version}; -use fj_operations::shape_processor; -use tracing::debug; - -use crate::{platform::HostPlatform, Parameters}; - -/// Represents a Fornjot model -pub struct Model { - src_path: PathBuf, - lib_path: PathBuf, - manifest_path: PathBuf, - parameters: Parameters, -} - -impl Model { - /// Initialize the model using the path to its crate - /// - /// The path expected here is the root directory of the model's Cargo - /// package, that is the folder containing `Cargo.toml`. - pub fn new( - path: impl AsRef, - parameters: Parameters, - ) -> Result { - let path = path.as_ref(); - - let crate_dir = path.canonicalize()?; - - let metadata = cargo_metadata::MetadataCommand::new() - .current_dir(&crate_dir) - .exec()?; - - let pkg = package_associated_with_directory(&metadata, &crate_dir)?; - let src_path = crate_dir.join("src"); - - let lib_path = { - let name = pkg.name.replace('-', "_"); - let file = HostPlatform::lib_file_name(&name); - let target_dir = - metadata.target_directory.clone().into_std_path_buf(); - target_dir.join("debug").join(file) - }; - - Ok(Self { - src_path, - lib_path, - manifest_path: pkg.manifest_path.as_std_path().to_path_buf(), - parameters, - }) - } - - /// Access the path that needs to be watched for changes - pub fn watch_path(&self) -> PathBuf { - self.src_path.clone() - } - - /// Evaluate the model - pub fn evaluate(&self) -> Result { - let manifest_path = self.manifest_path.display().to_string(); - - let cargo_output = Command::new("cargo") - .arg("rustc") - .args(["--manifest-path", &manifest_path]) - .args(["--crate-type", "cdylib"]) - .output()?; - - if !cargo_output.status.success() { - let output = - String::from_utf8(cargo_output.stderr).unwrap_or_else(|_| { - String::from("Failed to fetch command output") - }); - - return Err(Error::Compile { output }); - } - - let seconds_taken = str::from_utf8(&cargo_output.stderr) - .unwrap() - .rsplit_once(' ') - .unwrap() - .1 - .trim(); - - let mut warnings = None; - - // So, strictly speaking this is all unsound: - // - `Library::new` requires us to abide by the arbitrary requirements - // of any library initialization or termination routines. - // - `Library::get` requires us to specify the correct type for the - // model function. - // - The model function itself is `unsafe`, because it is a function - // from across an FFI interface. - // - // Typical models won't have initialization or termination routines (I - // think), should abide by the `ModelFn` signature, and might not do - // anything unsafe. But we have no way to know that the library the user - // told us to load actually does (I think). - // - // I don't know of a way to fix this. We should take this as motivation - // to switch to a better technique: - // https://github.com/hannobraun/Fornjot/issues/71 - let shape = unsafe { - let lib = libloading::Library::new(&self.lib_path) - .map_err(Error::LoadingLibrary)?; - - let version_pkg_host = fj::version::VERSION_PKG.to_string(); - - let version_pkg_model: libloading::Symbol<*const Version> = - lib.get(b"VERSION_PKG").map_err(Error::LoadingVersion)?; - let version_pkg_model = (**version_pkg_model).to_string(); - - debug!( - "Comparing package versions (host: {}, model: {})", - version_pkg_host, version_pkg_model - ); - if version_pkg_host != version_pkg_model { - let host = String::from_utf8_lossy(version_pkg_host.as_bytes()) - .into_owned(); - let model = version_pkg_model; - - return Err(Error::VersionMismatch { host, model }); - } - - let version_full_host = fj::version::VERSION_FULL.to_string(); - - let version_full_model: libloading::Symbol<*const Version> = - lib.get(b"VERSION_FULL").map_err(Error::LoadingVersion)?; - let version_full_model = (**version_full_model).to_string(); - - debug!( - "Comparing full versions (host: {}, model: {})", - version_full_host, version_full_model - ); - if version_full_host != version_full_model { - let host = - String::from_utf8_lossy(version_full_host.as_bytes()) - .into_owned(); - let model = version_full_model; - - warnings = - Some(format!("{}", Error::VersionMismatch { host, model })); - } - - let init: libloading::Symbol = lib - .get(abi::INIT_FUNCTION_NAME.as_bytes()) - .map_err(Error::LoadingInit)?; - - let mut host = Host::new(&self.parameters); - - match init(&mut abi::Host::from(&mut host)) { - abi::ffi_safe::Result::Ok(_metadata) => {} - abi::ffi_safe::Result::Err(e) => { - return Err(Error::InitializeModel(e.into())); - } - } - - let model = host.take_model().ok_or(Error::NoModelRegistered)?; - - model.shape(&host).map_err(Error::Shape)? - }; - - Ok(Evaluation { - shape, - compile_time: seconds_taken.into(), - warning: warnings, - }) - } -} - -/// The result of evaluating a model -/// -/// See [`Model::evaluate`]. -#[derive(Debug)] -pub struct Evaluation { - /// The shape - pub shape: fj::Shape, - - /// The time it took to compile the shape, from the Cargo output - pub compile_time: String, - - /// Warnings - pub warning: Option, -} - -pub struct Host<'a> { - args: &'a Parameters, - model: Option>, -} - -impl<'a> Host<'a> { - pub fn new(parameters: &'a Parameters) -> Self { - Self { - args: parameters, - model: None, - } - } - - pub fn take_model(&mut self) -> Option> { - self.model.take() - } -} - -impl<'a> fj::models::Host for Host<'a> { - fn register_boxed_model(&mut self, model: Box) { - self.model = Some(model); - } -} - -impl<'a> fj::models::Context for Host<'a> { - fn get_argument(&self, name: &str) -> Option<&str> { - self.args.get(name).map(String::as_str) - } -} - -fn package_associated_with_directory<'m>( - metadata: &'m cargo_metadata::Metadata, - dir: &Path, -) -> Result<&'m cargo_metadata::Package, Error> { - for pkg in metadata.workspace_packages() { - let crate_dir = pkg - .manifest_path - .parent() - .and_then(|p| p.canonicalize().ok()); - - if crate_dir.as_deref() == Some(dir) { - return Ok(pkg); - } - } - - Err(ambiguous_path_error(metadata, dir)) -} - -fn ambiguous_path_error( - metadata: &cargo_metadata::Metadata, - dir: &Path, -) -> Error { - let mut possible_paths = Vec::new(); - - for id in &metadata.workspace_members { - let cargo_toml = &metadata[id].manifest_path; - let crate_dir = cargo_toml - .parent() - .expect("A Cargo.toml always has a parent"); - // Try to make the path relative to the workspace root so error messages - // aren't super long. - let simplified_path = crate_dir - .strip_prefix(&metadata.workspace_root) - .unwrap_or(crate_dir); - - possible_paths.push(simplified_path.into()); - } - - Error::AmbiguousPath { - dir: dir.to_path_buf(), - possible_paths, - } -} - -/// An error that can occur when loading or reloading a model -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Error loading model library - #[error( - "Failed to load model library\n\ - This might be a bug in Fornjot, or, at the very least, this error \ - message should be improved. Please report this!" - )] - LoadingLibrary(#[source] libloading::Error), - - /// Error loading Fornjot version that the model uses - #[error( - "Failed to load the Fornjot version that the model uses\n\ - - Is your model using the `fj` library? All models must!\n\ - - Was your model created with a really old version of Fornjot?" - )] - LoadingVersion(#[source] libloading::Error), - - /// Error loading the model's `init` function - #[error( - "Failed to load the model's `init` function\n\ - - Did you define a model function using `#[fj::model]`?" - )] - LoadingInit(#[source] libloading::Error), - - /// Host version and model version do not match - #[error("Host version ({host}) and model version ({model}) do not match")] - VersionMismatch { - /// The host version - host: String, - - /// The model version - model: String, - }, - - /// Model failed to compile - #[error("Error compiling model\n{output}")] - Compile { - /// The compiler output - output: String, - }, - - /// I/O error while loading the model - #[error("I/O error while loading model")] - Io(#[from] io::Error), - - /// Initializing a model failed. - #[error("Unable to initialize the model")] - InitializeModel(#[source] fj::models::Error), - - /// The user forgot to register a model when calling - /// [`fj::register_model!()`]. - #[error("No model was registered")] - NoModelRegistered, - - /// An error was returned from [`fj::models::Model::shape()`]. - #[error("Unable to determine the model's geometry")] - Shape(#[source] fj::models::Error), - - /// An error was returned from - /// [`fj_operations::shape_processor::ShapeProcessor::process()`]. - #[error("Shape processing error")] - ShapeProcessor(#[from] shape_processor::Error), - - /// Error while watching the model code for changes - #[error("Error watching model for changes")] - Notify(#[from] notify::Error), - - /// An error occurred while trying to use evaluate - /// [`cargo_metadata::MetadataCommand`]. - #[error("Unable to determine the crate's metadata")] - CargoMetadata(#[from] cargo_metadata::Error), - - /// The user pointed us to a directory, but it doesn't look like that was - /// a crate root (i.e. the folder containing `Cargo.toml`). - #[error( - "It doesn't look like \"{}\" is a crate directory. Did you mean one of {}?", - dir.display(), - possible_paths.iter().map(|p| p.display().to_string()) - .collect::>() - .join(", ") - )] - AmbiguousPath { - /// The model directory supplied by the user. - dir: PathBuf, - /// The directories for each crate in the workspace, relative to the - /// workspace root. - possible_paths: Vec, - }, -} diff --git a/crates/fj-host/src/parameters.rs b/crates/fj-host/src/parameters.rs deleted file mode 100644 index bdfd658a0..000000000 --- a/crates/fj-host/src/parameters.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, -}; - -/// Parameters that are passed to a model. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Parameters(pub HashMap); - -impl Parameters { - /// Construct an empty instance of `Parameters` - pub fn empty() -> Self { - Self(HashMap::new()) - } - - /// Insert a value into the [`Parameters`] dictionary, implicitly converting - /// the arguments to strings and returning `&mut self` to enable chaining. - pub fn insert( - &mut self, - key: impl Into, - value: impl ToString, - ) -> &mut Self { - self.0.insert(key.into(), value.to_string()); - self - } -} - -impl Deref for Parameters { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Parameters { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} diff --git a/crates/fj-host/src/platform.rs b/crates/fj-host/src/platform.rs deleted file mode 100644 index 47f6ead5f..000000000 --- a/crates/fj-host/src/platform.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Represents platform trait -pub trait Platform { - fn model_lib_file_name(&self, name: &str) -> String; -} - -// Represents all supported platforms - -// Mac OS -struct Macos; -// Windows -struct Windows; -// Linux -struct Unix; - -impl Platform for Windows { - fn model_lib_file_name(&self, name: &str) -> String { - format!("{name}.dll") - } -} - -impl Platform for Macos { - fn model_lib_file_name(&self, name: &str) -> String { - format!("lib{name}.dylib") - } -} - -impl Platform for Unix { - fn model_lib_file_name(&self, name: &str) -> String { - format!("lib{name}.so") - } -} - -// Abstracts over differences in host platforms -pub struct HostPlatform; - -impl HostPlatform { - pub fn get_os() -> Box { - if cfg!(windows) { - Box::new(Windows) - } else if cfg!(target_os = "macos") { - Box::new(Macos) - } else { - Box::new(Unix) - } - } - - pub fn lib_file_name(name: &str) -> String { - Self::get_os().model_lib_file_name(name) - } -} diff --git a/crates/fj-host/src/watcher.rs b/crates/fj-host/src/watcher.rs deleted file mode 100644 index 7248a7ecc..000000000 --- a/crates/fj-host/src/watcher.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::{collections::HashSet, ffi::OsStr, path::Path}; - -use crossbeam_channel::Sender; -use notify::Watcher as _; - -use crate::{Error, HostCommand}; - -/// Watches a model for changes, reloading it continually -pub struct Watcher { - _watcher: Box, -} - -impl Watcher { - /// Watch the provided model for changes - pub fn watch_model( - watch_path: impl AsRef, - host_tx: Sender, - ) -> Result { - let watch_path = watch_path.as_ref(); - - let mut watcher = notify::recommended_watcher( - move |event: notify::Result| { - // Unfortunately the `notify` documentation doesn't say when - // this might happen, so no idea if it needs to be handled. - let event = event.expect("Error handling watch event"); - - // Various acceptable ModifyKind kinds. Varies across platforms - // (e.g. MacOs vs. Windows10) - if let notify::EventKind::Modify( - notify::event::ModifyKind::Any - | notify::event::ModifyKind::Data( - notify::event::DataChange::Any - | notify::event::DataChange::Content, - ), - ) = event.kind - { - let file_ext = event - .paths - .get(0) - .expect("File path missing in watch event") - .extension(); - - let black_list = HashSet::from([ - OsStr::new("swp"), - OsStr::new("tmp"), - OsStr::new("swx"), - ]); - - if let Some(ext) = file_ext { - if black_list.contains(ext) { - return; - } - } - - // This will panic, if the other end is disconnected, which - // is probably the result of a panic on that thread, or the - // application is being shut down. - // - // Either way, not much we can do about it here. - host_tx - .send(HostCommand::TriggerEvaluation) - .expect("Channel is disconnected"); - } - }, - )?; - - watcher.watch(watch_path, notify::RecursiveMode::Recursive)?; - - Ok(Self { - _watcher: Box::new(watcher), - }) - } -} diff --git a/crates/fj-operations/Cargo.toml b/crates/fj-operations/Cargo.toml deleted file mode 100644 index 407d50d6e..000000000 --- a/crates/fj-operations/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "fj-operations" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[dependencies] -fj.workspace = true -fj-interop.workspace = true -fj-kernel.workspace = true -fj-math.workspace = true -itertools = "0.10.5" -thiserror = "1.0.40" diff --git a/crates/fj-operations/src/difference_2d.rs b/crates/fj-operations/src/difference_2d.rs deleted file mode 100644 index ad501f0d9..000000000 --- a/crates/fj-operations/src/difference_2d.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::ops::Deref; - -use fj_interop::{debug::DebugInfo, ext::ArrayExt, mesh::Color}; -use fj_kernel::{ - algorithms::reverse::Reverse, - objects::{Face, Sketch}, - operations::Insert, - services::Services, -}; -use fj_math::Aabb; - -use super::Shape; - -impl Shape for fj::Difference2d { - type Brep = Sketch; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - // This method assumes that `b` is fully contained within `a`: - // https://github.com/hannobraun/Fornjot/issues/92 - - let mut faces = Vec::new(); - - let mut exteriors = Vec::new(); - let mut interiors = Vec::new(); - - let [a, b] = self - .shapes() - .each_ref_ext() - .map(|shape| shape.compute_brep(services, debug_info)); - - if let Some(face) = a.faces().into_iter().next() { - // If there's at least one face to subtract from, we can proceed. - - let surface = face.surface(); - - for face in a.faces() { - assert_eq!( - surface, - face.surface(), - "Trying to subtract faces with different surfaces.", - ); - - exteriors.push(face.exterior().clone()); - for cycle in face.interiors() { - interiors.push(cycle.clone().reverse(services)); - } - } - - for face in b.faces() { - assert_eq!( - surface, - face.surface(), - "Trying to subtract faces with different surfaces.", - ); - - interiors.push(face.exterior().clone().reverse(services)); - } - - // Faces only support one exterior, while the code here comes from - // the time when a face could have multiple exteriors. This was only - // a special case, i.e. faces that connected to themselves, and I - // have my doubts that this code was ever correct in the first - // place. - // - // Anyway, the following should make sure that at least any problems - // this code causes become obvious. I don't know if this can ever - // trigger, but better safe than sorry. - let exterior = exteriors - .pop() - .expect("Can't construct face without an exterior"); - assert!( - exteriors.is_empty(), - "Can't construct face with multiple exteriors" - ); - - let face = Face::new( - surface.clone(), - exterior, - interiors, - Some(Color(self.color())), - ); - faces.push(face.insert(services)); - } - - let difference = Sketch::new(faces).insert(services); - difference.deref().clone() - } - - fn bounding_volume(&self) -> Aabb<3> { - // This is a conservative estimate of the bounding box: It's never going - // to be bigger than the bounding box of the original shape that another - // is being subtracted from. - self.shapes()[0].bounding_volume() - } -} diff --git a/crates/fj-operations/src/group.rs b/crates/fj-operations/src/group.rs deleted file mode 100644 index 7b4e997f5..000000000 --- a/crates/fj-operations/src/group.rs +++ /dev/null @@ -1,32 +0,0 @@ -use fj_interop::debug::DebugInfo; -use fj_kernel::{objects::FaceSet, services::Services}; -use fj_math::Aabb; - -use super::Shape; - -impl Shape for fj::Group { - type Brep = FaceSet; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - let mut faces = FaceSet::new(); - - let a = self.a.compute_brep(services, debug_info); - let b = self.b.compute_brep(services, debug_info); - - faces.extend(a); - faces.extend(b); - - faces - } - - fn bounding_volume(&self) -> Aabb<3> { - let a = self.a.bounding_volume(); - let b = self.b.bounding_volume(); - - a.merged(&b) - } -} diff --git a/crates/fj-operations/src/lib.rs b/crates/fj-operations/src/lib.rs deleted file mode 100644 index df6483338..000000000 --- a/crates/fj-operations/src/lib.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! # Fornjot CAD Operations -//! -//! This library is part of the [Fornjot] ecosystem. Fornjot is an open-source, -//! code-first CAD application; and collection of libraries that make up the CAD -//! application, but can be used independently. -//! -//! This library is an internal component of Fornjot. It is not relevant to end -//! users that just want to create CAD models. -//! -//! Fornjot models use the [`fj`] crate to define a shape. This crate provides -//! the connection between [`fj`] and the Fornjot kernel. It translates those -//! operations into terms the kernel can understand. -//! -//! [Fornjot]: https://www.fornjot.app/ -//! [`fj`]: https://crates.io/crates/fj - -#![warn(missing_docs)] - -pub mod shape_processor; - -mod difference_2d; -mod group; -mod sketch; -mod sweep; -mod transform; - -use fj_interop::debug::DebugInfo; -use fj_kernel::{ - objects::{FaceSet, Sketch}, - services::Services, -}; -use fj_math::Aabb; - -/// Implemented for all operations from the [`fj`] crate -pub trait Shape { - /// The type that is used for the shape's boundary representation - type Brep; - - /// Compute the boundary representation of the shape - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep; - - /// Access the axis-aligned bounding box of a shape - /// - /// If a shape is empty, its [`Aabb`]'s `min` and `max` points must be equal - /// (but are otherwise not specified). - fn bounding_volume(&self) -> Aabb<3>; -} - -impl Shape for fj::Shape { - type Brep = FaceSet; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - match self { - Self::Shape2d(shape) => { - shape.compute_brep(services, debug_info).faces().clone() - } - Self::Group(shape) => shape.compute_brep(services, debug_info), - Self::Sweep(shape) => shape - .compute_brep(services, debug_info) - .shells() - .map(|shell| shell.faces().clone()) - .reduce(|mut a, b| { - a.extend(b); - a - }) - .unwrap_or_default(), - Self::Transform(shape) => shape.compute_brep(services, debug_info), - } - } - - fn bounding_volume(&self) -> Aabb<3> { - match self { - Self::Shape2d(shape) => shape.bounding_volume(), - Self::Group(shape) => shape.bounding_volume(), - Self::Sweep(shape) => shape.bounding_volume(), - Self::Transform(shape) => shape.bounding_volume(), - } - } -} - -impl Shape for fj::Shape2d { - type Brep = Sketch; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - match self { - Self::Difference(shape) => shape.compute_brep(services, debug_info), - Self::Sketch(shape) => shape.compute_brep(services, debug_info), - } - } - - fn bounding_volume(&self) -> Aabb<3> { - match self { - Self::Difference(shape) => shape.bounding_volume(), - Self::Sketch(shape) => shape.bounding_volume(), - } - } -} diff --git a/crates/fj-operations/src/shape_processor.rs b/crates/fj-operations/src/shape_processor.rs deleted file mode 100644 index 6b7a451ea..000000000 --- a/crates/fj-operations/src/shape_processor.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! API for processing shapes - -use fj_interop::{debug::DebugInfo, processed_shape::ProcessedShape}; -use fj_kernel::{ - algorithms::{ - approx::{InvalidTolerance, Tolerance}, - triangulate::Triangulate, - }, - services::Services, - validate::ValidationError, -}; -use fj_math::Scalar; - -use crate::Shape as _; - -/// Processes an [`fj::Shape`] into a [`ProcessedShape`] -pub struct ShapeProcessor { - /// The tolerance value used for creating the triangle mesh - pub tolerance: Option, -} - -impl ShapeProcessor { - /// Process an [`fj::Shape`] into [`ProcessedShape`] - pub fn process(&self, shape: &fj::Shape) -> Result { - let aabb = shape.bounding_volume(); - - let tolerance = match self.tolerance { - None => { - // Compute a reasonable default for the tolerance value. To do - // this, we just look at the smallest non-zero extent of the - // bounding box and divide that by some value. - let mut min_extent = Scalar::MAX; - for extent in aabb.size().components { - if extent > Scalar::ZERO && extent < min_extent { - min_extent = extent; - } - } - - let tolerance = min_extent / Scalar::from_f64(1000.); - Tolerance::from_scalar(tolerance)? - } - Some(user_defined_tolerance) => user_defined_tolerance, - }; - - let mut services = Services::new(); - let mut debug_info = DebugInfo::new(); - let shape = shape.compute_brep(&mut services, &mut debug_info); - let mesh = (&shape, tolerance).triangulate(); - - Ok(ProcessedShape { - aabb, - mesh, - debug_info, - }) - } -} - -/// A shape processing error -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Error converting to shape - #[error("Error converting to shape")] - ToShape(#[from] Box), - - /// Model has zero size - #[error("Model has zero size")] - Extent(#[from] InvalidTolerance), -} diff --git a/crates/fj-operations/src/sketch.rs b/crates/fj-operations/src/sketch.rs deleted file mode 100644 index 35b097ae3..000000000 --- a/crates/fj-operations/src/sketch.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::ops::Deref; - -use fj_interop::{debug::DebugInfo, mesh::Color}; -use fj_kernel::{ - objects::{Cycle, Face, HalfEdge, Sketch}, - operations::{BuildCycle, BuildHalfEdge, Insert, UpdateCycle}, - services::Services, -}; -use fj_math::{Aabb, Point}; -use itertools::Itertools; - -use super::Shape; - -impl Shape for fj::Sketch { - type Brep = Sketch; - - fn compute_brep( - &self, - services: &mut Services, - _: &mut DebugInfo, - ) -> Self::Brep { - let surface = services.objects.surfaces.xy_plane(); - - let face = match self.chain() { - fj::Chain::Circle(circle) => { - let half_edge = HalfEdge::circle(circle.radius(), services) - .insert(services); - let exterior = Cycle::new([half_edge]).insert(services); - - Face::new( - surface, - exterior, - Vec::new(), - Some(Color(self.color())), - ) - } - fj::Chain::PolyChain(poly_chain) => { - let segments = poly_chain.to_segments(); - assert!( - !segments.is_empty(), - "Attempted to compute a Brep from an empty sketch" - ); - - let exterior = { - let mut cycle = Cycle::empty(); - - let segments = poly_chain - .to_segments() - .into_iter() - .map(|fj::SketchSegment { endpoint, route }| { - let endpoint = Point::from(endpoint); - (endpoint, route) - }) - .circular_tuple_windows(); - - for ((start, route), (end, _)) in segments { - let half_edge = match route { - fj::SketchSegmentRoute::Direct => { - HalfEdge::line_segment( - [start, end], - None, - services, - ) - } - fj::SketchSegmentRoute::Arc { angle } => { - HalfEdge::arc(start, end, angle.rad(), services) - } - }; - let half_edge = half_edge.insert(services); - - cycle = cycle.add_half_edges([half_edge]); - } - - cycle.insert(services) - }; - - Face::new( - surface, - exterior, - Vec::new(), - Some(Color(self.color())), - ) - } - }; - - let sketch = Sketch::new(vec![face.insert(services)]).insert(services); - sketch.deref().clone() - } - - fn bounding_volume(&self) -> Aabb<3> { - match self.chain() { - fj::Chain::Circle(circle) => Aabb { - min: Point::from([-circle.radius(), -circle.radius(), 0.0]), - max: Point::from([circle.radius(), circle.radius(), 0.0]), - }, - fj::Chain::PolyChain(poly_chain) => { - let segments = poly_chain.to_segments(); - assert!( - !segments.is_empty(), - "Attempted to compute a bounding box from an empty sketch" - ); - - let mut points = vec![]; - - let mut start_point = segments[segments.len() - 1].endpoint; - segments.iter().for_each(|segment| { - match segment.route { - fj::SketchSegmentRoute::Direct => (), - fj::SketchSegmentRoute::Arc { angle } => { - use std::f64::consts::PI; - let arc = fj_math::Arc::from_endpoints_and_angle( - start_point, - segment.endpoint, - fj_math::Scalar::from_f64(angle.rad()), - ); - for circle_min_max_angle in - [0., PI / 2., PI, 3. * PI / 2.] - { - let mm_angle = fj_math::Scalar::from_f64( - circle_min_max_angle, - ); - if arc.start_angle < mm_angle - && mm_angle < arc.end_angle - { - points.push( - arc.center - + [ - arc.radius - * circle_min_max_angle - .cos(), - arc.radius - * circle_min_max_angle - .sin(), - ], - ); - } - } - } - } - points.push(Point::from(segment.endpoint)); - start_point = segment.endpoint; - }); - - Aabb::<3>::from_points(points.into_iter().map(Point::to_xyz)) - } - } - } -} diff --git a/crates/fj-operations/src/sweep.rs b/crates/fj-operations/src/sweep.rs deleted file mode 100644 index ea32ea7d8..000000000 --- a/crates/fj-operations/src/sweep.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::ops::Deref; - -use fj_interop::debug::DebugInfo; -use fj_kernel::{ - algorithms::sweep::Sweep, objects::Solid, operations::Insert, - services::Services, -}; -use fj_math::{Aabb, Vector}; - -use super::Shape; - -impl Shape for fj::Sweep { - type Brep = Solid; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - let sketch = self - .shape() - .compute_brep(services, debug_info) - .insert(services); - - let path = Vector::from(self.path()); - - let solid = sketch.sweep(path, services); - solid.deref().clone() - } - - fn bounding_volume(&self) -> Aabb<3> { - self.shape() - .bounding_volume() - .merged(&Aabb::<3>::from_points( - self.shape() - .bounding_volume() - .vertices() - .map(|v| v + self.path()), - )) - } -} diff --git a/crates/fj-operations/src/transform.rs b/crates/fj-operations/src/transform.rs deleted file mode 100644 index e97306818..000000000 --- a/crates/fj-operations/src/transform.rs +++ /dev/null @@ -1,32 +0,0 @@ -use fj_interop::debug::DebugInfo; -use fj_kernel::{ - algorithms::transform::TransformObject, objects::FaceSet, - services::Services, -}; -use fj_math::{Aabb, Transform, Vector}; - -use super::Shape; - -impl Shape for fj::Transform { - type Brep = FaceSet; - - fn compute_brep( - &self, - services: &mut Services, - debug_info: &mut DebugInfo, - ) -> Self::Brep { - self.shape - .compute_brep(services, debug_info) - .transform(&make_transform(self), services) - } - - fn bounding_volume(&self) -> Aabb<3> { - make_transform(self).transform_aabb(&self.shape.bounding_volume()) - } -} - -fn make_transform(transform: &fj::Transform) -> Transform { - let axis = Vector::from(transform.axis).normalize(); - Transform::translation(transform.offset) - * Transform::rotation(axis * transform.angle.rad()) -} diff --git a/crates/fj-proc/Cargo.toml b/crates/fj-proc/Cargo.toml deleted file mode 100644 index 2ee0b9440..000000000 --- a/crates/fj-proc/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "fj-proc" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0.56" -quote = "1.0.23" - -[dependencies.serde] -version = "1.0.162" -optional = true - -[dependencies.syn] -version = "2.0.15" -features = ["full", "extra-traits"] diff --git a/crates/fj-proc/src/expand.rs b/crates/fj-proc/src/expand.rs deleted file mode 100644 index 990b0f711..000000000 --- a/crates/fj-proc/src/expand.rs +++ /dev/null @@ -1,185 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; - -use crate::parse::{ - ArgumentMetadata, Constraint, ConstraintKind, ExtractedArgument, - GeometryFunction, Initializer, Metadata, Model, -}; - -impl Initializer { - fn register() -> TokenStream { - quote! { - const _: () = { - fj::register_model!(|host| { - fj::models::HostExt::register_model(host, Model); - - Ok( - fj::models::Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")) - .with_short_description(env!("CARGO_PKG_DESCRIPTION")) - .with_homepage(env!("CARGO_PKG_HOMEPAGE")) - .with_repository(env!("CARGO_PKG_REPOSITORY")) - .with_license(env!("CARGO_PKG_LICENSE")), - ) - }); - }; - } - } -} - -impl ToTokens for Initializer { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { model } = self; - - tokens.extend(Self::register()); - model.to_tokens(tokens); - } -} - -impl Model { - fn definition() -> TokenStream { - quote! { struct Model; } - } - - fn trait_implementation(&self) -> TokenStream { - let Self { metadata, geometry } = self; - - quote! { - impl fj::models::Model for Model { - #metadata - #geometry - } - } - } -} - -impl ToTokens for Model { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(Self::definition()); - tokens.extend(self.trait_implementation()); - } -} - -impl ToTokens for Metadata { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { name, arguments } = self; - - tokens.extend(quote! { - fn metadata(&self) -> std::result::Result> { - Ok(fj::models::ModelMetadata::new(#name) - #( .with_argument(#arguments) )*) - } - }); - } -} - -impl ToTokens for ArgumentMetadata { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - name, - default_value, - } = self; - - tokens.extend(quote! { fj::models::ArgumentMetadata::new(#name) }); - - if let Some(default_value) = default_value { - tokens.extend(quote! { - .with_default_value(stringify!(#default_value)) - }); - } - } -} - -impl ToTokens for GeometryFunction { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - geometry_function, - arguments, - constraints, - fallible, - } = self; - - let argument_names = arguments.iter().map(|a| &a.ident); - - let invocation = quote! { - #geometry_function(#( #argument_names ),*) - }; - let invocation = if *fallible { - quote! { #invocation.map(fj::Shape::from).map_err(Into::into) } - } else { - quote! { Ok(#invocation.into()) } - }; - - tokens.extend(quote! { - fn shape( - &self, - ctx: &dyn fj::models::Context, - ) -> Result { - #( #arguments )* - #( #constraints )* - #invocation - } - }); - } -} - -impl ToTokens for ExtractedArgument { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - ident, - ty, - default_value, - } = self; - - let name = ident.to_string(); - let t = match default_value { - Some(default) => quote! { - let #ident: #ty = match ctx.get_argument(#name) { - Some(value) => value.parse()?, - None => #default - }; - }, - None => { - let error_message = format!("Expected {name}"); - quote! { - let #ident: #ty = match ctx.get_argument(#name) { - Some(value) => value.parse()?, - None => return Err(#error_message.into()), - }; - } - } - }; - - tokens.extend(t); - } -} - -impl ToTokens for Constraint { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { target, expr, kind } = self; - - let operator = match kind { - ConstraintKind::Max => quote!(<=), - ConstraintKind::Min => quote!(>=), - }; - let predicate = quote! { #target #operator #expr }; - // Note: this will cause `expr` to be evaluated twice. Predicates should - // be pure functions, so in theory this shouldn't be an issue. - let error_message = quote! { - format!( - "Expected {} {} {} (i.e. {} {} {})", - stringify!(#target), - stringify!(#operator), - stringify!(#expr), - #target, - stringify!(#operator), - #expr, - ) - }; - - tokens.extend(quote! { - if !(#predicate) { - return Err(#error_message.into()); - } - }); - } -} diff --git a/crates/fj-proc/src/lib.rs b/crates/fj-proc/src/lib.rs deleted file mode 100644 index 2ee7e85db..000000000 --- a/crates/fj-proc/src/lib.rs +++ /dev/null @@ -1,131 +0,0 @@ -mod expand; -mod parse; - -use proc_macro::TokenStream; -use syn::{parse_macro_input, FnArg, ItemFn}; - -/// Define a function-based model. -/// -/// The simplest model function takes no parameters and returns a hard-coded -/// `fj::Shape`. -/// -/// ``` rust ignore -/// # use fj_proc::model; -/// use fj::{Circle, Sketch, Shape}; -/// #[model] -/// fn model() -> Shape { -/// let circle = Circle::from_radius(10.0); -/// Sketch::from_circle(circle).into() -/// } -/// ``` -/// -/// For convenience, you can also return anything that could be converted into -/// a `fj::Shape` (e.g. a `fj::Sketch`). -/// -/// ``` rust ignore -/// # use fj_proc::model; -/// use fj::{Circle, Sketch}; -/// #[model] -/// fn model() -> Sketch { -/// let circle = Circle::from_radius(10.0); -/// Sketch::from_circle(circle) -/// } -/// ``` -/// -/// The return type is checked at compile time. That means something like this -/// won't work because `()` can't be converted into a `fj::Shape`. -/// -/// ``` rust ignore -/// # use fj_proc::model; -/// #[model] -/// fn model() { todo!() } -/// ``` -/// -/// The model function's arguments can be anything that implement -/// [`std::str::FromStr`]. -/// -/// ``` rust ignore -/// # use fj_proc::model; -/// #[model] -/// fn cylinder(height: f64, label: String, is_horizontal: bool) -> fj::Shape { todo!() } -/// ``` -/// -/// Constraints and default values can be added to an argument using the -/// `#[param]` attribute. -/// -/// ``` rust ignore -/// use fj::syntax::*; -/// -/// #[fj::model] -/// pub fn spacer( -/// #[param(default = 1.0, min = inner * 1.01)] outer: f64, -/// #[param(default = 0.5, max = outer * 0.99)] inner: f64, -/// #[param(default = 1.0)] height: f64, -/// ) -> fj::Shape { -/// let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer)); -/// let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner)); -/// -/// let footprint = outer_edge.difference(&inner_edge); -/// let spacer = footprint.sweep([0., 0., height]); -/// -/// spacer.into() -/// } -/// ``` -/// -/// For more complex situations, model functions are allowed to return any -/// error type that converts into a model error. -/// -/// ``` rust ignore -/// #[fj::model] -/// pub fn model() -> Result { -/// let home_dir = std::env::var("HOME")?; -/// -/// todo!("Do something with {home_dir}") -/// } -/// -/// fn assert_convertible(e: std::env::VarError) -> fj::models::Error { e.into() } -/// ``` -#[proc_macro_attribute] -pub fn model(_: TokenStream, input: TokenStream) -> TokenStream { - let item = parse_macro_input!(input as syn::ItemFn); - - match parse::parse(&item) { - Ok(init) => { - let item = without_param_attrs(item); - - let attrs = item.attrs; - let vis = item.vis; - let sig = item.sig; - let statements = item.block.stmts; - - let item = quote::quote! { - #(#attrs)* #vis #sig { - fj::abi::initialize_panic_handling(); - #(#statements)* - } - }; - - let tokens = quote::quote! { - #item - #init - - }; - - tokens.into() - } - Err(e) => e.into_compile_error().into(), - } -} - -/// Strip out any of our `#[param(...)]` attributes so the item will compile. -fn without_param_attrs(mut item: ItemFn) -> ItemFn { - for input in &mut item.sig.inputs { - let attrs = match input { - FnArg::Receiver(r) => &mut r.attrs, - FnArg::Typed(t) => &mut t.attrs, - }; - attrs.retain(|attr| !attr.path().is_ident("param")); - } - - item -} diff --git a/crates/fj-proc/src/parse.rs b/crates/fj-proc/src/parse.rs deleted file mode 100644 index 911517916..000000000 --- a/crates/fj-proc/src/parse.rs +++ /dev/null @@ -1,388 +0,0 @@ -use proc_macro2::Ident; -use syn::{ - bracketed, parenthesized, parse::Parse, parse_quote, Expr, ItemFn, - ReturnType, Type, -}; - -/// The call to `fj::register_model!()`. -#[derive(Debug)] -pub(crate) struct Initializer { - pub(crate) model: Model, -} - -/// The generated `Model` struct and its `fj::Model` impl. -#[derive(Debug)] -pub(crate) struct Model { - pub(crate) metadata: Metadata, - pub(crate) geometry: GeometryFunction, -} - -/// The model metadata we return in `<_ as fj::Model>::metadata()`. -#[derive(Debug)] -pub(crate) struct Metadata { - pub(crate) name: String, - pub(crate) arguments: Vec, -} - -/// Metadata for a specific argument. -#[derive(Debug)] -pub(crate) struct ArgumentMetadata { - pub(crate) name: String, - pub(crate) default_value: Option, -} - -/// The `<_ as fj::Model>::shape()` function. -#[derive(Debug)] -pub(crate) struct GeometryFunction { - pub(crate) geometry_function: Ident, - pub(crate) arguments: Vec, - pub(crate) constraints: Vec, - pub(crate) fallible: bool, -} - -#[derive(Debug)] -pub(crate) struct ExtractedArgument { - pub(crate) ident: Ident, - pub(crate) ty: Type, - pub(crate) default_value: Option, -} - -#[derive(Debug)] -pub(crate) struct Constraint { - pub(crate) target: Ident, - pub(crate) expr: Expr, - pub(crate) kind: ConstraintKind, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum ConstraintKind { - Min, - Max, -} - -pub(crate) fn parse(f: &ItemFn) -> syn::Result { - let model = parse_model(f)?; - - Ok(Initializer { model }) -} - -fn parse_model(item: &ItemFn) -> syn::Result { - let geometry_function = item.sig.ident.clone(); - - let args: Vec = item - .sig - .inputs - .iter() - .map(|inp| parse_quote!(#inp)) - .collect(); - - let metadata = Metadata { - name: geometry_function.to_string(), - arguments: args - .iter() - .map(|a| ArgumentMetadata { - name: a.ident.to_string(), - default_value: a.default(), - }) - .collect(), - }; - - let geometry = GeometryFunction { - geometry_function, - arguments: args - .iter() - .map(|a| ExtractedArgument { - ident: a.ident.clone(), - default_value: a.default(), - ty: a.ty.clone(), - }) - .collect(), - constraints: args.iter().flat_map(argument_constraints).collect(), - fallible: match &item.sig.output { - ReturnType::Default => false, - ReturnType::Type(_, ty) => contains_result(ty), - }, - }; - - Ok(Model { metadata, geometry }) -} - -fn contains_result(ty: &Type) -> bool { - match ty { - Type::Path(p) => p.path.segments.last().unwrap().ident == "Result", - _ => false, - } -} - -fn argument_constraints(arg: &Argument) -> Vec { - let Some(attr) = arg.attr.as_ref() else { - return Vec::new() - }; - - let mut constraints = Vec::new(); - - if let Some(min) = attr.get_minimum() { - constraints.push(Constraint { - target: arg.ident.clone(), - expr: min.val, - kind: ConstraintKind::Min, - }); - } - - if let Some(max) = attr.get_maximum() { - constraints.push(Constraint { - target: arg.ident.clone(), - expr: max.val, - kind: ConstraintKind::Max, - }); - } - - constraints -} - -/// Represents one parameter given to the `model`. -/// -/// ```text -/// #[param(default=3, min=4)] num_points: u64 -/// ^^^^^^^^^^^^^^^^^^^^^^^^^^ ~~~~~~~~~~ ^^^-- ty -/// | | -/// attr ident -/// ``` -#[derive(Debug, Clone)] -struct Argument { - attr: Option, - ident: Ident, - ty: Type, -} - -impl Argument { - fn default(&self) -> Option { - self.attr - .as_ref() - .and_then(|attr| attr.get_default()) - .map(|param| param.val) - } -} - -impl Parse for Argument { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let attr = if input.peek(syn::token::Pound) { - Some(input.parse()?) - } else { - None - }; - let ident: Ident = input.parse()?; - - let _: syn::token::Colon = input.parse()?; - - let ty: Type = input.parse()?; - Ok(Self { attr, ident, ty }) - } -} - -/// Represents all arguments given to the `#[param]` attribute eg: -/// -/// ```text -/// #[param(default=3, min=4)] -/// ^^^^^^^^^^^^^^^^ -/// ``` -#[derive(Debug, Clone)] -struct HelperAttribute { - param: Option>, -} - -impl Parse for HelperAttribute { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let attr_content; - let param_content; - let _: syn::token::Pound = input.parse()?; - bracketed!(attr_content in input); - let ident: Ident = attr_content.parse()?; - if ident != *"param" { - return Err(syn::Error::new_spanned( - ident.clone(), - format!( - "Unknown attribute \"{ident}\" found, expected \"param\"" - ), - )); - } - - if attr_content.peek(syn::token::Paren) { - parenthesized!(param_content in attr_content); - if param_content.is_empty() { - Ok(Self { param: None }) - } else { - Ok(Self { - param: Some( - syn::punctuated::Punctuated::parse_separated_nonempty_with( - ¶m_content, - DefaultParam::parse, - )?, - ), - }) - } - } else { - Ok(Self { param: None }) - } - } -} - -impl HelperAttribute { - fn get_parameter(&self, parameter_name: &str) -> Option { - if let Some(values) = self.param.clone() { - values.into_iter().find(|val| val.ident == *parameter_name) - } else { - None - } - } - - fn get_default(&self) -> Option { - self.get_parameter("default") - } - - fn get_minimum(&self) -> Option { - self.get_parameter("min") - } - - fn get_maximum(&self) -> Option { - self.get_parameter("max") - } -} - -/// Represents one argument given to the `#[param]` attribute eg: -/// -/// ```text -/// #[param(default=3)] -/// ^^^^^^^^^----- is parsed as DefaultParam{ ident: Some(default), val: 3 } -/// ``` -#[derive(Debug, Clone)] -struct DefaultParam { - ident: Ident, - val: syn::Expr, -} - -impl Parse for DefaultParam { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.peek(syn::Ident) { - let ident: Ident = input.parse()?; - let _: syn::token::Eq = input.parse()?; - Ok(Self { - ident, - val: input.parse()?, - }) - } else { - Err(input.parse::().expect_err("No identifier found")) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use quote::{quote, ToTokens}; - - #[test] - fn parse_a_typical_model_function() { - let tokens = quote! { - pub fn spacer( - #[param(default = 1.0, min = inner * 1.01)] outer: f64, - #[param(default = 0.5, max = outer * 0.99)] inner: f64, - height: f64, - ) -> fj::Shape { - let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer)); - let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner)); - - let footprint = outer_edge.difference(&inner_edge); - let spacer = footprint.sweep([0., 0., height]); - - spacer.into() - } - }; - let function: ItemFn = syn::parse2(tokens).unwrap(); - - let Initializer { - model: Model { metadata, geometry }, - } = parse(&function).unwrap(); - - // Note: we can't #[derive(PartialEq)] on our parsed structs because - // proc_macro2::Ident and friends don't implement PartialEq, so let's - // manually check everything parsed correctly. - let Metadata { name, arguments } = metadata; - assert_eq!(name, "spacer"); - let expected_meta = &[ - ("outer".to_string(), Some("1.0".to_string())), - ("inner".to_string(), Some("0.5".to_string())), - ("height".to_string(), None), - ]; - let meta: Vec<_> = arguments - .iter() - .map(|arg| { - ( - arg.name.clone(), - arg.default_value - .as_ref() - .map(|v| v.to_token_stream().to_string()), - ) - }) - .collect(); - assert_eq!(meta, expected_meta); - - let GeometryFunction { - geometry_function, - arguments, - constraints, - fallible, - } = geometry; - assert_eq!(geometry_function.to_string(), "spacer"); - assert!(!fallible); - let arguments: Vec<_> = arguments - .iter() - .map(|arg| { - ( - arg.ident.to_string(), - arg.default_value - .as_ref() - .map(|v| v.to_token_stream().to_string()), - ) - }) - .collect(); - assert_eq!(arguments, expected_meta); - let expected_constraints = &[ - ( - "outer".to_string(), - "inner * 1.01".to_string(), - ConstraintKind::Min, - ), - ( - "inner".to_string(), - "outer * 0.99".to_string(), - ConstraintKind::Max, - ), - ]; - let constraints: Vec<_> = constraints - .iter() - .map(|Constraint { kind, expr, target }| { - ( - target.to_string(), - expr.to_token_stream().to_string(), - *kind, - ) - }) - .collect(); - assert_eq!(constraints, expected_constraints); - } - - #[test] - fn parse_fallible_function() { - let tokens = quote! { - pub fn spacer() -> Result { - todo!() - } - }; - let function: ItemFn = syn::parse2(tokens).unwrap(); - - let init = parse(&function).unwrap(); - - assert!(init.model.geometry.fallible); - } -} diff --git a/crates/fj/Cargo.toml b/crates/fj/Cargo.toml deleted file mode 100644 index b32863766..000000000 --- a/crates/fj/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "fj" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - - -[build-dependencies] -anyhow = "1.0.71" - - -[dependencies] -fj-proc.workspace = true -backtrace = "0.3.67" - -[dependencies.serde] -version = "1.0.162" -features = ["derive"] -optional = true - -[dev-dependencies] -serde_json = "1.0.96" diff --git a/crates/fj/build.rs b/crates/fj/build.rs deleted file mode 100644 index 9c84e26e3..000000000 --- a/crates/fj/build.rs +++ /dev/null @@ -1,103 +0,0 @@ -use std::{ - fmt::Write, - path::PathBuf, - process::{Command, Output, Stdio}, -}; - -fn main() -> anyhow::Result<()> { - let version = Version::determine()?; - - println!("cargo:rustc-env=FJ_VERSION_PKG={}", version.pkg); - println!("cargo:rustc-env=FJ_VERSION_FULL={}", version.full); - - // Make sure the build script doesn't run too often. - println!("cargo:rerun-if-changed=Cargo.toml"); - - Ok(()) -} - -struct Version { - pkg: String, - full: String, -} -impl Version { - fn determine() -> anyhow::Result { - let pkg = std::env::var("CARGO_PKG_VERSION").unwrap(); - let commit = git_description(); - - let official_release = - std::env::var("RELEASE_DETECTED").as_deref() == Ok("true"); - println!("cargo:rerun-if-env-changed=RELEASE_DETECTED"); - - let mut full = format!("{pkg} ("); - - if official_release { - write!(full, "official release binary")?; - } else { - write!(full, "development build")?; - } - - if let Some(commit) = commit { - write!(full, "; {commit}")?; - } - - writeln!(full, ")")?; - - Ok(Self { pkg, full }) - } -} - -/// Try to get the current git commit. -/// -/// This may fail if `git` isn't installed (unlikely) or if the `.git/` folder -/// isn't accessible (more likely than you think). This typically happens when -/// we're building just the `fj-app` crate in a Docker container or when -/// someone is installing from crates.io via `cargo install`. -fn git_description() -> Option { - let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); - - let mut cmd = Command::new("git"); - cmd.args(["describe", "--always", "--tags"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .current_dir(&crate_dir); - - let Output { - status, - stdout, - stderr, - } = cmd.output().ok()?; - - let stdout = String::from_utf8_lossy(&stdout); - - if !status.success() { - // Not sure if anyone will ever see this, but it could be helpful for - // troubleshooting. - eprintln!("Command failed: {cmd:?}"); - let stderr = String::from_utf8_lossy(&stderr); - eprintln!("---- Stdout ----"); - eprintln!("{stdout}"); - eprintln!("---- Stderr ----"); - eprintln!("{stderr}"); - return None; - } - - // Make sure we re-run whenever the current commit changes - let project_root = crate_dir.ancestors().nth(2).unwrap(); - let head_file = project_root.join(".git").join("HEAD"); - println!("cargo:rerun-if-changed={}", head_file.display()); - - if let Ok(contents) = std::fs::read_to_string(&head_file) { - // Most of the time the HEAD file will be `ref: refs/heads/$branch`, but - // when it's a detached head we'll only get the commit hash and can skip - // the rerun-if-changed logic. - - if let Some((_, branch)) = contents.split_once(' ') { - let commit_hash_file = - project_root.join(".git").join(branch.trim()); - println!("cargo:rerun-if-changed={}", commit_hash_file.display()); - } - } - - Some(stdout.trim().to_string()) -} diff --git a/crates/fj/src/abi/context.rs b/crates/fj/src/abi/context.rs deleted file mode 100644 index 80d4bc0fa..000000000 --- a/crates/fj/src/abi/context.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{marker::PhantomData, os::raw::c_void, panic::AssertUnwindSafe}; - -use crate::abi::ffi_safe::StringSlice; - -#[repr(C)] -pub struct Context<'a> { - user_data: *const c_void, - get_argument: unsafe extern "C" fn( - *const c_void, - StringSlice, - ) -> crate::abi::ffi_safe::Result< - StringSlice, - crate::abi::ffi_safe::String, - >, - _lifetime: PhantomData<&'a ()>, -} - -impl<'a> From<&'a &dyn crate::models::Context> for Context<'a> { - fn from(ctx: &'a &dyn crate::models::Context) -> Self { - unsafe extern "C" fn get_argument( - user_data: *const c_void, - name: StringSlice, - ) -> crate::abi::ffi_safe::Result< - StringSlice, - crate::abi::ffi_safe::String, - > { - let ctx = &*(user_data as *const &dyn crate::models::Context); - - match std::panic::catch_unwind(AssertUnwindSafe(|| { - ctx.get_argument(&name) - })) { - Ok(Some(arg)) => { - crate::abi::ffi_safe::Result::Ok(StringSlice::from_str(arg)) - } - Ok(None) => { - crate::abi::ffi_safe::Result::Ok(StringSlice::from_str("")) - } - Err(payload) => crate::abi::ffi_safe::Result::Err( - crate::abi::on_panic(payload), - ), - } - } - - Context { - user_data: ctx as *const &dyn crate::models::Context - as *const c_void, - get_argument, - _lifetime: PhantomData, - } - } -} - -impl crate::models::Context for Context<'_> { - fn get_argument(&self, name: &str) -> Option<&str> { - unsafe { - let Context { - user_data, - get_argument, - _lifetime, - } = *self; - - let name = StringSlice::from_str(name); - - match name.trim().is_empty() { - true => None, - false => match get_argument(user_data, name) { - super::ffi_safe::Result::Ok(other) => { - match other.is_empty() { - true => None, - false => Some(other.into_str()), - } - } - super::ffi_safe::Result::Err(_) => None, - }, - } - } - } -} diff --git a/crates/fj/src/abi/ffi_safe.rs b/crates/fj/src/abi/ffi_safe.rs deleted file mode 100644 index 684f3e779..000000000 --- a/crates/fj/src/abi/ffi_safe.rs +++ /dev/null @@ -1,400 +0,0 @@ -//! FFI-safe versions of common `std` types. - -use std::{ - alloc::{GlobalAlloc, Layout, System}, - fmt::{self, Debug, Display, Formatter}, - ops::Deref, - ptr::NonNull, -}; - -use crate::models::Error; - -/// A FFI-safe version of `Vec`. -#[repr(C)] -pub(crate) struct Vec { - ptr: NonNull, - len: usize, -} - -impl Debug for Vec { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", &**self) - } -} - -impl PartialEq for Vec { - fn eq(&self, other: &Self) -> bool { - **self == **other - } -} - -impl From> for Vec { - fn from(mut items: std::vec::Vec) -> Self { - // Safety: To avoid accidental double-frees and other memory issues, we - // need to go through a specific dance. - unsafe { - // first, get a pointer to the first element and its length - let first_item = items.as_mut_ptr(); - let len = items.len(); - - // next, tell Vec to forget about these items so it won't try to - // run their destructors if we return early (e.g. via a panic). - // We've now taken over ownership of the items, but *not* the Vec's - // backing array. - items.set_len(0); - - // Use the system allocator to create some space for our - // FfiSafeVec's buffer. - let layout = Layout::array::(len).unwrap(); - let ptr: *mut T = System::default().alloc(layout).cast(); - let ptr = NonNull::new(ptr).expect("Allocation failed"); - - // Now, we can copy the items across - std::ptr::copy_nonoverlapping(first_item, ptr.as_ptr(), len); - - // the items are gone, time to free the original vec - drop(items); - - Self { ptr, len } - } - } -} - -impl From> for std::vec::Vec { - fn from(v: Vec) -> Self { - v.iter().map(Clone::clone).collect() - } -} - -impl Clone for Vec { - fn clone(&self) -> Self { - self.iter().cloned().collect() - } -} - -impl From> for Box<[T]> { - fn from(v: Vec) -> Self { - Self::from(&*v) - } -} - -impl Default for Vec { - fn default() -> Self { - std::vec::Vec::default().into() - } -} - -impl FromIterator for Vec { - fn from_iter>(iter: I) -> Self { - let vec: std::vec::Vec = iter.into_iter().collect(); - vec.into() - } -} - -impl Deref for Vec { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - // Safety: We control "ptr" and "len", so we know they are always - // initialized and within bounds. - unsafe { - let Self { ptr, len } = *self; - std::slice::from_raw_parts(ptr.as_ptr(), len) - } - } -} - -impl Drop for Vec { - fn drop(&mut self) { - let Self { ptr, len } = *self; - let ptr = ptr.as_ptr(); - - for i in 0..self.len { - // Safety: We control the "len" field, so the item we're accessing - // is always within bounds. We also don't touch values after their - // destructors are called. - unsafe { - let item = ptr.add(i); - std::ptr::drop_in_place(item); - } - } - - // Safety: This vec is immutable, so we're using the same layout as the - // original allocation. It's also not possible to touch the allocation - // after Drop completes. - unsafe { - let layout = Layout::array::(len).unwrap(); - System::default().dealloc(ptr.cast(), layout); - } - } -} - -// Safety: We're Send+Sync as long as the underlying type is. -unsafe impl Send for Vec {} -unsafe impl Sync for Vec {} - -#[cfg(feature = "serde")] -impl serde::ser::Serialize for Vec -where - T: serde::ser::Serialize, -{ - fn serialize( - &self, - serializer: S, - ) -> std::result::Result - where - S: serde::ser::Serializer, - { - self.deref().serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, T> serde::de::Deserialize<'de> for Vec -where - T: serde::de::Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::de::Deserializer<'de>, - { - Ok(std::vec::Vec::deserialize(deserializer)?.into()) - } -} - -/// A FFI-safe version of `Box`. -#[repr(transparent)] -#[derive(Debug, PartialEq, Clone)] -pub struct String(Vec); - -impl From for String { - fn from(s: std::string::String) -> Self { - Self(s.into_bytes().into()) - } -} - -impl From for std::string::String { - fn from(s: String) -> Self { - s.to_string() - } -} - -impl From for Box { - fn from(s: String) -> Self { - Self::from(&*s) - } -} -impl PartialEq for String { - fn eq(&self, other: &str) -> bool { - **self == *other - } -} - -impl PartialEq<&str> for String { - fn eq(&self, other: &&str) -> bool { - *self == **other - } -} - -impl Display for String { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&**self, f) - } -} - -impl Deref for String { - type Target = str; - - fn deref(&self) -> &Self::Target { - // Safety: The only way to create a FfiSafeString is from a valid Rust - // string, so we can skip the UTF-8 checks. - unsafe { std::str::from_utf8_unchecked(&self.0) } - } -} - -/// A version of `Result` that is `#[repr(C)]`. -#[must_use] -#[repr(C)] -pub enum Result { - Ok(T), - Err(E), -} - -impl Result { - pub fn unwrap(self) -> T { - match self { - Self::Ok(value) => value, - Self::Err(e) => panic!("Unwrapped an Err({e:?})"), - } - } -} - -impl From> for Result { - fn from(result: std::result::Result) -> Self { - match result { - Ok(ok) => Self::Ok(ok), - Err(err) => Self::Err(err), - } - } -} - -impl From> for std::result::Result { - fn from(result: Result) -> Self { - match result { - Result::Ok(ok) => Self::Ok(ok), - Result::Err(err) => Self::Err(err), - } - } -} - -#[repr(C)] -pub(crate) struct Slice { - ptr: NonNull, - len: usize, -} - -impl Slice { - /// Create a new [`Slice`] from a slice. - /// - /// # Safety - /// - /// It is the caller's responsibility to make sure this [`Slice`] doesn't - /// outlive the slice that was passed in. - pub unsafe fn from_slice(items: &[T]) -> Self { - let ptr = items.as_ptr(); - let len = items.len(); - Self { - // Safety: It's okay to cast away the const because you can't mutate - // a slice. - ptr: NonNull::new(ptr as *mut T).unwrap(), - len, - } - } - - pub unsafe fn into_slice<'a>(self) -> &'a [T] { - let Self { ptr, len } = self; - std::slice::from_raw_parts(ptr.as_ptr(), len) - } -} - -impl Debug for Slice { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Debug::fmt(&**self, f) - } -} - -impl PartialEq for Slice { - fn eq(&self, other: &Self) -> bool { - **self == **other - } -} - -impl Deref for Slice { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - // Safety: We control both "ptr" and "len", so the array is always - // initialized and within bounds. - // - // The lifetime of the &[T] is also bound to the lifetime of &self, so - // this should be safe as long as people can never get a Slice that - // outlives the data it points to. - unsafe { - let Self { ptr, len, .. } = *self; - std::slice::from_raw_parts(ptr.as_ptr(), len) - } - } -} - -#[repr(transparent)] -pub(crate) struct StringSlice(Slice); - -impl StringSlice { - /// Create a new [`StringSlice`]. - /// - /// # Safety - /// - /// It is the caller's responsibility to make sure this [`Slice`] doesn't - /// outlive the slice that was passed in. - pub unsafe fn from_str(s: &str) -> Self { - Self(Slice::from_slice(s.as_bytes())) - } - - pub unsafe fn into_str<'a>(self) -> &'a str { - let bytes = self.0.into_slice(); - std::str::from_utf8_unchecked(bytes) - } -} - -impl Deref for StringSlice { - type Target = str; - - fn deref(&self) -> &Self::Target { - // Safety: the only way you can construct a StringSlice is via a string. - unsafe { std::str::from_utf8_unchecked(&self.0) } - } -} - -#[derive(Debug)] -#[repr(C)] -pub struct BoxedError { - pub(crate) msg: String, -} - -impl Display for BoxedError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - Display::fmt(&self.msg, f) - } -} - -impl std::error::Error for BoxedError {} - -impl From for BoxedError { - fn from(err: Error) -> Self { - // Open question: is it worth capturing the message from each source - // error, too? We could have some sort of `sources: Vec` field - // where `Source` is a private wrapper around String that implements - // std::error::Error, however then people will see what *looks* like a - // particular error type, but they won't be able to downcast to it. - Self { - msg: err.to_string().into(), - } - } -} - -#[derive(Debug, Clone)] -#[repr(C)] -pub enum Option { - Some(T), - None, -} - -impl Option { - pub fn map(self, func: impl FnOnce(T) -> T2) -> Option { - match self { - Self::Some(value) => Option::Some(func(value)), - Self::None => Option::None, - } - } -} - -impl From> for Option -where - T1: Into, -{ - fn from(opt: std::option::Option) -> Self { - match opt { - Some(value) => Self::Some(value.into()), - None => Self::None, - } - } -} - -impl From> for std::option::Option { - fn from(opt: Option) -> Self { - match opt { - Option::Some(value) => Some(value), - Option::None => None, - } - } -} diff --git a/crates/fj/src/abi/host.rs b/crates/fj/src/abi/host.rs deleted file mode 100644 index 31d61edf2..000000000 --- a/crates/fj/src/abi/host.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::{marker::PhantomData, os::raw::c_void, panic::AssertUnwindSafe}; - -use crate::abi::Model; - -/// A FFI-safe `&mut dyn Host`. -#[repr(C)] -pub struct Host<'a> { - user_data: *mut c_void, - register_boxed_model: unsafe extern "C" fn(*mut c_void, model: Model), - _lifetime: PhantomData<&'a mut ()>, -} - -impl<'a, H: crate::models::Host + Sized> From<&'a mut H> for Host<'a> { - fn from(host: &'a mut H) -> Self { - extern "C" fn register_boxed_model( - user_data: *mut c_void, - model: Model, - ) { - let host = unsafe { &mut *(user_data as *mut H) }; - - if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| { - host.register_boxed_model(Box::new(model)); - })) { - crate::abi::on_panic(e); - } - } - - Host { - user_data: host as *mut H as *mut c_void, - register_boxed_model: register_boxed_model::, - _lifetime: PhantomData, - } - } -} - -impl<'a> crate::models::Host for Host<'a> { - fn register_boxed_model(&mut self, model: Box) { - let Host { - user_data, - register_boxed_model, - .. - } = *self; - - unsafe { - register_boxed_model(user_data, model.into()); - } - } -} diff --git a/crates/fj/src/abi/metadata.rs b/crates/fj/src/abi/metadata.rs deleted file mode 100644 index 7d56d8698..000000000 --- a/crates/fj/src/abi/metadata.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::abi::ffi_safe; - -#[derive(Debug)] -#[repr(C)] -pub struct ModelMetadata { - name: ffi_safe::String, - description: ffi_safe::Option, - arguments: ffi_safe::Vec, -} - -impl From for crate::models::ModelMetadata { - fn from(m: ModelMetadata) -> Self { - let ModelMetadata { - name, - description, - arguments, - } = m; - - Self { - name: name.into(), - description: description.map(Into::into).into(), - arguments: arguments.iter().cloned().map(Into::into).collect(), - } - } -} - -impl From for ModelMetadata { - fn from(m: crate::models::ModelMetadata) -> Self { - let crate::models::ModelMetadata { - name, - description, - arguments, - } = m; - - Self { - name: name.into(), - description: description.into(), - arguments: arguments.into_iter().map(Into::into).collect(), - } - } -} - -#[derive(Debug, Clone)] -#[repr(C)] -pub struct Metadata { - name: ffi_safe::String, - version: ffi_safe::String, - short_description: ffi_safe::Option, - description: ffi_safe::Option, - homepage: ffi_safe::Option, - repository: ffi_safe::Option, - license: ffi_safe::Option, -} - -impl From for crate::models::Metadata { - fn from(m: Metadata) -> Self { - let Metadata { - name, - version, - short_description, - description, - homepage, - repository, - license, - } = m; - - Self { - name: name.into(), - version: version.into(), - short_description: short_description.map(Into::into).into(), - description: description.map(Into::into).into(), - homepage: homepage.map(Into::into).into(), - repository: repository.map(Into::into).into(), - license: license.map(Into::into).into(), - } - } -} - -impl From for Metadata { - fn from(m: crate::models::Metadata) -> Self { - let crate::models::Metadata { - name, - version, - short_description, - description, - homepage, - repository, - license, - } = m; - - Self { - name: name.into(), - version: version.into(), - short_description: short_description.into(), - description: description.into(), - homepage: homepage.into(), - repository: repository.into(), - license: license.into(), - } - } -} - -#[derive(Debug, Clone)] -#[repr(C)] -pub struct ArgumentMetadata { - name: ffi_safe::String, - description: ffi_safe::Option, - default_value: ffi_safe::Option, -} - -impl From for ArgumentMetadata { - fn from(meta: crate::models::ArgumentMetadata) -> Self { - let crate::models::ArgumentMetadata { - name, - description, - default_value, - } = meta; - - Self { - name: name.into(), - description: description.into(), - default_value: default_value.into(), - } - } -} - -impl From for crate::models::ArgumentMetadata { - fn from(meta: ArgumentMetadata) -> Self { - let ArgumentMetadata { - name, - description, - default_value, - } = meta; - - Self { - name: name.into(), - description: description.map(Into::into).into(), - default_value: default_value.map(Into::into).into(), - } - } -} diff --git a/crates/fj/src/abi/mod.rs b/crates/fj/src/abi/mod.rs deleted file mode 100644 index d6e7c2363..000000000 --- a/crates/fj/src/abi/mod.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! Internal implementation details for the host-guest interface. -//! -//! Note that the vast majority of this module is just providing FFI-safe -//! versions of common `std` types (e.g. `Vec`, `String`, and `Box`), -//! or FFI-safe trait objects. -//! -/// If the macro generated the wrong code, this doctest would fail. -/// -/// ```rust -/// use fj::{abi::INIT_FUNCTION_NAME, models::Metadata}; -/// -/// fj::register_model!(|_| { -/// Ok(Metadata::new("My Model", "1.2.3")) -/// }); -/// -/// mod x { -/// extern "C" { -/// pub fn fj_model_init(_: *mut fj::abi::Host<'_>) -> fj::abi::InitResult; -/// } -/// } -/// -/// // make sure our function has the right signature -/// let func: fj::abi::InitFunction = fj_model_init; -/// -/// // We can also make sure the unmangled name is correct by calling it. -/// -/// let metadata: fj::models::Metadata = unsafe { -/// let mut host = Host; -/// let mut host = fj::abi::Host::from(&mut host); -/// x::fj_model_init(&mut host).unwrap().into() -/// }; -/// -/// assert_eq!(metadata.name, "My Model"); -/// -/// struct Host; -/// impl fj::models::Host for Host { -/// fn register_boxed_model(&mut self, model: Box) { todo!() } -/// } -/// ``` -mod context; -pub mod ffi_safe; -mod host; -mod metadata; -mod model; - -use backtrace::Backtrace; -use std::{any::Any, fmt::Display, panic, sync::Mutex}; - -pub use self::{ - context::Context, - host::Host, - metadata::{Metadata, ModelMetadata}, - model::Model, -}; - -/// Define the initialization routine used when registering models. -/// -/// See the [`crate::model`] macro if your model can be implemented as a pure -/// function. -/// -/// # Examples -/// -/// ```rust -/// use fj::models::*; -/// -/// fj::register_model!(|host: &mut dyn Host| { -/// host.register_model(MyModel::default()); -/// -/// Ok(Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))) -/// }); -/// -/// #[derive(Default)] -/// struct MyModel { } -/// -/// impl Model for MyModel { -/// fn metadata(&self) -> std::result::Result> { todo!() } -/// -/// fn shape(&self, ctx: &dyn Context) -> Result { -/// todo!() -/// } -/// } -/// ``` -#[macro_export] -macro_rules! register_model { - ($init:expr) => { - #[no_mangle] - unsafe extern "C" fn fj_model_init( - mut host: *mut $crate::abi::Host<'_>, - ) -> $crate::abi::InitResult { - let init: fn( - &mut dyn $crate::models::Host, - ) -> Result< - $crate::models::Metadata, - $crate::models::Error, - > = $init; - - match init(&mut *host) { - Ok(meta) => $crate::abi::InitResult::Ok(meta.into()), - Err(e) => $crate::abi::InitResult::Err(e.into()), - } - } - }; -} - -/// The signature of the function generated by [`register_model`]. -/// -/// ```rust -/// fj::register_model!(|_| { todo!() }); -/// -/// const _: fj::abi::InitFunction = fj_model_init; -/// ``` -pub type InitFunction = unsafe extern "C" fn(*mut Host<'_>) -> InitResult; -pub type InitResult = ffi_safe::Result; -pub type ShapeResult = ffi_safe::Result; -pub type ModelMetadataResult = - ffi_safe::Result; - -/// The name of the function generated by [`register_model`]. -/// -pub const INIT_FUNCTION_NAME: &str = "fj_model_init"; - -// Contains details about a panic that we need to pass back to the application from the panic hook. -struct PanicInfo { - message: Option, - location: Option, - backtrace: Backtrace, -} - -impl Display for PanicInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let message = self - .message - .as_ref() - .map_or("No error given", |message| message.as_str()); - - write!(f, "\"{}\", ", message)?; - - if let Some(location) = self.location.as_ref() { - write!(f, "{}", location)?; - } else { - write!(f, "no location given")?; - } - - writeln!(f, "\nBacktrace:\n{:?}", self.backtrace)?; - - Ok(()) - } -} - -struct Location { - file: String, - line: u32, - column: u32, -} - -impl Display for Location { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}:{}", self.file, self.line, self.column) - } -} - -static LAST_PANIC: Mutex> = Mutex::new(None); - -/// Capturing panics is something Rust really doesn't want you to do, and as such, they make it convoluted. -/// This sets up all the machinery in the background to pull it off. -/// -/// It's okay to call this multiple times. -pub fn initialize_panic_handling() { - panic::set_hook(Box::new(|panic_info| { - let mut last_panic = - LAST_PANIC.lock().expect("Panic queue was poisoned."); // FIXME that can probably overflow the stack. - let message = if let Some(s) = - panic_info.payload().downcast_ref::() - { - Some(s.as_str()) - } else { - panic_info.payload().downcast_ref::<&str>().copied() - } - .map(|s| s.to_string()); - - let location = panic_info.location().map(|location| Location { - file: location.file().to_string(), - line: location.line(), - column: location.column(), - }); - - let backtrace = backtrace::Backtrace::new(); - *last_panic = Some(PanicInfo { - message, - location, - backtrace, - }); - })); -} - -fn on_panic(_payload: Box) -> crate::abi::ffi_safe::String { - // The payload is technically no longer needed, but I left it there just in case a change to `catch_unwind` made - // it useful again. - if let Ok(mut panic_info) = LAST_PANIC.lock() { - if let Some(panic_info) = panic_info.take() { - crate::abi::ffi_safe::String::from(format!( - "Panic in model: {}", - panic_info - )) - } else { - crate::abi::ffi_safe::String::from( - "Panic in model: No details were given.".to_string(), - ) - } - } else { - crate::abi::ffi_safe::String::from( - "Panic in model, but due to a poisoned panic queue the information could not be collected." - .to_string()) - } -} diff --git a/crates/fj/src/abi/model.rs b/crates/fj/src/abi/model.rs deleted file mode 100644 index 892285b20..000000000 --- a/crates/fj/src/abi/model.rs +++ /dev/null @@ -1,117 +0,0 @@ -use std::{os::raw::c_void, panic::AssertUnwindSafe}; - -use crate::{ - abi::{Context, ModelMetadataResult, ShapeResult}, - models::Error, -}; - -#[repr(C)] -pub struct Model { - ptr: *mut c_void, - metadata: unsafe extern "C" fn(*mut c_void) -> ModelMetadataResult, - shape: unsafe extern "C" fn(*mut c_void, Context<'_>) -> ShapeResult, - free: unsafe extern "C" fn(*mut c_void), -} - -impl crate::models::Model for Model { - fn shape( - &self, - ctx: &dyn crate::models::Context, - ) -> Result { - let ctx = Context::from(&ctx); - - let Self { ptr, shape, .. } = *self; - - let result = unsafe { shape(ptr, ctx) }; - - match result { - super::ffi_safe::Result::Ok(shape) => Ok(shape), - super::ffi_safe::Result::Err(err) => Err(err.into()), - } - } - - fn metadata(&self) -> Result { - let Self { ptr, metadata, .. } = *self; - - let result = unsafe { metadata(ptr) }; - - match result { - super::ffi_safe::Result::Ok(meta) => Ok(meta.into()), - super::ffi_safe::Result::Err(err) => Err(err.into()), - } - } -} - -impl From> for Model { - fn from(m: Box) -> Self { - unsafe extern "C" fn metadata( - user_data: *mut c_void, - ) -> ModelMetadataResult { - let model = &*(user_data as *mut Box); - - match std::panic::catch_unwind(AssertUnwindSafe(|| { - model.metadata() - })) { - Ok(Ok(meta)) => ModelMetadataResult::Ok(meta.into()), - Ok(Err(err)) => ModelMetadataResult::Err(err.into()), - Err(payload) => { - ModelMetadataResult::Err(crate::abi::ffi_safe::BoxedError { - msg: crate::abi::on_panic(payload), - }) - } - } - } - - unsafe extern "C" fn shape( - user_data: *mut c_void, - ctx: Context<'_>, - ) -> ShapeResult { - let model = &*(user_data as *mut Box); - - match std::panic::catch_unwind(AssertUnwindSafe(|| { - model.shape(&ctx) - })) { - Ok(Ok(shape)) => ShapeResult::Ok(shape), - Ok(Err(err)) => ShapeResult::Err(err.into()), - Err(payload) => { - ShapeResult::Err(crate::abi::ffi_safe::BoxedError { - msg: crate::abi::on_panic(payload), - }) - } - } - } - - unsafe extern "C" fn free(user_data: *mut c_void) { - let model = user_data as *mut Box; - - if let Err(e) = std::panic::catch_unwind(AssertUnwindSafe(|| { - let model = Box::from_raw(model); - drop(model); - })) { - crate::abi::on_panic(e); - }; - } - - Self { - ptr: Box::into_raw(Box::new(m)).cast(), - metadata, - shape, - free, - } - } -} - -impl Drop for Model { - fn drop(&mut self) { - let Self { ptr, free, .. } = *self; - - unsafe { - free(ptr); - } - } -} - -// Safety: Our Model type is a FFI-safe version of Box, and -// Box: Send+Sync. -unsafe impl Send for Model {} -unsafe impl Sync for Model {} diff --git a/crates/fj/src/angle.rs b/crates/fj/src/angle.rs deleted file mode 100644 index 807a9cccf..000000000 --- a/crates/fj/src/angle.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::f64::consts::{PI, TAU}; - -// One gon in radians -const GON_RAD: f64 = PI / 200.; - -/// An angle -#[derive(Copy, Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Angle { - /// The value of the angle in radians - rad: f64, -} - -impl Angle { - /// Create a new angle specified in radians - pub fn from_rad(rad: f64) -> Self { - Self { rad } - } - /// Create a new angle specified in degrees - pub fn from_deg(deg: f64) -> Self { - Self::from_rad(deg.to_radians()) - } - /// Create a new angle specified in [revolutions](https://en.wikipedia.org/wiki/Turn_(angle)) - pub fn from_rev(rev: f64) -> Self { - Self::from_rad(rev * TAU) - } - /// Create a new angle specified in [gon](https://en.wikipedia.org/wiki/Gradian) - pub fn from_gon(gon: f64) -> Self { - Self::from_rad(gon * GON_RAD) - } - /// Retrieve value of angle as radians - pub fn rad(&self) -> f64 { - self.rad - } - /// Retrieve value of angle as degrees - pub fn deg(&self) -> f64 { - self.rad.to_degrees() - } - /// Retrieve value of angle as [revolutions](https://en.wikipedia.org/wiki/Turn_(angle)) - pub fn rev(&self) -> f64 { - self.rad / TAU - } - /// Retrieve value of angle as [gon](https://en.wikipedia.org/wiki/Gradian) - pub fn gon(&self) -> f64 { - self.rad / GON_RAD - } - - /// Returns this angle normalized to the range [0, 2pi) radians - pub fn normalized(&self) -> Self { - Self { - rad: Self::wrap(self.rad), - } - } - - // ensures that the angle is always 0 <= a < 2*pi - fn wrap(rad: f64) -> f64 { - let modulo = rad % TAU; - if modulo < 0. { - TAU + modulo - } else { - modulo - } - } -} - -impl std::ops::Add for Angle { - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - Self::from_rad(self.rad + rhs.rad) - } -} - -impl std::ops::AddAssign for Angle { - fn add_assign(&mut self, rhs: Self) { - self.rad += rhs.rad; - } -} - -impl std::ops::Sub for Angle { - type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - Self::from_rad(self.rad - rhs.rad) - } -} - -impl std::ops::SubAssign for Angle { - fn sub_assign(&mut self, rhs: Self) { - self.rad -= rhs.rad; - } -} - -impl std::ops::Mul for Angle { - type Output = Self; - fn mul(self, rhs: f64) -> Self::Output { - Self::from_rad(self.rad * rhs) - } -} - -impl std::ops::Mul for f64 { - type Output = Angle; - fn mul(self, rhs: Angle) -> Self::Output { - rhs * self - } -} - -impl std::ops::MulAssign for Angle { - fn mul_assign(&mut self, rhs: f64) { - self.rad *= rhs; - } -} - -impl std::ops::Div for Angle { - type Output = Self; - fn div(self, rhs: f64) -> Self::Output { - Self::from_rad(self.rad / rhs) - } -} - -impl std::ops::DivAssign for Angle { - fn div_assign(&mut self, rhs: f64) { - self.rad /= rhs; - } -} - -impl std::ops::Div for Angle { - type Output = f64; - fn div(self, rhs: Self) -> Self::Output { - self.rad / rhs.rad - } -} diff --git a/crates/fj/src/group.rs b/crates/fj/src/group.rs deleted file mode 100644 index 832b82104..000000000 --- a/crates/fj/src/group.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::Shape; - -/// A group of two 3-dimensional shapes -/// -/// A group is a collection of disjoint shapes. It is not a union, in that the -/// shapes in the group are not allowed to touch or overlap. -/// -/// # Examples -/// -/// Convenient syntax for this operation is available through [`crate::syntax`]. -/// -/// ``` rust -/// # let a = fj::Sketch::from_points(vec![[0., 0.], [1., 0.], [0., 1.]]).unwrap(); -/// # let b = fj::Sketch::from_points(vec![[2., 0.], [3., 0.], [2., 1.]]).unwrap(); -/// use fj::syntax::*; -/// -/// // `a` and `b` can be anything that converts to `fj::Shape` -/// let group = a.group(&b); -/// ``` -/// -/// # Limitations -/// -/// Whether the shapes in the group touch or overlap is not currently checked. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Group { - /// The first of the shapes - pub a: Shape, - - /// The second of the shapes - pub b: Shape, -} - -impl From for Shape { - fn from(shape: Group) -> Self { - Self::Group(Box::new(shape)) - } -} diff --git a/crates/fj/src/lib.rs b/crates/fj/src/lib.rs deleted file mode 100644 index eba71f6ad..000000000 --- a/crates/fj/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! # Fornjot Modeling Library -//! -//! This library is part of the [Fornjot] ecosystem. Fornjot is an open-source, -//! code-first CAD application; and collection of libraries that make up the CAD -//! application, but can be used independently. -//! -//! The purpose of this library is to support Fornjot models, which are just -//! Rust libraries. Models depend on this library and use the primitives defined -//! here to define a CAD model. Together with the Fornjot application, this -//! library forms the part of Fornjot that is relevant to end users. -//! -//! To display the created CAD model, or export it to another file format, you -//! need the Fornjot application. Please refer to the [Fornjot repository] for -//! usage examples. -//! -//! [Fornjot]: https://www.fornjot.app/ -//! [Fornjot repository]: https://github.com/hannobraun/Fornjot - -#![warn(missing_docs)] - -pub mod syntax; - -#[doc(hidden)] -pub mod abi; -mod angle; -mod group; -pub mod models; -mod shape_2d; -mod sweep; -mod transform; -pub mod version; - -pub use self::{ - angle::*, group::Group, shape_2d::*, sweep::Sweep, transform::Transform, -}; -pub use fj_proc::*; - -/// A shape -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub enum Shape { - /// A group of two 3-dimensional shapes - Group(Box), - - /// A 2D shape - Shape2d(Shape2d), - - /// A sweep of 2-dimensional shape along a straight path - Sweep(Sweep), - - /// A transformed 3-dimensional shape - Transform(Box), -} diff --git a/crates/fj/src/models/context.rs b/crates/fj/src/models/context.rs deleted file mode 100644 index 332913d96..000000000 --- a/crates/fj/src/models/context.rs +++ /dev/null @@ -1,16 +0,0 @@ -/// Contextual information passed to a [`Model`][crate::models::Model] when it -/// is being initialized. -pub trait Context { - /// Get an argument that was passed to this model. - fn get_argument(&self, name: &str) -> Option<&str>; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn context_is_object_safe() { - let _: &dyn Context; - } -} diff --git a/crates/fj/src/models/host.rs b/crates/fj/src/models/host.rs deleted file mode 100644 index 9af499dc1..000000000 --- a/crates/fj/src/models/host.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::models::Model; - -/// An abstract interface to the Fornjot host. -pub trait Host { - /// Register a model. - /// - /// This is mainly for more advanced use cases (e.g. when you need to close - /// over extra state to load the model). For simpler models, you probably - /// want to use [`HostExt::register_model()`] instead. - fn register_boxed_model(&mut self, model: Box); -} - -/// Extension methods to augment the [`Host`] API. -/// -/// The purpose of this trait is to keep [`Host`] object-safe. -pub trait HostExt { - /// Register a model with the Fornjot runtime. - fn register_model(&mut self, model: M) - where - M: Model + 'static; -} - -impl HostExt for H { - fn register_model(&mut self, model: M) - where - M: Model + 'static, - { - self.register_boxed_model(Box::new(model)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn host_is_object_safe() { - let _: &dyn Host; - } -} diff --git a/crates/fj/src/models/metadata.rs b/crates/fj/src/models/metadata.rs deleted file mode 100644 index 0ce58c0e5..000000000 --- a/crates/fj/src/models/metadata.rs +++ /dev/null @@ -1,232 +0,0 @@ -/// Information about a particular module that can be used by the host for -/// things like introspection and search. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Metadata { - /// A short, human-friendly name used to identify this module. - pub name: String, - - /// A semver-compliant version number. - pub version: String, - - /// A short, one-line description. - pub short_description: Option, - - /// A more elaborate description. - pub description: Option, - - /// A link to the homepage. - pub homepage: Option, - - /// A link to the source code. - pub repository: Option, - - /// The name of the software license(s) this software is released under. - /// - /// This is interpreted as a SPDX license expression (e.g. `MIT OR - /// Apache-2.0`). See [the SPDX site][spdx] for more information. - /// - /// [spdx]: https://spdx.dev/spdx-specification-21-web-version/#h.jxpfx0ykyb60 - pub license: Option, -} - -impl Metadata { - /// Create a [`Metadata`] object with the bare minimum required fields. - /// - /// # Panics - /// - /// The `name` and `version` fields must not be empty. - pub fn new(name: impl Into, version: impl Into) -> Self { - let name = name.into(); - assert!(!name.is_empty()); - let version = version.into(); - assert!(!version.is_empty()); - - Self { - name, - version, - short_description: None, - description: None, - homepage: None, - repository: None, - license: None, - } - } - - /// Set the [`Metadata::short_description`] field. - pub fn with_short_description( - self, - short_description: impl Into, - ) -> Self { - let short_description = short_description.into(); - if short_description.is_empty() { - return self; - } - - Self { - short_description: Some(short_description), - ..self - } - } - - /// Set the [`Metadata::description`] field. - pub fn with_description(self, description: impl Into) -> Self { - let description = description.into(); - if description.is_empty() { - return self; - } - - Self { - description: Some(description), - ..self - } - } - - /// Set the [`Metadata::homepage`] field. - pub fn with_homepage(self, homepage: impl Into) -> Self { - let homepage = homepage.into(); - if homepage.is_empty() { - return self; - } - - Self { - homepage: Some(homepage), - ..self - } - } - - /// Set the [`Metadata::repository`] field. - pub fn with_repository(self, repository: impl Into) -> Self { - let repository = repository.into(); - if repository.is_empty() { - return self; - } - - Self { - repository: Some(repository), - ..self - } - } - - /// Set the [`Metadata::license`] field. - pub fn with_license(self, license: impl Into) -> Self { - let license = license.into(); - if license.is_empty() { - return self; - } - - Self { - license: Some(license), - ..self - } - } -} - -/// Metadata about a [`crate::models::Model`]. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ModelMetadata { - /// A short, human-friendly name used to identify this model. - pub name: String, - - /// A description of what this model does. - pub description: Option, - - /// Arguments that the model uses when calculating its geometry. - pub arguments: Vec, -} - -impl ModelMetadata { - /// Create metadata for a model. - /// - /// # Panics - /// - /// The `name` must not be empty. - pub fn new(name: impl Into) -> Self { - let name = name.into(); - assert!(!name.is_empty()); - - Self { - name, - description: None, - arguments: Vec::new(), - } - } - - /// Set the [`ModelMetadata::description`]. - pub fn with_description(self, description: impl Into) -> Self { - let description = description.into(); - if description.is_empty() { - return self; - } - - Self { - description: Some(description), - ..self - } - } - - /// Add an argument to the [`ModelMetadata::arguments`] list. - /// - /// As a convenience, string literals can be automatically converted into - /// [`ArgumentMetadata`] with no description or default value. - pub fn with_argument(mut self, arg: impl Into) -> Self { - self.arguments.push(arg.into()); - self - } -} - -/// Metadata describing a model's argument. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ArgumentMetadata { - /// The name used to refer to this argument. - pub name: String, - - /// A short description of this argument that could be shown to the user - /// in something like a tooltip. - pub description: Option, - - /// Something that could be used as a default if no value was provided. - pub default_value: Option, -} - -impl ArgumentMetadata { - /// Create a new [`ArgumentMetadata`]. - /// - /// # Panics - /// - /// The `name` must not be empty. - pub fn new(name: impl Into) -> Self { - let name = name.into(); - assert!(!name.is_empty()); - Self { - name, - description: None, - default_value: None, - } - } - - /// Set the [`ArgumentMetadata::description`]. - pub fn with_description(mut self, description: impl Into) -> Self { - let description = description.into(); - if description.is_empty() { - return self; - } - - self.description = Some(description); - self - } - - /// Set the [`ArgumentMetadata::default_value`]. - pub fn with_default_value( - mut self, - default_value: impl Into, - ) -> Self { - self.default_value = Some(default_value.into()); - self - } -} - -impl From<&str> for ArgumentMetadata { - fn from(name: &str) -> Self { - Self::new(name) - } -} diff --git a/crates/fj/src/models/mod.rs b/crates/fj/src/models/mod.rs deleted file mode 100644 index 904bf53ed..000000000 --- a/crates/fj/src/models/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Interfaces used when defining models. - -mod context; -mod host; -mod metadata; -mod model; - -pub use self::{ - context::Context, - host::{Host, HostExt}, - metadata::{ArgumentMetadata, Metadata, ModelMetadata}, - model::Model, -}; - -/// A generic error used when defining a model. -pub type Error = Box; diff --git a/crates/fj/src/models/model.rs b/crates/fj/src/models/model.rs deleted file mode 100644 index 6a2e818e2..000000000 --- a/crates/fj/src/models/model.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{ - models::{Context, Error, ModelMetadata}, - Shape, -}; - -/// A model. -pub trait Model: Send + Sync { - /// Calculate this model's concrete geometry. - fn shape(&self, ctx: &dyn Context) -> Result; - - /// Get metadata for the model. - fn metadata(&self) -> Result; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn model_is_object_safe() { - let _: &dyn Model; - } -} diff --git a/crates/fj/src/shape_2d.rs b/crates/fj/src/shape_2d.rs deleted file mode 100644 index a448a9bb0..000000000 --- a/crates/fj/src/shape_2d.rs +++ /dev/null @@ -1,259 +0,0 @@ -use crate::{abi::ffi_safe, Angle, Shape}; - -/// A 2-dimensional shape -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub enum Shape2d { - /// A difference between two shapes - Difference(Box), - - /// A sketch - Sketch(Sketch), -} - -impl Shape2d { - /// Get the rendering color of the larger object in RGBA - pub fn color(&self) -> [u8; 4] { - match &self { - Self::Sketch(s) => s.color(), - Self::Difference(d) => d.color(), - } - } -} - -/// A difference between two shapes -/// -/// # Examples -/// -/// Convenient syntax for this operation is available through [`crate::syntax`]. -/// -/// ``` rust -/// # let a = fj::Sketch::from_points(vec![[0., 0.], [1., 0.], [0., 1.]]).unwrap(); -/// # let b = fj::Sketch::from_points(vec![[2., 0.], [3., 0.], [2., 1.]]).unwrap(); -/// use fj::syntax::*; -/// -/// // `a` and `b` can be anything that converts to `fj::Shape2d` -/// let difference = a.difference(&b); -/// ``` -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Difference2d { - shapes: [Shape2d; 2], -} - -impl Difference2d { - /// Create a `Difference2d` from two shapes - pub fn from_shapes(shapes: [Shape2d; 2]) -> Self { - Self { shapes } - } - - /// Get the rendering color of the larger object in RGBA - pub fn color(&self) -> [u8; 4] { - self.shapes[0].color() - } - - /// Access the shapes that make up the difference - pub fn shapes(&self) -> &[Shape2d; 2] { - &self.shapes - } -} - -impl From for Shape { - fn from(shape: Difference2d) -> Self { - Self::Shape2d(shape.into()) - } -} - -impl From for Shape2d { - fn from(shape: Difference2d) -> Self { - Self::Difference(Box::new(shape)) - } -} - -/// A sketch -/// -/// Sketches are currently limited to a single cycle of straight lines, -/// represented by a number of points. For example, if the points a, b, and c -/// are provided, the edges ab, bc, and ca are assumed. -/// -/// Nothing about these edges is checked right now, but algorithms might assume -/// that the edges are non-overlapping. If you create a `Sketch` with -/// overlapping edges, you're on your own. -/// -/// # Examples -/// -/// Convenient syntax for this operation is available through [`crate::syntax`]. -/// -/// ``` rust -/// use fj::syntax::*; -/// -/// // `a` and `b` can be anything that converts to `fj::Shape` -/// let sketch = [[0., 0.], [1., 0.], [0., 1.]].sketch(); -/// ``` -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Sketch { - chain: Chain, - color: [u8; 4], -} - -impl Sketch { - /// Create a sketch made of sketch segments - pub fn from_segments(segments: Vec) -> Option { - // TODO Returning an option is just a temporary solution, see: https://github.com/hannobraun/Fornjot/issues/1507 - if segments.is_empty() { - None - } else { - Some(Self { - chain: Chain::PolyChain(PolyChain::from_segments(segments)), - color: [255, 0, 0, 255], - }) - } - } - - /// Create a sketch made of straight lines from a bunch of points - pub fn from_points(points: Vec<[f64; 2]>) -> Option { - if points.is_empty() { - // TODO Returning an option is just a temporary solution, see: https://github.com/hannobraun/Fornjot/issues/1507 - None - } else { - Some(Self { - chain: Chain::PolyChain(PolyChain::from_points(points)), - color: [255, 0, 0, 255], - }) - } - } - - /// Create a sketch from a circle - pub fn from_circle(circle: Circle) -> Self { - Self { - chain: Chain::Circle(circle), - color: [255, 0, 0, 255], - } - } - - /// Set the rendering color of the sketch in RGBA - pub fn with_color(mut self, color: [u8; 4]) -> Self { - self.color = color; - self - } - - /// Access the chain of the sketch - pub fn chain(&self) -> &Chain { - &self.chain - } - - /// Get the rendering color of the sketch in RGBA - pub fn color(&self) -> [u8; 4] { - self.color - } -} - -impl From for Shape { - fn from(shape: Sketch) -> Self { - Self::Shape2d(shape.into()) - } -} - -impl From for Shape2d { - fn from(shape: Sketch) -> Self { - Self::Sketch(shape) - } -} - -/// A chain of elements that is part of a [`Sketch`] -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub enum Chain { - /// The chain is a circle - Circle(Circle), - - /// The chain is a polygonal chain - PolyChain(PolyChain), -} - -/// A circle that is part of a [`Sketch`] -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Circle { - /// The radius of the circle - radius: f64, -} - -impl Circle { - /// Construct a new circle with a specific radius - pub fn from_radius(radius: f64) -> Self { - Self { radius } - } - - /// Access the circle's radius - pub fn radius(&self) -> f64 { - self.radius - } -} - -/// A polygonal chain that is part of a [`Sketch`] -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct PolyChain { - segments: ffi_safe::Vec, -} - -impl PolyChain { - /// Construct an instance from a list of segments - pub fn from_segments(segments: Vec) -> Self { - Self { - segments: segments.into(), - } - } - - /// Construct an instance from a list of points - pub fn from_points(points: Vec<[f64; 2]>) -> Self { - let segments = points - .into_iter() - .map(|endpoint| SketchSegment { - endpoint, - route: SketchSegmentRoute::Direct, - }) - .collect(); - Self::from_segments(segments) - } - - /// Return the points that define the polygonal chain - pub fn to_segments(&self) -> Vec { - self.segments.clone().into() - } -} - -/// A segment of a sketch -/// -/// Each segment starts at the previous point of the sketch. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct SketchSegment { - /// The destination point of the segment - pub endpoint: [f64; 2], - /// The path taken by the segment to get to the endpoint - pub route: SketchSegmentRoute, -} - -/// Possible paths that a [`SketchSegment`] can take to the next endpoint -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub enum SketchSegmentRoute { - /// A straight line to the endpoint - Direct, - /// An arc to the endpoint with a given angle - Arc { - /// The angle of the arc - angle: Angle, - }, -} diff --git a/crates/fj/src/sweep.rs b/crates/fj/src/sweep.rs deleted file mode 100644 index 18f129148..000000000 --- a/crates/fj/src/sweep.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{Shape, Shape2d}; - -/// A sweep of a 2-dimensional shape along straight path -/// -/// # Examples -/// -/// Convenient syntax for this operation is available through [`crate::syntax`]. -/// -/// ``` rust -/// # let shape = fj::Sketch::from_points(vec![[0., 0.], [1., 0.], [0., 1.]]).unwrap(); -/// use fj::syntax::*; -/// -/// // `shape` can be anything that converts to `fj::Shape2d` -/// let group = shape.sweep([0., 0., 1.]); -/// ``` -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Sweep { - /// The 2-dimensional shape being swept - shape: Shape2d, - - /// The length and direction of the sweep - path: [f64; 3], -} - -impl Sweep { - /// Create a `Sweep` along a straight path - pub fn from_path(shape: Shape2d, path: [f64; 3]) -> Self { - Self { shape, path } - } - - /// Access the shape being swept - pub fn shape(&self) -> &Shape2d { - &self.shape - } - - /// Access the path of the sweep - pub fn path(&self) -> [f64; 3] { - self.path - } -} - -impl From for Shape { - fn from(shape: Sweep) -> Self { - Self::Sweep(shape) - } -} diff --git a/crates/fj/src/syntax.rs b/crates/fj/src/syntax.rs deleted file mode 100644 index 662cbbeee..000000000 --- a/crates/fj/src/syntax.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Convenient syntax for `fj` operations -//! -//! This model defines extension traits, which provide convenient syntax for -//! the various operations defined in this trait. - -/// Convenient syntax to create an [`fj::Difference2d`] -/// -/// [`fj::Difference2d`]: crate::Difference2d -pub trait Difference { - /// Create a difference between `self` and `other` - fn difference(&self, other: &Other) -> crate::Difference2d - where - Other: Clone + Into; -} - -impl Difference for T -where - T: Clone + Into, -{ - fn difference(&self, other: &Other) -> crate::Difference2d - where - Other: Clone + Into, - { - let a = self.clone().into(); - let b = other.clone().into(); - - crate::Difference2d::from_shapes([a, b]) - } -} - -/// Convenient syntax to create an [`fj::Group`] -/// -/// [`fj::Group`]: crate::Group -pub trait Group { - /// Create a group with `self` and `other` - fn group(&self, other: &Other) -> crate::Group - where - Other: Clone + Into; -} - -impl Group for T -where - T: Clone + Into, -{ - fn group(&self, other: &Other) -> crate::Group - where - Other: Clone + Into, - { - let a = self.clone().into(); - let b = other.clone().into(); - - crate::Group { a, b } - } -} - -/// Convenient syntax to create an [`fj::Sketch`] -/// -/// [`fj::Sketch`]: crate::Sketch -pub trait Sketch { - /// Create a sketch from `self` - /// - /// Can be called on any type that implements `AsRef<[[f64; 2]]`, which is - /// implemented for types like slices, arrays, or `Vec`. - fn sketch(&self) -> crate::Sketch; -} - -impl Sketch for T -where - T: AsRef<[[f64; 2]]>, -{ - fn sketch(&self) -> crate::Sketch { - crate::Sketch::from_points(self.as_ref().to_vec()).unwrap() - } -} - -/// Convenient syntax to create an [`fj::Sweep`] -/// -/// [`fj::Sweep`]: crate::Sweep -pub trait Sweep { - /// Sweep `self` along a straight path - fn sweep(&self, path: [f64; 3]) -> crate::Sweep; -} - -impl Sweep for T -where - T: Clone + Into, -{ - fn sweep(&self, path: [f64; 3]) -> crate::Sweep { - let shape = self.clone().into(); - crate::Sweep::from_path(shape, path) - } -} - -/// Convenient syntax to create an [`fj::Transform`] -/// -/// [`fj::Transform`]: crate::Transform -pub trait Transform { - /// Create a rotation - /// - /// Create a rotation that rotates `shape` by `angle` around an axis defined - /// by `axis`. - fn rotate(&self, axis: [f64; 3], angle: crate::Angle) -> crate::Transform; - - /// Create a translation - /// - /// Create a translation that translates `shape` by `offset`. - fn translate(&self, offset: [f64; 3]) -> crate::Transform; -} - -impl Transform for T -where - T: Clone + Into, -{ - fn rotate(&self, axis: [f64; 3], angle: crate::Angle) -> crate::Transform { - let shape = self.clone().into(); - crate::Transform { - shape, - axis, - angle, - offset: [0.; 3], - } - } - - fn translate(&self, offset: [f64; 3]) -> crate::Transform { - let shape = self.clone().into(); - crate::Transform { - shape, - axis: [1., 0., 0.], - angle: crate::Angle::from_rad(0.), - offset, - } - } -} diff --git a/crates/fj/src/transform.rs b/crates/fj/src/transform.rs deleted file mode 100644 index d22b84548..000000000 --- a/crates/fj/src/transform.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{Angle, Shape}; - -/// A transformed 3-dimensional shape -/// -/// # Examples -/// -/// Convenient syntax for this operation is available through [`crate::syntax`]. -/// -/// ``` rust -/// # let shape = fj::Sketch::from_points(vec![[0., 0.], [1., 0.], [0., 1.]]).unwrap(); -/// use fj::syntax::*; -/// -/// // `shape` can be anything that converts to `fj::Shape` -/// let rotated = shape.rotate([0., 0., 1.], fj::Angle::from_rev(0.5)); -/// let translated = shape.translate([1., 2., 3.]); -/// ``` -/// -/// # Limitations -/// -/// Transformations are currently limited to a rotation, followed by a -/// translation. -/// -/// See issue: -/// -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[repr(C)] -pub struct Transform { - /// The shape being transformed - pub shape: Shape, - - /// The axis of the rotation - pub axis: [f64; 3], - - /// The angle of the rotation - pub angle: Angle, - - /// The offset of the translation - pub offset: [f64; 3], -} - -impl From for Shape { - fn from(shape: Transform) -> Self { - Self::Transform(Box::new(shape)) - } -} diff --git a/crates/fj/src/version.rs b/crates/fj/src/version.rs deleted file mode 100644 index 23ebaf389..000000000 --- a/crates/fj/src/version.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! API for checking compatibility between the Fornjot app and a model - -use std::{fmt, slice}; - -/// The Fornjot package version -/// -/// Can be used to check for compatibility between a model and the Fornjot app -/// that runs it. -/// -/// This is just the version specified in the Cargo package, which will stay -/// constant between releases, even though changes are made throughout. A match -/// of this version does not conclusively determine that the app and a model are -/// compatible. -#[no_mangle] -pub static VERSION_PKG: Version = - Version::from_static_str(env!("FJ_VERSION_PKG")); - -/// The full Fornjot version -/// -/// Can be used to check for compatibility between a model and the Fornjot app -/// that runs it. -#[no_mangle] -pub static VERSION_FULL: Version = - Version::from_static_str(env!("FJ_VERSION_FULL")); - -/// C-ABI-compatible representation of a version -/// -/// Used by the Fornjot application to check for compatibility between a model -/// and the app. -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct Version { - ptr: *const u8, - len: usize, -} - -impl Version { - const fn from_static_str(s: &'static str) -> Self { - Self { - ptr: s.as_ptr(), - len: s.len(), - } - } -} - -impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // This is sound. We only ever create `ptr` and `len` from static - // strings. - let slice = unsafe { slice::from_raw_parts(self.ptr, self.len) }; - - write!(f, "{}", String::from_utf8_lossy(slice).into_owned()) - } -} - -// The only reason this is not derived automatically, is that `Version` contains -// a `*const u8`. `Version` can still safely be `Send`, for the following -// reasons: -// - The field is private, and no code in this module uses it for any write -// access, un-synchronized or not. -// - `Version` can only be constructed from strings with a static lifetime, so -// it's guaranteed that the pointer is valid over the whole lifetime of the -// program. -unsafe impl Send for Version {} - -// There is no reason why a `&Version` wouldn't be `Send`, so per definition, -// `Version` can be `Sync`. -unsafe impl Sync for Version {} diff --git a/justfile b/justfile index 7b0f2fb0f..02d42fc60 100644 --- a/justfile +++ b/justfile @@ -9,7 +9,7 @@ export RUSTDOCFLAGS := "-D warnings" # For a full build that mirrors the CI build, see `just ci`. test: cargo test --all-features - cargo run --package export-validator + # cargo run --package export-validator # Run a full build that mirrors the CI build # diff --git a/models/cuboid/Cargo.toml b/models/cuboid/Cargo.toml deleted file mode 100644 index 42746d61c..000000000 --- a/models/cuboid/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "cuboid" -version = "0.1.0" -edition = "2021" - -[dependencies.fj] -path = "../../crates/fj" diff --git a/models/cuboid/README.md b/models/cuboid/README.md deleted file mode 100644 index 00be43e16..000000000 --- a/models/cuboid/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Fornjot - Cuboid - -A model of a simple cuboid that demonstrates sweeping a 3D shape from a primitive with multiple connected edges. - -To display this model, run the following from the repository root (model parameters are optional): -``` sh -cargo run -- cuboid --parameters x=3.0,y=2.0,z=1.0 -``` - -![Screenshot of the cuboid model](cuboid.png) diff --git a/models/cuboid/cuboid.png b/models/cuboid/cuboid.png deleted file mode 100644 index 0e4cc8d25f2fe53af41cb864fc17d72493ef1b2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63874 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 diff --git a/models/cuboid/src/lib.rs b/models/cuboid/src/lib.rs deleted file mode 100644 index 6c8767664..000000000 --- a/models/cuboid/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[fj::model] -pub fn model( - #[param(default = 3.0)] x: f64, - #[param(default = 2.0)] y: f64, - #[param(default = 1.0)] z: f64, -) -> fj::Shape { - #[rustfmt::skip] - let rectangle = fj::Sketch::from_points(vec![ - [-x / 2., -y / 2.], - [ x / 2., -y / 2.], - [ x / 2., y / 2.], - [-x / 2., y / 2.], - ]).unwrap().with_color([100,255,0,200]); - - let cuboid = fj::Sweep::from_path(rectangle.into(), [0., 0., z]); - - cuboid.into() -} diff --git a/models/spacer/Cargo.toml b/models/spacer/Cargo.toml deleted file mode 100644 index a83c4c299..000000000 --- a/models/spacer/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "spacer" -version = "0.1.0" -edition = "2021" - -[dependencies.fj] -path = "../../crates/fj" diff --git a/models/spacer/README.md b/models/spacer/README.md deleted file mode 100644 index 0a9a4ff44..000000000 --- a/models/spacer/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Fornjot - Spacer - -A simple spacer model that demonstrates the circle primitive, the difference operation, and sweeping that into a 3-dimensional shape. - -To display this model, run the following from the repository root (model parameters are optional): -``` sh -cargo run -- spacer --parameters outer=1.0,inner=0.5,height=1.0 -``` - -![Screenshot of the spacer model](spacer.png) diff --git a/models/spacer/spacer.png b/models/spacer/spacer.png deleted file mode 100644 index 936a01a02a41dd6b0c2ae6a30b0d2d1476813950..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75170 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 diff --git a/models/spacer/src/lib.rs b/models/spacer/src/lib.rs deleted file mode 100644 index 98724904d..000000000 --- a/models/spacer/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -use fj::syntax::*; - -#[fj::model] -pub fn model( - #[param(default = 1.0, min = inner * 1.01)] outer: f64, - #[param(default = 0.5, max = outer * 0.99)] inner: f64, - #[param(default = 1.0)] height: f64, -) -> fj::Shape { - let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer)); - let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner)); - - let footprint = outer_edge.difference(&inner_edge); - let spacer = footprint.sweep([0., 0., height]); - - spacer.into() -} diff --git a/models/star/Cargo.toml b/models/star/Cargo.toml deleted file mode 100644 index 0279bef41..000000000 --- a/models/star/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "star" -version = "0.1.0" -edition = "2021" - -[dependencies.fj] -path = "../../crates/fj" diff --git a/models/star/README.md b/models/star/README.md deleted file mode 100644 index 27762a401..000000000 --- a/models/star/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Fornjot - Star - -A model of a star that demonstrates sweeping a sketch to create a 3D shape, concave sketches, and how to generate sketches from code. - -To display this model, run the following from the repository root (model parameters are optional): -``` sh -cargo run -- star --parameters num_points=5,r1=1.0,r2=2.0,h=1.0 -``` - -![Screenshot of the star model](star.png) diff --git a/models/star/src/lib.rs b/models/star/src/lib.rs deleted file mode 100644 index beb626e8f..000000000 --- a/models/star/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::f64::consts::PI; - -#[fj::model] -pub fn model( - #[param(default = 5, min = 3)] num_points: u64, - #[param(default = 1.0, min = 1.0)] r1: f64, - #[param(default = 2.0, min = 2.0)] r2: f64, - #[param(default = 1.0)] h: f64, -) -> fj::Shape { - let num_vertices = num_points * 2; - let vertex_iter = (0..num_vertices).map(|i| { - let angle = - fj::Angle::from_rad(2. * PI / num_vertices as f64 * i as f64); - let radius = if i % 2 == 0 { r1 } else { r2 }; - (angle, radius) - }); - - // Now that we got that iterator prepared, generating the vertices is just a - // bit of trigonometry. - let mut outer = Vec::new(); - let mut inner = Vec::new(); - for (angle, radius) in vertex_iter { - let (sin, cos) = angle.rad().sin_cos(); - - let x = cos * radius; - let y = sin * radius; - - outer.push([x, y]); - inner.push([x / 2., y / 2.]); - } - - let outer = fj::Sketch::from_points(outer).unwrap(); - let inner = fj::Sketch::from_points(inner).unwrap(); - - let footprint = fj::Difference2d::from_shapes([outer.into(), inner.into()]); - - let star = fj::Sweep::from_path(footprint.into(), [0., 0., h]); - - star.into() -} diff --git a/models/star/star.png b/models/star/star.png deleted file mode 100644 index e667ef04ae964c959ab882770598a6f02bf20b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104154 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 diff --git a/models/test/Cargo.toml b/models/test/Cargo.toml deleted file mode 100644 index c70a94aa4..000000000 --- a/models/test/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "test" -version = "0.1.0" -edition = "2021" -description = "An example model" -homepage = "https://www.fornjot.app/" -repository = "https://github.com/hannobraun/fornjot" -license = "0BSD" - -[dependencies.fj] -path = "../../crates/fj" diff --git a/models/test/README.md b/models/test/README.md deleted file mode 100644 index a109afc31..000000000 --- a/models/test/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Fornjot - Test Model - -A model that tries to use all of Fornjot's features, to serve as a testing ground. - -To display this model, run the following from the repository root: -``` sh -cargo run -- test -``` - -![Screenshot of the test model](test.png) diff --git a/models/test/src/lib.rs b/models/test/src/lib.rs deleted file mode 100644 index 1aaa37bdc..000000000 --- a/models/test/src/lib.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::f64::consts::PI; - -use fj::{syntax::*, Angle}; - -#[fj::model] -pub fn model() -> fj::Shape { - let a = star(4, 1., [0, 255, 0, 200], Some(-30.)); - let b = star(5, -1., [255, 0, 0, 255], None) - .rotate([1., 1., 1.], Angle::from_deg(45.)) - .translate([3., 3., 1.]); - let c = spacer().translate([6., 6., 1.]); - - let group = a.group(&b).group(&c); - - group.into() -} - -fn star( - num_points: u64, - height: f64, - color: [u8; 4], - arm_angle: Option, -) -> fj::Shape { - let r1 = 1.; - let r2 = 2.; - - // We need to figure out where to generate vertices, depending on the number - // of points the star is supposed to have. Let's generate an iterator that - // gives us the angle and radius for each vertex. - let num_vertices = num_points * 2; - let vertex_iter = (0..num_vertices).map(|i| { - let angle = Angle::from_rad(2. * PI / num_vertices as f64 * i as f64); - let radius = if i % 2 == 0 { r1 } else { r2 }; - (angle, radius) - }); - - // Now that we got that iterator prepared, generating the vertices is just a - // bit of trigonometry. - let mut outer = Vec::new(); - let mut inner = Vec::new(); - for (angle, radius) in vertex_iter { - let (sin, cos) = angle.rad().sin_cos(); - - let x = cos * radius; - let y = sin * radius; - - if let Some(angle) = arm_angle { - outer.push(fj::SketchSegment { - endpoint: [x, y], - route: fj::SketchSegmentRoute::Arc { - angle: fj::Angle::from_deg(angle), - }, - }); - inner.push(fj::SketchSegment { - endpoint: [x / 2., y / 2.], - route: fj::SketchSegmentRoute::Arc { - angle: fj::Angle::from_deg(-angle), - }, - }); - } else { - outer.push(fj::SketchSegment { - endpoint: [x, y], - route: fj::SketchSegmentRoute::Direct, - }); - inner.push(fj::SketchSegment { - endpoint: [x / 2., y / 2.], - route: fj::SketchSegmentRoute::Direct, - }); - } - } - - let outer = fj::Sketch::from_segments(outer).unwrap().with_color(color); - let inner = fj::Sketch::from_segments(inner).unwrap(); - - let footprint = fj::Difference2d::from_shapes([outer.into(), inner.into()]); - - let star = fj::Sweep::from_path(footprint.into(), [0., 0., height]); - - star.into() -} - -fn spacer() -> fj::Shape { - let outer = 2.; - let inner = 1.; - let height = 2.; - - let outer_edge = fj::Sketch::from_circle(fj::Circle::from_radius(outer)) - .with_color([0, 0, 255, 255]); - let inner_edge = fj::Sketch::from_circle(fj::Circle::from_radius(inner)); - - let footprint = outer_edge.difference(&inner_edge); - let spacer = footprint.sweep([0., 0., height]); - - spacer.into() -} diff --git a/models/test/test.png b/models/test/test.png deleted file mode 100644 index 4b0f5c34ec56ff78341ba6ca1fad6646be1e9316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128331 zcmd?RX;jl^_ccr%xfMn0L><5?PKZDinFONcMjI4RnPk=~BJ(_iDGn7{tyob&<~jfZ zLIeb4jFciGLPUfJ0iq&Ah!Ey5C3*J`@ov5Q|M0B;TJQ7aVPz>K;Wu2@Is5Fr&k4UA zHaM_o!RiHKVq%NPKm7Zsn3&`oF)?vv@p;5E@f$ty-yE;+$;RUN3KBo{Gk*TYTlbi^ zk=to+zZ0HzV)m|XE_OS-PI}texq6*(^XAQ_YvWCui8twZ+MV!raC2R6?BHT2wo_^6 z&K*kXJ9erYf45U(=Pr$1JJ;*z8X3R8DN`gSwqA_ZZ=?Kg7O$GF#>K;wLJ9 z%I`WPKKomxC5Ikd=~!`6zWTzcG|QvBHj}o-y!Q0sTAARU^n*S6N27c6CW7h)27-CP zZ1!ZZZBkKiP#>i|^|$vXU*YQi_BHRQNAdi>{Y-qDb-!fb$IRxbKYbU^`tl{s+om+S zM*R|rFQ1;B$zBrF^Jjf;{Om7ZcqFl?{L5#Q9#1SPFST*o!};=gF$EnRr7xe7P&#+~ z%V(sH&-%yV=A8fXj*g%G*PJh};<4DW>HYreSZBZ5{Eu6>KlQYxeN_L;gNa!+Z9I3J z^52)CCfNHx^S`e)@tv>aU#FP<&MIc_f4}R0c~6ltlx9}hwl9x=w&vwOZsX&%(BpIe zdy(9g|94ALGH|;f!D!~B4|+pHBmsR~lG-n0v~~GVrrShHPzRIR=6e-6MSSg5_|MX7 zvP!wZJVp|AxZ^7Krekshd)!av_3d90HMSTeKBEb`+)|mvV`|-!Q^oA(+igZ)evEA{ zti3+7`tIcQ#}3S_+~Z|K!=loWj~|^$67jO7Hj24kxy`$G1y?Zg%E}TKE?g++&Cbq# z<&?K3MEcWZoIMcOKpG2|tsV|fo4U@MsG?NX*aS2Rctw*3$2vjg|;Po*h2zQi6}`l(amtJrp&xM z>y&SwRY(_y9eFkQ-F358&JcB|aw;P!(4HhwlJJb#KKIib-!yk;vP0}gKeqTZ1}~`m z!$`TdY4y5qsu{vRuf8XVqOYbL-rB_#);aCcRDRfYa%K%$4_~RTmzUqMiCom?8ZuBy zkMJyvH!B@n5jQ&dkt5O|@fXtLO*0y)Zl_MY-|oOQg3&WEgC{VJhAHMk*Pke~Lg)Xe>V`tPj$MOvVM`_eg!qeCCC>>g1_3B+OA z2m1K$=eMxq^pYd|N+z|sy*1*(i-#V*zsZ`;r@m%`Y zvG63#$X^Eb+Xy38aRSRaSgO4wP0e`8QqHP|ZtuY=(fG#k_x9S+*HaBrtrR+6=&N>S z(0d}sTbRMMJ_&w|K5GhZ54A7UhRqR9xD6VKx)QEZMAp1OYWqkTe+xN1QZIzVq>fzT z%xtQEj&Ra#tVp(cK4~gWz@OfuV0y$|_ZX%AI8RPNNq5@Xf$#nUPOiV>uErxJgVmD8 zhV4~K*^YU+-lxZTT3V8mp zwQxF+LnaQ03W6%V+~_@JJYhk!TTO8JP}#d2o~W{lS z+lFV$2s53e2a4$f<;xeY(z#C`*h4SgCLcIdK6|_iLGUp#rdsE~)h<7(NOCfXA5dDt zzd%p)6Al{=S3EFlIP+`gCdHtT(6G><7xgD8rYO{B~6%aX5-VVS|-dJ&!SFA8qpfF zoTB`M&ka+)Q)yy{)Mk|JWbyt)+SWyy8%3AK*7F}DJO*JsVtoa1|f-Zdmuiv|wz z4i>P2pW6c|=~F_V*`A;)}mPCCm| zMbxn_ecn3Chqq2yf>9oY>l1d4|FJ!98<2OzVuvEN`=Cmx-{AXhu7z8Lsg2Ccwi@-` zNg6xVKHkSqZlJvPd+}rT%^wG@eQ=>MYiLs~VMEvGV`IL*KKdo}_@`euyWPJ^B)(dSkr_gdXM`4&`=pRuh8t~_{-E|wl4JT!67;{ zo+oD{Zuh6-^e-Q)F$~e5_b1vmFQ2Ed@-k(T)gG%_GP-*e`@_g!qOIUCHR(#qo42=a zm2{cd7Op#)>fdWUa}AH8(yFWVS-D;ug<|&LpdLv+P8QY1QiFMA@X$1`#2eo z<1qO`e|~{;${L65G+OGqnK$}hir+(GQ?0iyA1q~$UsbtyPSr|cE|r;re5}%L{0pbf z#z{)QekkA}$8sM$c+_2Lka}IjB~1xa_+^7D6xoB#u0O4z1nZ34(q|0~t#W;FW9HtN z_Kuz#NMoba2*5kj)*}gPk}))!%`c6KjrU)$rMP5pqoS7Q%yt~lwaMZayoxRzyGW_a zEO=idxz-@!dBUD(s&FGI$}O-#6&O9=3k6o#AtNFK2`4A3N`$*5;ld zs%>y>hD{8!q$iSDDl*+>8#n_{Vp++Dx4xe&o7^*~LmFJx?ORNaXfE`k@aBgIawb(* zpB%3%XdO2WAG_Q7lpA+bih+xXo$RBoC-3wV1__(#W7mF_khG`RZQ=!U=yiS8yDYuF zojJXs4oG9j@lQXUt<9p#jNOiN)3_-kdMooxkuK6v5AOQ7f*;s?+4cr6kmYyU*?G9A ziCRME*tm*nd_>yq?QgO0Y>^>5N=BHS^z7xJr>L|uGE%nMmO9WEBv>P=+nq8~{{2T^ zd}fC3vrFGlY_HjlF-ej5WokSlSh!G~(sQV(iP<2vrZzLowa&?AByzlm7SQQ?b^NEc zdt6q5y1I|(|nB1vs%$`^{!&3ZLZ|0j48I}PGBBH5D_q#if6 z?}x6EcXoL$)ZH%_{^NW#j~?TYkEtU4rlzLrvUfIaq)hTlc8BN?+7Mq-Ydf8I zGX?7LE|XGI#=Cb%BThMcE0ZP^g{iGU{f|`)XV}2wNAI&dB2#ud1dbmvHkp%*rS{f zod=hW|L5oFZ=^I=|3eCCy{B z_2$k4^z4$7l678U?sG>1aELEnKfY!~LEsn90N$VdCsbx$`QIwm|C5#ZU&_t@=(w3n z{6CY>|8HN=b~nD5m}aSWaa+8)*XB%9C&bg)jMb}GKN}t%_PzS|*fNYo2s7;{6!lcY zq`HBQDzEoXuRUil-1t9;xtr|TwX3i@B`(K}UH1I>^Sd@Q;_1J>`hOCiGuvIirlw~6 z!vRw4B#%x?ns}8;ynU^j`|;wcS!XjQz%Xlq-fxttuCWykYVs(InhaY(4|sX6^bCug zKA^NJES9Y9;qHb)k#MrXS!Jk8m99QtQu_JyHP00@^BH_!tvUQcXS|i89ewN8ElrL( zqdHM5FhB4!a1#geZ*}!w|LE}6Q)kX>z4-mx{fdj+E0p7Ey)_bgr8^vMv3k4420F%H zrD{j2(#LBlZCsn^fq?C>k@KfJg#Rh=|NmJS(v+v-`fBj)DK*L~T4t*Y1?kHT-KPg7l8Z7dgwhj+1cZQSF> za*=x9tXrpF+-6W}!}#Nm?_+Dbe6Q|)V3BFs#o>6pd3^4Wp`jaJO|1XX@&y+moOTto z2RN-9{UaM%>d~~jSSKH!-CQnrwd%QZCTYe;Gfj=~Z$={?{{;?G z#|$-=SZ!TCQC`&8*ceZu+DJ)B`A6z)>geeBX3d&|c^3MGRtCjw?O)srQ0BW!hQ8;p zU&a}Y#C<$|+^|2R@!7;g0IoZ|(XnS_;y<>{%vDB@+?`zxDqZ>GcL@dua25-5TU*1_ z26FbXL)l|@>5Km!!D7b`{_yyVC&d0QW5hE;%rLBmhK34xWs`%Qid1!C-_wjzUpnT! z6~xl?BHbyb9{qJR0qox6AYRGe;3_xa*B@`=>6 zyxr`4+QDJlDg~>jEiD$lA75WfOH0F2%2wk?EMI6?n2|y>G+=~mMn(oX!nGh!hx0!l z>PJ^sg{ts%HE%1za*K*$QE0S^{djeM{`v2S_fz9T=H}*PtTFZx%N+5%u5L5gBC}~x zf_+BQ7ewH-NIg~WT9ES7xfC7doHr9YZ{KcFW|(IF9T&tN zH0JMEVkH-O6u*kN%rq^KT~dNX%gf9Al-FyDw8hK{a&jUNmROiW`ucXbO9$oEX~uHo zi2Zl2;R{d*`Txo>Pj(fZepQB)Slm~%eTzZ-51s?%OHZCW*_?gyfNR-!7s6Vb-yY}| zzE(XV*Pd}2xQHM$GBRqWQ>lxkHE&*+F9|w)pTx*(((>;=-qqFR$#3&Z1y=`RoWS;E zcgNO2JQVZF_8_ead-GX2C#%ALy>i7aBt#ns7)=0dymqk0<;$0^#&Ggkt~IT#R(+-X zc6eLUaFCeTr@HwI-`J`;f9`qiNWhb-Dk3}OR~Ecqn`g0OBtVOtgOb0ABlc!cFZrnG zgk-NTaaM;s3jtqs*5l~hM~`meHc}cLH<3wdCH3ztw=PFCPKu(O@0%bh@$&fiI94p# zBGXla(b&{G5+Lv$|Nho*t_71f3t=UhnwjP1=STS#C)m)UJ3Bk&vBxJ)eCy=o^o_K1 zUy_%%w=y6Axr#I6NplN}9=dA*OZAPx7k9w$W~~&@YIL+*JdF^BET zazRccUdJ%()5XkSV{qW%oKzPB$KTd{kU%mhrN5pkXN=k%P(9eH9efk6GjHUcaqfFCX z+S-Zr_4Qb6Ja9Mf--gN5y?l8T@QsBf@|0^+(CBD~K(738Am>DzX+|WBK07t3AN3+_PbP6^0zGs%7km-ii9)Ik(CqnrZSYyY`R1E} z&yK?Cnsa=dZoJ^?6&u@$Hx4rF8(P_%B%54M80~!@EL<7?F1rf z2pGoeB>56h5+D&U02hjX5%QgH$}cn*`?*C7U&vciJ_V?gY9!SBdb8M(Q&tqj*hxl*CC?n_q2F zX2TifiEk$kaD9sf>s?bDx~aCbtSkg&a+|9?g+ejfHI>)Y>#bqBB3<{wN4)WWs=WhtYMEmd0t!FSs^3rUuI+f z=_>7XFZjT<)#4l{qBcmAt<%jBcguU;ANKX4$| zkJm1*pl}*lNAS@4I|sbR`ri`i8D;5Oz0R-onnk8>?e4%*8&gwLPXa;JJT?G930@F^ zYwkoA8{9hl6fs%U)@N$_qi=6Zq(N$fqz1LeDCybz_os-;*@o?Q+&d$xKgFZ}hQy`k z-Voap6B9#@0AKDRXXe!#sFO%ZM2!Hf0cJsQ)ZO3gee&eVP7RG%&@x2Pmf*52P8)|+ zzWc7;?XLXt1qk<;`1n^6L5uoioHLPQcz`F$0s#Yc>6quth*l_+$dpdZH{K0z(cxrJ*omq2cSbx43yIMCzGa zTh|i5i0E3Y?kQV)I+rb5w2m*C*!!&0lm1Jvv$H&oH2!`IDF&5M=*P;7*QD%}Bu%*+ zBx=Us9#@sTnF}M%9x1P2_2@SuM(okV%d-f!%5@3aBD>1+XWEwdc+HpRt!T_#sz^xW z1`t!-I#QBgtc%4KAN&0dPq5kSTw9q<=?HLqkJMVq#r?zdMyE6rXr|$k~zo&kL8v_WBkR zEHV4obC6GXRTH~51mSe3FbD_qLkZ#7(2bPc79L z*c%CtiaC)P56A~t;F9$-O>44GR(S%=P)uA3sr}A5OZ#_3>Ir`D-G8!w{{=TBK*asb zOqT_3d^HkKix5-#Ra>g3rh@7cwc;u(D`$!w0qM|5JtG0(pm}&coI;!$pIpb>0e-?g_iDTF2&&OmJ;9zJR>PotnL~%Ps2{ z2Z8V0l4&opI_rcw;MeMLfS%wR9IVBuOE?$-kpA`RReQt{SR*P@-1*zY{UOw=uW#7} zR6*TE4Fd(iI|2PfawQm~%80kCvB6mA4t}5L6|vbM1IBXL^0+^o4NunzCi$tR8J(!s z0(E9nQ_jPOH&B+H+AC_TIY=WU*JS1`us@m1gm0ZVwY}9XoCLPQT_gLr>FXr{TqQ_Pr?7_ zEBrS0tKhOB+(EcE;u_4Yte(Gn_aj1q=Rd3gL&i?1-)7*}zwjr!e zXjf9d@9yYg%(V=JJV4u9Hc~H{eV^s5LQoW}wD4ne{?D!@1z?T<)KicV0BeG=H8F|G z43f5M_ilo^0dYUJ6{~{KwM9+NXKaSf`!N6G!Q_`Ik6m+{pA$p`Y67?eI)__8-H}+d z2xN2<$|MTj2i3lnmn*@BLSE2~<%s7Igj~|Sx2=U<>QrG+Z0%|K+rWyvrZ(~Ms|ZUd z$t{M7ha%9&J&iJ7T}gFXxoVZFmeh|BWQ1}kUBn(s0(zS-?q4v|8)fQPmfB=3C58vW8QdOf>pN1k} z`xBoH5f6YQmed|Qj^9ObC!h-)fgrD_+JX@XjhT}FWA>wNB2OLjEbvy4{>fW?L_Rh; zy3`0R*k32g6nv#Z_qX4Rcpi5a6*N0{K~f`UzLAju+6F?mUYEUo{W_6ftLS5ut%1B! zLP3$1AIHl?=nW2f5*JI-_I5^~VK*0i*Ju>JpS0 z2t+tw(28IhAuo0mN*!twKkKS&6^^hA3M!$G;vE1az!JUF6A3|{=*=O@uU_E^+>4;8 z8s!3fY5$#i$+&!0F9M?PJO1(0r&HJw&Aq;EWATXikXAn5lSx4+5uCUF%r8c}UUq%usNin$l;8lmEi7_$V^UrSCvSpX5YQYXv ze0`PJbC#K=h=1rD)w$l6D}Mxr=0YhD843DZT^XiDc&Gp~Fm|K{C?0efAv2U$HpL@@c~CrXB5CFEP7xS~XkerXpz*F7Ja*nEZq?M#h9D>rr9gRLuxGBBWBNE zJX}Cqxu8AW_s#nse|CX2LAQob0^LTP8R9VP!KmSeu$GIrp9x3l$8M>6e>m)mOLS0U?TLg+XyvO@A8g8(ZtwPyhsm@u}=$J=T9)eq+ zvRxhFgsACnf5KUVTuE>2NttwaK;sP5fOyli%8!DpWB*EUVV_TfzN#In9bsi5?nte+ zI1{cF^aRzAa7chigr@;dwBXcq9`_?}14`Q^Cm>(>tUp$*TIGekzkmNe7{#zmtdfRC zizu+oJ=5pjt1kO-FUW0-`PuF3y4p;5`4TrKKgez{O2d?D!o!Kh!5g8XiO$_tpRk2;4m&ND8gW z`h2br(`_LEC)rxiQwVaf4nf!g=jYdCf#rgs%&e?YQR(ZOC(oR@M>=q){sXE1xVQI1 zs?8URl|d|4@>_Ps#q$L@>kt4;CQ&Oj`ybCHE2xwiKr&-PiJSp%CaS^I*Z{Wi6aq_T zttD`bfNF>Z;HaBL0haJ+8Wvy&96E%`J0Aw>!9@AXD`!2SI|}C6w%iNcZK=gsmgnJe zRmeL)6@J*;zyAf_k^lB313XwGp>9{7u>ALyGgkD6K7s&5F+{tS4~CLnNWN=->Z97! zE}3>Xb&oCk>w!_~nhAdOWMVI!~wEPA z9eHw%-Mx;Qp3Z&2*~qxP|K*t{vjdxs?NKWE;k^8BR$X#_A&Vw2sqD>~{oawbvU<(2 zjd33pnliEzvbDbU`Q87+?6@}TMr#vmlfotGrZ;}Lne%?U( z`zC+-K8R$UDc^Holq%wh8X@`TYf*((#l^)FQ$viA%eGVBzpOQzug&-|`$cv2FY_*( z1N?VRmKC*SX$yvpe2T5fFWuFQr9wT|9_aKmZ*(-dvR@~>qxI=c__Tj0-Ed(IY8qm3 z5)3#AkvmDFYHlrD?r#iK#AiO%--Le4%D>;e`W^f%ZnVV8*4HVuS+o7 zhAe75{R9`B6C zfiHC&3q7%w{Pm8N3&+c7Z{6=zpUN4$`J^KoYBY!)%`L+3PQ1IUh~bReTaQNBwY+|P zIQi(c6H+@Rbj+iIhdtUKHZ&NGm)YLeNo}Y%3>jQDci-=_MlK>@3A(6 z&ErChSlHUaQ)}qzO8GHqd_ z%XQ*W?ThCsuDrY^b8(QT=Z0<*_aS|XZnJAa1{MvqQ1^&zw@D}St^3w0j~gjzYpaeF zT18=P^>gp-bdq|KH@WEi-j0D|DGrV*aVy?7)StOqTB?=oAjkAE$uzwz%#k;WO<8b$ z@4h8xz;?vXuUW@2DRD~VSTk}Ckt3EfbLF>Z;nr|?R(_;ij1@D|&fLChiS9LoUwC-9 zvXsOnZP$~6(285w+t`#{juj&%AsFQB1MknLgQ~S}0 zbZeg|q7G^hZB$X?fGVX-VGr~3 zy8ZP(b0k50&Oy=Y1&hzWd6GVxPQ#Vs5kglae*%Eyqh#}_ZI?D6cMyj^P)|t7N$8-0 zX@yK3!(xsESci8!Ro34TeGo}H9+cJTnWn7oW*4uzBZ+|r%0BjCc-T1f z#Jxtx>LE?@s7>7+S#14}q^#2#2|aj)9B~g+Y8sRvLSTOIT}8LOyp1?qYtHJw?sDe4 zH$|?Smc+}Fk9^mwVMAL!_1#3EEyaEKWDJ9|-6TQqx}ya5TFnn0FQfL_lbR+f3B01U zN%mXAA{Z`a3k>GZpPwUt5H+1x-dC;#iovc*QXkO3z5;k{bS=m$EzL|44yUSWw=3%d zZVe0#O=wKg)8bgO74$1?9roOPOFs1XAHFKB6=7#S(aZ=*!G=fH*pX%Y1g<}dJa!u< zX*agFCmw2eg$zcT8!JTHNrj@p99TfDS1(E%8p-k!d|T-uk3e&G|D8S6r8q6L~eoub4YXd>P3RF zDpHeT$iTwP%uMUREYT2EXn3XSB*d2??y?BHERXfibu8{NomGCh^7|vpt9`6vQYd$V zlO58xUMf>PXC^kLP`y2CqKu}jZ<5jY@R3u~V^KdXcgNZHLp0 zALi$$VEFcE1O!O?H^=_u8%l+y8_NZZ_22UK^{tx<_HVG2_FAUhRcIr>lBgl0qoc}6 z3~=0Jc>6U<61}~>-S6N3n>tn@vt0Mu>MNm;^;aH7+ryEE z0Y^@&Kl5~`B(f{ktiO(aY-VQdubn(A6Jfb<4rfK6H8Zk9ZM)fm;vXVpk%A|rj$%I4 zl^kVfA-_^XOREv&0U$gfC`)flcV>lbU$R#B8h)}V3rYicv-t?}+gm4&cU-fXdv(=i z-6L~VYIdU1Asm4i@-_Fg$FIH4-@zC%&NSWByDnq>c$8gr zey`-wO=Bk~j&U@?Pr?-JV z_QU#{>y^Y>v*c@T$o?XWLp)QGDD@xsK8u@Pf99T*LQ)~=(uQ@?(kuC3fYnOB7Rc>8 zZJwg6j}G88>Y{=}bAhcO687xbLu{#vic0$e`i|(D4gD%o64=K`*xoL*lw$w?O|D2 z7H<6mtf(S5+JTuBG^)T!i!w;$6iRKO)iaN^yW{kc%npHpmJYE!NO_bpg(okZReX5u zTEPRAt9pEk-R4dT`*=bF!$i&c?(Vd%p~9ES2m>TYOIurGYElTnT_S5b-!%iy!3W4M zAr&H<92y13>_mz?rLT@uk%UKx->$E}meP=RsKFQ%3+AOazt4IaQa;tRO%+PR_S8{( z-Mu?Ol!hD4Y*9(Hg2oQFxtgKD-NHE$Ica8ImGOMCMv-1J7(AZXhn+{$DPVjs`L5Zw zEf$$6U?j)@T1hx_fNLwVXy`UZq{F|!qmpC1WR8Z0#>?YMFHY?|a;N?UknHk8R?$Z* z(FBv;)ZT70AOwh`lY;}n$M}%T&~9jH!N5>U^4n%qt8Q=X@m;)*E(MXyV7w^~V7P@@ z83I9YLVWxgP3a6oK|^;?|L7oNQkD#eLMS&nt3ZFtruowbfA{Ot;LLp5>F@cKgZ!e0 zzflkiA_0Kr=&P!zfOLWyLfT(u5>c zZDoDnCK%xvWx4bvmQkB(a*+5aC_X_!_krMPY~J?plVp?;&@7-)qvOb-k!mnZ>8qF1 zE6t#_1G5-f5fJwn`WTCqp%)LyJsx7qP)n2xi2D! zZMI|%;PJ}-J2%}TWFd*bIKigL5l@FSoAa$dazK~?2m}t?_-4+&de<^)=m}JRq&mQ$ zCg)YJ@8#ofJPTj>Yij$^6fIIsMo%^hNLP82v4=L}v7q8G{Mmay#)YXZs@VE{1WHKg ziFlT4YzoMbZf65?`!}|HO)I{To7G{$32vP9G>?K?me73Nv+zDbQ7?JWhU>qsrt>I% zvy6xGme9S0N8)W1fM28yhe#)9T=MbeJ#^~s# zHf$S3hO38fWb-k4fh<;zIrM%~c=6mDzJsDD=qXb;BIcfE9N`e_HsaUj6=C*Gnd6y% zD4esr>u$Pe9`y@u%>Pp^@={-0@fJioi!C%2gwXFj=%&~#jb9@%4;UJGLJCp^>zbOH z$_ecj_J@%^5P7MkwIiL}b*>T#hA`;;_z^n+R)jo%R|ql<95jzg_ATB~<$)>(G$3Gp z=+KfE{9wVrRq889bbv49DOiFm8S+`zq^8+9MMgAMHK^&9Jc6*GPIbuS6%?d)dt1!0 zw`KxQP#G}$cj%s9q|a}6@7&o)(1P~TK#+Ls^CyZ78?*ro7l}CP z$WpjqBVz+6q37CQd>)dT90>+$UMSz2;*hw9jw53(V9vRfmvxsam-bPPbxv9Nt)pr1 zxc9H#-SD8*G4IlYbZ!x{M|ZDxqfbe=l7x?^=h2{R`^uzW(&q)iZOOAz0OwF};pTWH z&=-zR+Q$-#3IK-+_DMHFYYx;UZV{g~UZzOabtg&kx^9`*yfP09FvoXd4bBv8x6ZXN$C$3K1dSaJ~tCLdqXi}*Ka1+WPViNU5Q z-X!6~fpT1Y4JkknYMdPF9C3HRKWOuz=&&wABd$`kIdk%)4hTD783rI^FjR^yRgeb> z)6^7&T{zSrpSVX&?FBdxcKXsAkG0reLpVj3Iv$C~T|5C@d>eSs4c_6`*San7x{XQp zNB(5Mz`KhG4rxvQ-<9+>=>cRC%RL0eK_mIQ6sUMHYNTZuKg5^1Ud58#QSjzDGqX%Ht26& zhENV3s#Vo%FkZ7}4bFf?afn@c>kq%V9%gM&ISdRn5aNHU~RN9Cgr;t~=P6IV7mDS9;*h71X6w%;_|ylkC}4A+o4aUE5dlSCy{ z%F*sAQuENIO3H{$O-^0V==-lMsyr^e5TV21{+!23bciLi?pP2=wpuvw9EZHh;dET%{4nAhO0b4_h2}H4|scf-;GwZMSTV{#VR9T z(%HOKCKsIYVpm?i_)=NRsM`z0hRq`$Rtj8H#8*vCrbn`OlE22)Zx^2hSiUVo?%_`d zR-@xYw;?q>GZVepP1q^<8Wak60K1`_ADFk>N-vqJHVnCq*Fl%$YaZt2-a$pf=AeUg zTceB8sHAwEziaJcB+Mm>dbrV?K^w`6%%a6{ad8pG#>>hA`C0h|1=F$_MG>2r+k9Vq zj+>hsxG0jR$^*h=u5^N<6UjK5a|8 zS8S!AnE8i5ySH;ia7||p@E>uz`8s*2SRa}npgFKn0C3Mb2lLn)8`OYliz0V(k z{cB&(y3H0I3_yZE%Q=Kd!%E*(cWM2Uv+zX2Lz-881~<}g<21~T+8utd3rh#pO^XccLAK8 z@3DG;9|_-Zf6OKvo$hXjdZL7%QGV&KiP@y>-(Q1#(I?o#G%82Rl9#?Ic3nRP%e9fg@zRfYU+b7FHL}9KpsF*far$Q?L#WQ{|kZ!%Wx2NZAyqPgPb$NbPpn-f% z1h=RXQ@{W9HTByPeE72Uo~H6?sr^gE7FQg$4IYVvPx7$1czd^rt}!b=4;ca4O|1Os z+=5xw!NAV1 z%)3pV`e;f!xw#p_e%1Y%24UiqaDM&PF3^k1J@Kn94w_nr)!fkA1d9Ta8~F%8wo=tM zO608I`nCO*0uVOMsPrFn@ASD}B$Lr6(gk1uXi>A!ZyBMB_##tSjs>hnc!ZFPpW?sh zGkdO^Okr|Th6A)No;YS)T{pV{LS$Ol;!FrTgH*4LCzT}bh?kdfwW_yka~fYOM$Gry zQB&ky%TCo|3_Z-wzGaYl2bd^JhSi5mAu9ca3$qCog!-N|#MIi7?~(zyKKJ8)Ewq?e z$Pf7Jftah6w4V3c1DF!WI(n>yL4+bxlOqp67#Z<_I&0+m>{xmE_8&dm@`pH6r{^|8 z&zT-c8>|(gOv%^4<xI_aCHVe5BSjN}kR#Qa;V0p5 zA@hIwsKqGG_IPiv9DWi4Zy_(&#WK7D-1qiGUz`m+icMYCYaZp}x~T(s5EMlU96f}B z4p&0IVLSc6xO#O)DbsPc+<*P?1*~d_`}8`#;-1Ta9tkMT@-5E9t^gv~A|YQ_JP*Ru zW373V$(#f1`%9)K7>en~V@TI!e*r&e$#t2`9Br@6kyj5Vf*OK}t0;V@2)!sYr8>ku z%XfjAi#+dWmiau!(r2B*^)T{KdJ<>uuZ+oYpSup}@->84QQmFB2VbZE?855t)&R)> zpCA+ixF4bSKjH8PpRiVX*4T7)tjNf8Z3(@S0kn=P6{mv%3!)5zLD zi_(POEhPaDXm?yK%%B@7-Sg9w1PX3-{0)@KK4twCRRb5lYTtSZ(XPoT9v)fmR^8^IdFP6#o&4Y}M(~yFv1Hii5=*78o5Qt!V}> z)`-q#)ZFC|_m=TiF|1eKz`lcq;t-!Kf`;zpg5a&uwcL^c6i}XLCX*IFud!$9kTX zS&hG~a(2zqs(~1}-)LTI4`AVRU*ET(aiMlvT3hAM+f#JSx}DcQYIeqgR4Yk9G2a7y zfvCif`7Revz#+o^D<>Eb?gN|&q~W_l#55EGB@q=d(BTmc`nuU!u4X6r%`QRa(SMEQ zgrJiO{_C!$*}$N5AYffBY|g1pC*18z9@6uq7l+}_i@Z3{Cr|QNwmu}&Hc`d{7fbYD zLQgDj3>#X~%{7VMa{ZT2#ufUIdl+p#bro`aI z?I3t*#&vUkcVQ7qW^ouw-KtQ|-~c*@i^6nDgT{}(%y7)7kfzPXYM-`9=Gzo75tK&M z#g!{=<;ojLg%acpO_tF~xekh#z_jhE3Alel_e1d#PULSx=_TWZb&OIp0afA<&bsuW z@sfKV0)f)dtHzHbO-YhwrBq10o)TKFwprdY4IA(9L4JQp5-T4Q6v@rbn;Apc1fsK9 zl;!K^ha!=sH5wPe!(dnubY!-I$6C-;;$m_CpZX$TRD+tdd(zr#7sHf;yqfOsw&?YC zert_nN2vQj9ujf^+a2Bk=Vy838(N^j;92)5JH3V*|NTKze6DK#MLhXt|4%3Gf#=DR z+ZUL&_jRg67Sk^{ z=IH)DL}V8TDvT<$-HO|qFiT?>n=;Dhk4%kw;TI{1uo~c648w)X7XLXmB@Oln#M^KI zv_AA5l5E4hsQCs3`T4^buHO~u4+0E78Y4&>!M^ZeX}YFg>7dd(9C%aO2Q+n8uCUx5 zc?on_MOAgAJ2ffm;bPSt`bLDa2#jm!e(Gb5?!tQU<)OETirQY9fe$P>o;=x+&|6v< zf}w4OS&JlN$h1XtSrct&h*Pw52_%(22=haE^|tVnC_o$fKdKRDfNeo&UtB4aC160l zMp^nQ;ZxwCdI2l`wC>cj;s{}M-hUWwmcaLE;-o_dUamVLluqP(L9NZ{lU%$Qzk?k_ z?p4=0}BNj2S`)|Nid1)iHK4pdd(3jN0A778@@K#?Yw`oR8bc=J6nfZj+ln zuzG+cWm|2LP%0E;MiHEHBYyJQsRDollNVs%n+v`lIFbkwdXe66q%$X+mQSOLw> zz9~G-e_LcUq6d6=pz8z+&*N3Ka0qUKS7m@mS(_yv?QY?`4etA-BO|g(0QZoT-r)+9 zl#pIqVW;hIttuO%1l`EK34E|K?aGxayBfpXEQP8(lsx#-s52o+r~vP%p9Jc(_JB18 z9fN$Xtp)X1L`f_-8i-O1qZ4E7P_$qXmu}x13#8YdSKhvJ~fe(Flo0e$}Ztw6hIPqKcELfc<&kE z#TDa2$)Hk7aU?j|m-;Pi$ge!&3!Y#+5R18DPWc|rNC0WNI}ddQo_sV-mgxiYpO?E> zwVG&9Epl;nMZrUZd}ZS~wiPo&&26R%Yoc+V$N{rJJ z2mT?ZVJgEz9dVK5?&&t8PejUg7tjTn(pmxDHFv)HZI?tx=5HLIM_Gg)6Pf5`&DZ0v z4*j;uBWs<_E(slYIvBXnCVJS6oq_{4mWV}^wG6~`j zY0&FCsmEUvI$$c!PtWp_HZ8O*njriTrgc(=h21)gU$_aU(A7dW1&fN93mE-K@H6en zkGp`4H19g?l?}?5wVYMrcqMFN1}Z_a$wpdMA-aZ{UKW_(<=O22k5C&cD~{ajZj;@= zx#F>UgL0=@xF(|k=H-B}On=akLV3|y6gk#U8`Nu{nCOZ<9v^&jTo@R})*4gz=4usD z2hpa%uu-NdF?d8UMaR6cf7+R1t+AHm?)N6M=vM@S9y9t&kdo<6FlK=OL+E&`!~tUo zZX6gG7$BS~%#kmCTTiPS(nL%`jRF9Adv1)GQ6BG*oHtvnuAs1R8+od(+%Lj~B^O-- zQw<=7>ILTj4}2Qrj!;1SZO$*h`Zb=7mfiyLg8_@rg0otobR)%K=>p%i2eAGaL&%W_ zE7jk8oh3b5C|@I&KAV*`XH?u41#4GA2d70B616gs zvvl4UA4Qu!b-^X?jx~@W=zd^^#Ru*!G_rY=Gs`r$`4tucbPekO_ADFhG`n0(EXX+t z;pZax)v@NQA;WiA(*J0=i#;wBpF>A+0waVch+%%rKs>_~Mw~thX*y~eN?e;O2rJkp zyT^ZMfiETv(MyrS-~<{-#`A=Gr>%1EsR?+yM~(mWYX?fH6$&c4qNvL?IhN8pzFfFZ zKaFXByCmqGEEy63eI(4s=r#q}6?KyYTR;F{XrXYxuE6w05(Bi8K;9-+9f4Fm%PyfS^zK7d~Z^+pT^U?yqo`AyRVMqgi+_W1i{gld9oGNfr| zzWLL*42pq(n2o6q>rf-+1u@gsp@tYlI}xjh&KE=r#5g(Pe!$27X(7ehzh6HF6K0#f z5wvZ$iMW6Ev90<<*95x=BR+{%EkLc)Vms4aPx0eNanapV||`y=jRH^Dtn%y2sSnz}hIP$r#}L~I_x+U;VXYtV|lQ-2t?CT=i7 zR`-oEkJjk8BGMNH1*H)-1zNB!tm*lX;NLvj3%%gB^uX}1RGaoPuJa~&Ruw~C<_gi6 zyMh!(bN%*K%(N0dhiBmp4h0q#00#A-({udLdjH6AXIEE8P-Pa2h4rqgssc&H;Xw!X zEs0MXcEW%f6MO;ABr9LB?H>(aVja#(p4;26KB)Hqvr)R@kEW+=2$vqMb`y^wy@y~P za3BCH_y#G72hj^2#^)7qRUkZQn;rS-(55sPG{$lc7;gLU0kMV|6T;+1#v%LB|IILg z{$>mTkRk5@(3gdX@AK<_;}$`};&LM{g=qh&wxi}H+Oluo-oiFcPtWaJQsU*ph_laa zCVVerIapsgF2(oW7ax-cT_C*ZY|cqa()cT`5AlJW8avEif{7v2HmD_po%ccnlD~$@ ziF8+zfS`_$z2?HI6uxrY4L@`9p)Wr8@Hr^@wZ-QoRVeEN>yhPXt2gHdZ$9h9%EwR^ z+AWw?*hI#PeB!Clx+oLG)QGo6Brym-GYNuMK}D2nH>g3$zMCFXxV%aczKSzT;d~}c zKLjRpS-uNlBRl4KODZjksDsEwl1`!V_g%}~~ovLahILN4J!c}gOuDdGDJqR93gYu>{aSgh9AK)1u za>1dnjMk4x|Mdr0W^Vp*7_eXwMHgfLDe$;4p)@`FiiO4lnTh*{WWP*&WO(bWROBPd z7kn~sF_-8XJIG!{T9wCpN+O1t@7|8>#cr0Dm&beu&IW~rA^|#wD8Oa9b5X0&_Hm<5 zvUV|uerxtI*n7zi6XjJDpk?ofmRllp0hk4911JVo0&|8i79xy)x|q?J4wT;r7{cuu zF4qOVP1_i_Unrvn@WT8spg zj)|borVTt|UItvC^71PCS>=>u^i$DThy6~gt*zbF+jZ{UdU5DUd^#oOMYk@-uaTw> zvB*UP0O)AGOVYpcZ7TJ{z0kb?t=LA6*zj9|Sx+r({=VPgyjxM(<0bwBx9>e{J)=Af zx<^b*V1gPsgrP~3C3BwTd`#$|22)|9BpwN!m8|`J4u3%_V&W4e~j6kHHYq~XhqCE2jn za}GJgCa#ga(s2xG2pK;#KBafUh>R^DNBnT~$i;kgj+B0VXJPZzn6;~Cdi=ZU>xeS~uX`h|-20;Upj#5M5me;(@7{#mW5uJro%5u&sb4f+Th zlbgsW3rkCa0D|TKI)%bA49P6l)jiY54~BJ2_&)~@9I$WMt#6d?f>BArO2a39o}C0Y z7ccJmsuQ!2>E$%DSEbMOuX>q`x3*7l;+SlSIW&zVSu#<`mGv*nRR97&)4=ke^a-;3 z|FQQbU{Tlm-?y2mEk`|NYHnEJlA0JUxUgg$D{{*cR|Inba{&dBeMY5@|D#9EAvHyy zOfx|w7er(k$>V|u$&Z(O~>FJD4A>7s&0BdoCr&G5C#ZCSkiF0UAr^I@Up@aft0Ea zyO@rgK7IN>eXpaRzg^!4w%Ag3RKQ)^B9#mIJQd~>d82StWRT>0HKM}xNis%UHZ-k? zp;%iF3>g~NSvdsOy>om1*g}4ymKX~X^LNK()Zf}MKDXxV4==9mf7(ggVz}x(uI~!U z7tm*@@W>g?JMWDHpTT0z=`v5v4C9O)cz=;lP?Peyxyi=-F5ePLFTM8Dr>HG*G2ZEl zSeYx!jD1Qdqk*|ecCu>awA|Qi?8|dJ0vU3|SF%?)RG;Kw6oUQzTj)HPU*Z>-T8mP} z!P5+i%-Ysr_50uLBuU600#+>rPo#{`j_zhWeku5d^S{EdfP~BBbkLK1-?vmm&cMg` z20c0#_&!-?iM6Y@+GYHlr+93+|9cOcPH%pu-MK@pCK8b_Uok*w=4_A1KJh=gjhFx1 z8$H}K^a{5T6?7~ibc92eFlEHhE>AZ}jc(Wejz81K_Xw_SVat6=Lc{uemAwq}Rn-5_ z;9tA#?fNnQeD8IOP2c+C>Ndz9;~0DZ=O~_G_3CL{nowGRE!E#F2^&(v#2~IAiQla5 zdkIcas(4{1p(IYFq#OYWyN>|*kctQM^!hty&s8<#h4VWmyXT!adggJ4E=wCZ%Q&j*EA;$% z1Q+|(Jvsi~@{H#;FaDqh4|K&9%?WWhj=IICCsqd6Tz5CqZl;I3af9|26tPcl5J`LGI z;Pul;;{X$!(#RJiPNJ#`1ZD}5DLz3o;KV7zCCs7vVi&DTZj~otvK8t?sC@#XJl$8z z=FM~+pZL>%#9?cSES(Km@Bd|i;z6rl@JEf()Bv_GRMaimq5FPefU~5ZUZXNf@%hX@ zaS8Huj$TaLHofQ8FR+F~3rbO;mnUD-53T=mEY8nvu-U%g*$z`aCRn=H^xnIfNXGUh zUwj(0B;5L7e*Z6rBkLCQ`@Ptl3`oT3SlfS4xZdmUvpW4*`}nPMfdvWAY>KqqsWng$ z_wOR@-{0?#|39M!KFBy>Q|cf8@ZbM_uK8-wlcQ39`^ybm|2_Qt-#^~=FBumk75|6- zoilrDOV{5!t26p2=d7OXc6{#ex-Gl&Ph47^ni`$e?y2^+owT7NJ5j%X<(Z1@?JI^9 zs(L=vv1-xx+xmYKar)r)gue__9r$4O@%8c5PTkHdzI9^3>47_Mm>f4O%C4AiE}QS> zY%Fi%njOBj9!S!zozKWgPb`cmUbJb^A0Cc>J(JlX)A~==L_GV)frNmzf`+( ze;M@qg!12hc|)J6Pj-ws%&j@w=>wTk<8`w6J?%U$3(uS$aHgo%Yg}KZksrRcJZ;w~`f=&X zRV{VwuiiDv`t8eCpXJAIRHfz9(@ff%EG;Qr&t=_=P!*LBd~W;+X+Wl+eI?Gd(5XNl z(nqJDk;+viy-ZMYwLH_-c0I{$xxSN^D^>#HGK#pJa4y5-k5&a*OV5;h!Tc? z!}>t|&91rKqAhF~TbX{lTZvDy@^QNLI~_Zp)SdpnQ8`|^_hR%I3h&Xq50z;T^VNoI zndP?Y!s4Cu^!Dx7?>=*fQj}&zMoV@4(ZZT!N7IbL+V!J)y+CSY#E20e@4g?f<55KE z#kBKxe#~&N`jB(#_Z&i(Js87eEG7i(ojPb-O|R4i6M@Sy;QmXN zs7dr6O=j+0ZQzNW&6=P-I*-X09Kt8sTfeP-rmG^G9M_c-SHX_{K|xrz(2g5gy^c1#K^B`id{6e& z!#Ct@@7{ea`@VgAe)YB(tBHi|>IZe+q<-Jct?e}C4e!CrRmEw08t?X`#&|kzmG`V! zNzC)zwab@ml>2G3uq9S}<%NzM-o1z8fVa)mgnSL~{5~#jPy3t7rp?OBbPnKQSrftF z$dBFJM%dZ;`}wJ8;IU^!LiVb$*;wVgVOw6~;hPyFbs{x2itkCE#_%3n(E<9>dHOxC z6~&Iyl$lLs-b$qllz|K^7#oFrjdygs3ahBwVQ$({W|*G1q!ds_KYwzvG`4fvbU~s% zV4AST{5g%G)FSh=v5J<@B=I!RwN-C!EtAJ?y6zuFWo|Q#Kc94;3@a8z>5uo=sC-R0 z<0_he{ceYS?`aJr&K%K*h2AXQBCHYpznct3zhuj~4a#nr1{HI+(lc_3eq|yK81w@{FO{g%w9hF%EgQCkw(!T_ef+yhF?uV z0=-;)`}ePGYC4m#J~VV9<&sEiN?IC``qhy1+z>{{-^gN2Z7fno-s$Foem6heS zwkm+m)gs5VkEj1tAGNVIZ?A6X-GvFWxn#yY?9K7w6)@Tqer!o;{D7b(OI~+#LjrHc zE?|%qo#BXbMr%urW5}Z)TBDIaTU5F%csmSywPKEc|Gs^fav8`UblcVouerJ9pX9<` z=ory*>xmsX=VD*)-1=7}2Q_jiQ50onPJ*yM`NC!yV|!Jj+rOVyLShp8zFkmY5dm6S zK0Xs-cwEan^7%tR7V{Dd`5?akt=qS+prbGXpSZd8>);uk%&nkiTnX2F)$1gqlqLkN z9^D%W@m-orHd2N)-8sLxasSC%!x^{$Ywyz3%9r*!InNC1m2&3H2Q#}PQdN4x_;KSd z%W3AX*u{7%cMlJ*k-e6V?mf`8U#o)X&Wj}0mIIdwjjHF7IByUYBdy4Txw+kc^yt)f z#pd&$`jlnJXh~-HAieveqfO-#t$bj+ZXML`e0S}KX_g1{f*Bk zaO6O^?A_VRI2_I*lAlBcI2)V}dHe@j?cp+Qp@vq4W%heH6>KuvWqTwq_$B|bQ8^e1 zM}={mo7*x()j8JNes7p|Vy)g#Xz!~!Iy+weTG{Hz0FoIEuh{|;$%9YB7}VAIlq$`p z;`C4)0FxpHW=-kyO`^LgZO9fzT6Ld1)1?f_=I~JdQT=y_{_}7cZT`09L>LJSByHhI=N{4#EIDtAFl29b(hWO9~n%mR;?0i$Rmp_ zX>JZyRa8{iigE+EuWR*;v6RU~Zf>fc*QysEzkKV~JP~NV3ZFb741P7>gWEpICy^g) z&YT#gG*qun8E)Xj`~-PXNn8`6nZthav=X#(&5Iq>KVe6a4tx>TbY}OqTY;o< z6=xL|wx8pvnEbb}q-Lsb)9pJf9Nt6Z5OL%0Jty|SpdN{%7VOPTbb0d3n=Yxhe=um$ za>kpBQyf;8#{V1w8F0LDSA1S`b*yqaHZWFOy`z_P$Qkq!o?>Kdo{o`AVN7js^;q>y zNoi>=ij%M)8B7Bh(NTd!Avp#UAOs+M7_=WORGb=UN0pFWNwUS8;Ii&x>lpT^tle8s z6VUH#k7ur6n_pe|-L;?uLt5>4b>6i7409^TeCXEKNcr^*-VI+VCfD6MB&zoZ6f8^k z@J?~BeDr8EiHXUorPxDn@4>Do)E1YMo}O-`2`o&@mV1bI9Z46`C2El_P(SgC8y zdv2QNp&?s%c>Wxl4-n-3aVcP0M$)DiW@L|7R}IQp(0y{7zrq=CXD8+kx8FOpx-8}P z3ltQ4uFT~d&= zrSU_kWL%0WbWzYSb<*PGu@WjQb_xETX)f;W?!*Cw*Q&i^!)N=(QQjJ?P1m0|etf3yLHon?Yi8Wm}JaoYDpMyjvGmgC`SaLH+_|gh6-00 zDOJVfG$W0*_XUbE%WO|kvak1q=z>?Z`QP6~(I&VbiyKNm+Oc=<;Y+#3G;^!LRfTon zjb_KYb^4jocW?C^YXf5!JD_O+)6#a^g1X9@Z{`2J2%W)?rfr`X*)vnam*J=}$wW+z zA>1OhyQLw9@TgCfzZPdLRdI!YVQ%r}TMx3bTm@-@O#q<^wP!3-3xSsC2rs|2YjS7I4?Fj`aVho zfJc7PJv1CCxEh3c)cfq&({XVv`eW_y#%t-<|HJ>zbrlCCRaN&xmFr*jZ+)Ds&XJ^w z*2w=HU73cq7j_RYkpI`u8eV%l9Y>1yP0x9JMQKVjH-_e!G|EX%N;Adn!$gwp``}+o&w(r*XqLv%A4hy3LU$$kQ+N#y7WzZVP3lT<-kIzk8SG#EB z<>jM4WQ4t&TVs#=SZQ!2-w6TZyu22nT=J}yrEi|&~F7l*W0?%!XAlTtnk z(1zp=(=IOMfU=f;d@04{qT2(**}@&~eP^V-I~Qz5k#D~~fvF|(7>w$2j@4lCVXP;< z`b3l)?>?m)y$GXX^CYNSKDxAe9^)d&)y&^Cr}Zqx9rz7;@U`=x9Tun9Vy z{}VB9{rqDCJ5N{P$xxv6siuMhd*W^Dq*|SFG8Pqu71EAXf6U4VOyy>E)MH))oWDI#^AwF1Ac9O3L^{( zw6(h{NZEpGW1O;?S2t#cHBo0=>9=~)8aFqh23`n)@4(JI+UrI{TlD6^*-KUr(603- z_XXXI(|%fL{l`H`7XzMTaKl`lIFNS#y?oUFB0)C4(CKXz>c&+&gyhA=*+)+=`_;+H zk+mylbhGp_d|y-x$wiov{9=bZxrCG4<}A0y_q0zOhW4kVXM{mIe`lmcOYbG@zlM0# zu8*`u2ov^OzkNI38~ivEwGu9#b43Q3@rN+B4vi?E;bI!CZplyNPXxM&Be~O6d8Y1o z=|0PN4;3*J+xyCXFqpbiC5qA4tIgpVO?S5jDx9sZY>f%&tXofP*TARD0dSI_1m~}t z5MHAxu?6el=!tje2%TS8pi~F&+=8x|-Jr6iN6w8>->>sm1iW6S|779nGmJ*pK61DE zg!0NWyWcs*A>CS4dRgaeHtKLHpnOZvv@s2Zk_6TCifDwdz=MYz4Zs?dC_Zt)HkW1e8gx zplX*|=b+R%2)Q8v3d%>QX%t4p^K7fylx%+N|9%^p`ulU2eyJ|rb=+lUVxbIf!wOnr z2^fih?(Vx;Ft8pQ&Q1(DdbFyhzhM`6F1_EG5+8NswyT9lhHPnhW$S@6YxRaVj=fe_ zSW;J9wN7QMfA3Dbb}}GjMN&vrVB#_B&-CdssXkdC#PDnhF~Pb}0mSy;j#s*D27vHS z0EgITRi8c`%wuq0!EC4L_p>w1LH7NIi^bZ77-m;R(09xmV?y~9bs60nAyp~I{?kWc zY#0xLeFv zq5plnsjG@p1rqo1R|no7y2xj2RGC5i5E6vQxVfdD3U8_o`Tc|~|G&JlTL6U>OiF)cI2r^8{EEb|59;c?{QXDqun;_G zCq9((JT#<9bG4ZnU3x*bguCEouDOkZkO0PbkC9fZP(W3@|~pXDsSm3^*me!i~nosnY)I3-(?}z}*ErqSh;18g+4TL*an= z?vYssyL>Sx-VKT(QX(B7U}RQBr?P=ovRGgEZa3rW2s~_=Z>8xp{6HAh!Hoq)gksPJ zwpmC3Wq5oD95Hc_=X0wWI2(;m7rFx!!C^0(Cvqf2*R5MwP?K?SaZlff*YU|%3t$pF z`)+Um0EZW>6(OD^Nh;G|p216)(z$u#qjJO?J`6HOXtf-TOqe@iA-NXj(V~t4JbC>1 zPH1)s1Cr)LD*RCTqH%(zOUH-_<^5jhST;T|Fi;q(xHzH0frPFQj{cC0a4K-DfK`C- zgHWyh6BqhZ_adJ3X?jDH!PGLpcwFB~HU;DnIdT%zV?9A~zC8`O67b#-^72#3^AiA4 z%Y1$9A~L@zogSJrFA#ba=O!8mzeijJEYdMsGsK^p_Tl_sF@&7rpt0Vz7&OUESO{)H z7@#GVNWzxaZ~xyhdLhr(=8yMXoD1#8SGcsp=5Smb)(BT%^Ut}7xv*ZmIZ#H(Eg}Md zoW#J4|VDaaSNy_>O=|0wZh#U#?$A&(&{~a{WlHvUHC%{n4})!2Jcc4ky6y z>H(NlTt@eDnVO6hC7`vij*GKbVY)3G`DNb5pINs=oP%Ad>u4Z<1ufj)! zXv76kY9zr5_R%yQmJ0KV^GVQNZaXQQD7SEfoCPH?zoZS%4hdb0E3B)lTbW~}i0zC4 zc!g}YgG{5|ixwg&mM~Dp@AE=^V5}X3>c8o-iFUQ>4X0`R4M~S&=hae*>E>Ked=#Ll z7M5VJYkG9xgy>4D>FsjXk!iqi@|MT}n#={3Pd=?!XhCRoan{pXKg9651 zTLx(y9Ua4J^%MGbLF;P9HDlB%K5pI;N>(htY`!;t7` zQM#$0s@mbwN~B4slQuf9YxD6~I!Y3&|DH>1C*q>#O|5QIZ=UBS@5oQ%I56ivjH`S3?9#1h|IFZ z;ZjH1Wwr7HOMpxk1Rx>m@Ipu61k4{V$dts!RlfeYHYd=F6T?0C>5CrLBG5^#tcc!o z>i&G0xrv2*CM<=dIJUe7odt{dD)Zch^ustt9xSatYl-wdUfIu5WYGwMY3{a(3X!@Jf=*qEj^$Ob2#?S{rKAJi7mse1adYc-B)q05MPnDYz^5%Zhffg2|bYq}oZ$C84^pbAD>b}wkJDj^eNAUE0>r0wysL|Q1ASR!agiGK_&@=z!TvP&2^dp zY&bOeQcRRezPb*UoI{0=oT8S+ZDO)WoK&s!-D6!k zacnc34Zjc1jGY(3wRIKuwtl@zGM-3^9K)JM0!RabQY`raiF3=CONtaV_PFEl zZ_0B*-2*`63muhX>%;c=D{eSwNM;b9Vypyobq;HM0h9yA!vA{5PVB!!qH8i2yO%*m z`Y~pjD_`aud)N|9-R$$v#IbLyUCIC8Q^B19j|@P4BNWUuH@9X7is8VdBznvbIy2_y z5Z(2%{Y2qu> zCj_D)mRz@_fL`HjVJT|9Y}AZ{^?ZUhofSK(_qOWJDfok=zx;&t`kf@n?-N;e3Pu>D zgU|`B7jQWSMC0J#AP&u95+A=;YFz+P|M-_~#@;vuUP7s}jLS9)>;%99^NPTPgzV!@ zVcy`F$+xKuscMlLex3v-GCch-GA!&YP-aF96;7kuQi?+K;@vDX>%aa;vu3o>ti$kN zc|nfHG`QIu39iOsk1&J~qm(!d&qj-pe{g&HPX9cx0c_{Yax0TZCr zJO>w9a}s(LHDlRE+`Ei}H_NFy}rD_0!j*~|us zKpuO*MC%!ZDJ-bKnA!`jLR|`Cii+a%43&Fbp{8`0MiEdd5u@{hSz=J216m0&J+q!N$%tRz zDd2C@j^`wiWUwA4%~>i2UaQ2(N3mV&0jx7Mgao`1+JznYV! zkIg+DZsqr=#fcMisE=z~q6%Piq)#@lwkVlJ**aw|BT8#(`akn;~1w4#!_@ z+D7|!|3AOP%IE9`6BeG&v@;McuNieP5|r3S(0j9KAOC?WaXjE33?!` z-t_lKnV?5JY0b|8Yh#@$NzojQi;K}s!->JT$kq^2KGjT|$COm}3OqjdruNaa1;cv; zKl*hhndVu_>2nsS&`1IWWSQJJ1a^SA!OpBgI@!cIUn3%d_v~upEI^IVfknUw5Ib;? zh`kUW9*Jd5{?;WmzNHqDJH5M8>!E1)lnvo?gyFvKRTSuiJiJ~PjmLunD(r_*>)uKO(xLJ0%wP zMOYwkmdz_z@MU35fJx8UwV7|AS#hPcu$RA=vU|X|;N$6dJBswwNkf7#)E1^ceV0L( zc%JA0-#jXMIxJ@OkRbcLlGu{VY&8ZWQ^1OKCHJh)A+6%}rt@tfmmm49bzKQBTfjDL?FF3C(`;I!wTV4VXz$gdpq_AYGmvh zFt_v6hQ;sJ?2ePYp0onv7{ zc;V0a`EL2TV%w;bgI1n6Ixuv6YxKD1=RWJ#&UO2%<5hhppWZa!srE}RP3oZoS4J=%zxSV%(WiNwj8+m=KZr9I-YeaGbr-(X^m^O1^*^lGJR++*?&pdpn zxn^Z;v$nNFJw;_Sm6^j8&DoyL4{Ovj`WG;b^J_NQNhT9A>5_X@tid_9DDYde)q~bA z9MsMDG6O-SUy?}^@%ilF39ohPv+d#{t?%6yZKPebk~J@*@*E>Ux?^$I><9NLWvLLh z?cNCeQ)PwzhGUOxtll*vd;6Gf#v8G%qh&0&{rK^cF=z8^+QW-|An_EsLp8FTp@P<= z&*-0~$4)kG8uyqRJTo4K5F*OTKejhf4aMrYuv#Z?FE7j9wzA8OWrIC$n%P;GMhd5y zVINVpvD4yB6d}!UAJAwB@hRKY`tOU8hdLK{>v+da+8JTA*iB`-JKNlnRu;;;TbK{S zxkk49v3)J)>eZ=?>XyHNNMlBZ@1at*;AO76&GM(OvOU$|`gbqvm@xivyoWiw;m6jV zTQ9K84#eJl>xCu98n%YV=GCVDth73ZrV$ehq>1q|?=Aa$d2C20FIL=v69}gOX<_9p zq`YNiE&Igwj97Su^?f)-eAo7>XUF*fZkl1oKZ(z$yiGWAh{^te^UQfqeo|$>l>>s> zmQS&Nie3+#75*uhI&S~_S9ZCluCLu#)3Zm9hGPG`Wh`^zC)gm!DlI6H83$unWoNs$ zZRw(H=%&GXcn^3!Sp?13=PPC=UHX>oe5}T0^P64Uie7SV5-~x)N`k#DLvv~Ny?a6X zTeVuP2FgD|dxgk<`U8KZ4X1PSiPJeYfzx4=5Y3PvaKbU73469s_4EjOl}v74`%IR{ zHD`8Jd<~Sk;g-459uyT0NDV@q>-gzVUF~gLk&o>Lkd_>5AM3l zroB6E>_8QzU9y?k)B6dUW%ER{L2Wj4TG7lNi`|7Dp>MK0+BfatdmdE9WuB^38hp*G zWivLdn3IB1`O-vr4nD<~g_}!*HO4~+FM9FOy+=xJF zNHPD(w6y(@hv<;4E8vizyi>S=l^3zZWYPcSZ|G^h>nZE*kw&I`HM9QsODaX!URHJe zkYnS`PuS@VNC0JWmDI#3C+9acw|knOaBTGt6`@tdyFtHQmDkT0SnV-ZdA7x z+C4D47`)BH8PR-|>L3=?d@LGcg4f12l9K!u3bprxYXAKoqX89bd zjF3wGHu#e>Xa4!Q42X{$T5RQ5xLed0#MEWb3D#e@XM~MMC8(4t9q><3ShM@tb#GAI zHa@&%-ES-FEocGsHYyScu@%pA(2KZa65gQ52HP0&xk|rBQzwzvN)86 zUz~>Z*veC1VY@q2B5UeUtx*UL#szaL%gBGPKZ4J^RQ)~?EC!dI(khnPnCHEzcd=tb zVPOqRR3q|Omp$sOw{~Jqi7Y0mu@pqQBRn(qqorZ>nVlqSsj4_`a2YdZ7lfj178?m# z-rlz3lS|a}`#rpSL6$DFRhPYuiUF5pH#0dXC#Ps^(4>!=Z1)%4x$`M{g)X7}{!j+8 zn40JvQ!-xJ8b*LB%+9K@HuJTIrtI-+c~sT5#jt{J*KJlQSv@MSl8eHZn~P{G*}mOx z&6<(FHk;1*Vs^1?REd_G)Mfg_mj1Nb%92^O&m#J;O4y+%yl7ZrU&H6+91}^t%+cix zpLXV!*-9u&m&qx~&h0a2zQ(B1?=On#;{z`C^7CuJa>#~0PjjQEH_JcyEhs!uOz2#` z0v9SG@O;k^2a9SenMx#8yvT)4JQm+nWDI_YrDuGDj*~8ac*wqUU}>O|w3|D|3c>R! zIlIeNHeNn>uuAqA^BAn(OKP9Vw5WQ?a6z=8hny-)+g|wTC`>%-7qK`YM-{>shlGT5 zdqh&I_2m4zPLrPh;vMIfU%%^=d#B-kHaFdjH)HE+G&F&1UPZ%+j%6t(K?2v(X;Sf} zTJxU#=5YbV+CtV4B2yhrXI*y5CQw`mR_!v^zK?@pM{3M}`KN{(){Bhrg_k!#UytYXzL2!x#{1y+)&$34lBg%%{KA*p9FezG0 zt>p@+f$;&=vqU~vqY7XV%ffRB8@F|9gzHJ}LDcZb51wx7vKi1xh@oWMr3Hqv08A-u zi#V;Wj)gQ8nCF_BO0C2D6hG98UEZ^|AY zxa9h$`rZGK+adPFRQXk2-ODME=Ul2#IAh`vMX9J@Q2z3C5i8yc-9<4ZL(h`%k$fm$ z8Tdd6YS&sbS*>sE_&pZmvMe=1pM;9h2Eu#b@OPN{mn1IZKPl?nn03MGe(S^69^F`u z>+@?q`6Av&m~F^;faWBr6!OAj%6*IaaM+kL;n?`Uk>ej{uz#6r_Fl1KC?7lLn0r=~ zZ^mBkBXcPW6TbWB_U*Fym)i=iOVRkj!Q&<|CWq$^uN1Q@KMa5qUBD1_7Gai$)bbw!%1(7|E!MN6TF3TC>pzJ4TUy7TLDWSkhk&Ks{13wq11?#?TEU0PBd{l>Ga+DSKcDS&rvG?auUAuO*Iok?KlYSQ< zS;tT7_^z_oe13xE%&1o$-Fc)7PJc&Zf2RLbux|bex+F*Cp3-nA?-{GhEt`xxp02{i zRh&Fm0bzCW#0eGOZc?{~i`)9JO!t+kekN%Sqs<tg#cd6Rwy z7Ghsz_ZxfBJrcdKfB3ctO&^O*A{!ygu+e6J+C?r^vng;2(1CB?zy`XmUBe2Nj}{Lm zr$CN@ZinNBhXLV=*Xkx}({$&&`6!-i%JI1juV?FX(-OToD(;>_%_@n8GP6O6{`Y%S z10r%geB+hRm)KlL4)75Gs4#_K#AK83sZ*mn&-eB5sl2jlm>Bhwr%yjV^wK68i8|g0 z#p(;f6RalPEXa*d@tK`K3deOOOe{@wk+ykGddu=`?g|G7kOj)Oi6iF9}|bJNM z3DgMDRuetl`^WFmhPlJL4DvG%#0e>Ir=7kv;Q0h@=H?E{8t}Hs#jG)h{n{UGVV`jC zsV@BisD#B0cQ)G)p5EX1?D@e}PF8f`hRDjfPiKDd&Zpfz8s7amNiU#Cg0B$|0kKC8 z#|UU5RzN=_OO&~^5KQE~vE1-f7Ip$IV)5}NZ#|xhbLicnTT$)f0dEH@=RfF~V0aBy z6V<^XOL0+Ym@daXJ*08t$}9K%SX3@C-3}1a#sL+P>&(u?StUODjpu%yQ~SQUOj#Zl zb5NmnX>xU*XaE;sT(R1myc{@Ew5a8g!+qD71u6txb9Jx9<}w_PuoI+;g#goe1l{?< zw)wjDQ%W4g7K5&dny@TOF(^2G2?97og5ml@8_N$JIwahK00q1_j#d_iiwsKlQu*!3 zM}Lx_Rh&WOT$jZB6+o%`>;}2r5BT?_c7|0ca=PsudmB`7iXK@ZxVCN8ai+8TuK9T_dLEg z(^$2%@SSh#EwYECRw8R$1l)|nA{jpwkNeY4Kb5D43D_6+Coq-Y z=I}*v+|>#vSgE?$h30@#Mw#qp>$m8J{>Gz4RyHwXMn>~nN4w5EGOOcD%gv*Eg%RLL z)FjRor-J3f4D8mIhT3tL(h*BkHK3cZdC}eK>J@yL#QQ{eJjC!3BV@n*eZTQo9v0Wj zR_=<5rGTXqXV31#I1BhFm^OVn@N3j7Ha6z#nlnl@@orm4uoNIihO$ppQBMgreq)I6 z27Kwvzumtif;|IKz?7&60c0ohD}DMH-ix`j)?hx!K5Kzs#7$5=-nsj;XdeF>U?8}G zEV4vM39}8iU;14A4}T2Jcuc29NRr!;LZ z)^I3FOBxrua~w8R6=0DydTFtr44m?A$@OJn9)>Jh*iK!*w&(|5|R_|MFLU6UR1 zpiRyTJ6z%u_@fV6gXGGk$=W-imZ-|cYJ$HxSkmbt5zMIhrquAsB66ZSwnD##%QtJ) zzoatue3W-?g{LWADGldnFe1nCcZyjlw1#14SdR|$^h(?OPd~k*Xw}6^Rtocm#~2^_ z_`#xu-+t6I|C4z($>tfiabW^~!L|ZmJrXrc5aX%^*@0bp^yu+%!t72O!w-3;)%%Mi zUK5ZtS6`VE70e{6lT?POTjJGMh}ncw5Zm=jS&y${YoM)-R zP&?et&Kveq*rLqLNh%=eAXh;lKo=1VAdgrqaZd#ceD9OG6N5a${mhrt2!Ia3myR0eJZ3`YpnF;B^p$gqHvkjCmI`q!I>Pi@KUWK%)ZIzF(@U2K@J zG3|oDJTMvb*tXo>ST4S(U`0~H!%H^aY_FHNEjHHX(Qwx`5`y>c_(`0Lxg`8+-AEFaH75R3DUyq!IUyyB#` z)}+<-Ofm0oV|XP(I+2!UJQpZIzi)cuY&1*`{ZhydiL;@D?`vGl&E;jA81%gH341wb z`-qqTVYA!>4`+G>G`eDNy5YI6%A@NVKLf5&b8V zFF~KkSb%Hm@KeTM1XLUe6%y#v*6`GTnu0|WYD{ObG36;@xTI*Q@Yup^$dCzVF5P={ zPs(Oc4G1`9g9-#aXF@EQG$nAv8R=Tq&9+3KfTrqFfa{XLWVepPR z^NeTe%+~#RWe#81x=d3kP4wC&3W*HMq&)N&a3dV4$`*^}8EkU+6f1~9nCI?(_4@UX z1%^YDyz}kI&O?3h=zNZRHZd6}Hz_D2WP&gmM^kNnO7q2FN}K)c*|YuNg@7Mka2*8H zycIREy*_s`m|7Q|s47VfBoJ&k_j$RcX%uD@h=js7(B0iIC6;mI*~+~HGn3}(6d#-{ z@zJ2Q68y;Tojp6D^JmUJE_E)2rT`ded@DQzK$w4Gbx>8gy9N}G(df>PQ=ELvv8Qm* zL>^-A@nG1^?E3op0bd!fj>@Y@G`C2pvQ8Sy&CLK_S{}4h^v1DywXQ*QdPr^qn3tI{ z*p;YIh@9dS-OuM*?}hBwFGrK@GMW|(GDK$6fCQ3;u}SHPyQd%uuodxRvn$izbXSHY zE*00rM7k*9%r7mpZY?a^1Zvu$(tb9eDZ_9lyA9P4uZlV%>JdlMJiXvKK;k50-q`iw zXN*~(U>q1sSU`qFy{6<}|N5H*TG)5g34A4AS`Mta&L+r)ZOD<_bXH$gzU<{G2EzS?reWIjs_;}7f2SwgmAP#+mg zIA@3!2)LLm+A#K$6f-|AYhBQKPifqtJkIdn4;K2zix$unHO@^6{ z)uE%YNql8nsrP_6wrTGF1j)O9ss8EA`}amvM6FY*YLNqp)f3YcoDP+ap1~y?1k6$Y zDk+1<%1ScldLw2Y7zv4Gz7pW%M!DOA4$2f{ZOh zvt+CdHpQlQg(*!Qzou2+(9&2_cH7BCJF|>E2qlT#+gnwJZqV-21`O!c|NRvA+@F8` zNXYvmvt-bORxj6q^hufrwuHCgX@G1K>r_^raf9})jfV z&F$s083PMmn^M-$?lGvDYl+e==kCR81QNS{$v@$xdRM;(3 z^f9e(Yo%|45b**Buu_;XdT7xF!uXvl3yjg6F?}6(4s&5_g=2vJ@GK5RJ#i#*5R%-2 zP2zsAbNmxEf#uMy3wITs;kPuiAaf>U@XQSq$vJXt@}WGZB=1Qy_S8>N$tMvqwFN@i zC~g~;>;J|<>v4%7xH8~BNL};{D+V%v{A8tF1N4yh^5sJ?A}rh3**=-a zwGhwTp%7#I7S;;6Lk7yWwy2mk>*NZ?5O9vg5>3au!ZU&YvGMZ#fByOB*K3<^ellYj z3k1JjW>TQOzz+-eFE|!+U~^W} zW~k)m7ant_T4kRDZ8Gxe^? z9()rF%9bo*n^Tl*6+cTtz2(D=MUft5nm0#`kPu$j9&}Bz%_Ju1$6LzhaE4u{zSwH7 zHk4>Ps?B$MsxOli?H@nL`lMq1x#al{iM%tvf~CM`W3ojmu{#`?m=r9j&PDtBWaLdmSrkp@Z7Q!HnnFam3LzDqlQ@!J7Ia`&VCBZz zoy%H>po<2PE08xL>+I+H6gX0z0WKJu9CFD`C5Q1LD&i^$B{6b&bLdu7GaM!cl4PN z5*`}}1z?*6Rl~Qlhp!;Ag~Y4ikhu0-LQC?T3Fe-t2xwD@9~`8Ft1l2xomJN|D)zg1SUq& zQkAC5omh6Or7o3F`so`=PXMJOjo-m{zUV2!^jm%xC}&cb?dq%>SI=ch290QgTSf0_ zOZ-dwlR`FGK=*G*i`1d{Q0O#-^7k}|hAE195CW05BJhww1pfp{Xc)I~yY}%O%V~9$ z?7nRlCl7EWh7>C>$;(=DGbU2URg6y4Is!2UttF%=$e(k--h2QrN7@20#?&<*3LO(A z^9m(iA;m!Xy?LFzkGjmf^@bm}(^XhpL{rA#5$#wgYUctE>ksUkxa2P#%t!E~;v7Xc zb=(JNxk3;EX&I>#%M6gh3_PN$0`DSNLyAZQg;nRrW39SANIiAxD{+b3hQNWk;w&lq zhod!zx|P96ED-_$dBTZct2*AP+??<2XFVzujp6JC;lTDo;N^)`C14^q1)jyGOELxn z6x7byP@cyj=|Q_;(R`WRC={ z1X+w?CJ#qviiCkM{2BQz)>ju~FExLM;dsy(MsPLcPSjSg)8y)J$p6eSi-q`$!aPNW z)wOjZaZETP3QWo;dU}5Q(FeSa&%KIwEZ6h7*d-*NOu!gncc0!^_I`r&0TUNQs0n1u zi>FyXg5x}rk{Or>73ElA-Ig;GgnViaG^czVGTW&@aI}yhKuVlDr=JcgQ&fe^a7aOx z`TDpFC`SqSxG9*ktN2KciJ7V8@PLD*2CI#-Yu#02XiQW_dzxDcJ&!yAq+^aE$DR}q zkcFC|;yT~o_kAjaf)r&%1bB}AR&V~IMdISRRBa_vD-a2;khGxyU25vY=7Z5NBoUvT zEAx6{II!a@e9=0~%q1cu+8dHzTFCo`9<}*;a^TgWaQ~r!!rpKmiCu_Inu^29f7hXDnX1Vem2RAWsWFq8Ar{F9GdOvjzM?PGodtBB=PP`4_JTG!M9&}w!`4qFE&ww zg_KLwI;4Bd%>tg54LYusR-Y0!*zx$=_2B>}N&shsEU@~^c1_BUAD*$E9NVs4|LIRo zTAn`geU8?c6I}mQ=q5L+s)91LH_OcNOBgkoWWj9XQX5FyKBFm*kjG>_}+hKQ#l{Trj+iJ zX}7ZsAFG;N+`GKf%F(^^Pc`re%=EWbnR4%%_d8|MsExrGo=txwb3wOm+&E>1*BN{IFfCnT^B8iJT9WZ#pp{!Vx1jmj zq%||U6Ry&!zIajjhv0GkjsHH9uXyAcXm#Lg-stXU^#|?NeDS9l>d*aYhWg8YnxQ`I zPczi#{Hb^5*q?^F|0etRu~qZZe`TLYs}I$!pb?cYSCZT+ z8Iv8{RR8Jj?E3=~A1pRr*INf6X(>~lmCP-n=dRFFD!qhx5Fkjcvt2%V7Cf3{>8)=!}E90S)GwR~% zw5v@u>eiKQW4fe1Nt-5#2`S#i<X}6_vwQv+rkU`fJ8<94+w9x4-S+qIj7MIcJoA|%yNtuxr+|nRhNmAoq|^=N zDZo;;iLo*cI65A0HXHvX(SnRO?sH0KQ?vgu;A(Ay*S*wyNd z2CMH^ztXmIEAjq&ZOPQ%y4u-h{T3cPt8mg?3CIcx7|S(k?^IQoQ%VvHIP{dvjDkv4 z$*{rEz3bEix>eNgj+&P>XZqQ*8mwIWG+Nf|oR%DCBCdfGGcbxcx$;*y9;>R!m>QfWeW zsx;?Q9UenhgETWTzre=)(1ylv$)u7%BtJ29wgRqGGM1l_={Pcy6QpG04)wD4?ZoP2#&__u_wnzzy2q+1T z-zMuYIi@+xqgKo2>TDU5%zyz3n1)CXNu;fkiU1wcYcfv7$RA3AmoFw?fRqyxA#7m- zB|oaFATRCaXc$UT6%`1S7i@QDCgO)g&f_-c|^L!Wy^8rFjv;eNe(dgowRCp?Gh?k%ym^(i0ZypJ=+al~4^awD%LWYhcgs*1CCACsxqugFy z$0Tf0w2W6;uz-D4G@<8bh!aq*bh1;Fva%pPUuD~t#c=&Nx;9=az)3yQ;zGUZu>#Bj zYXO*A5Lb4tsLp($?A>|0?P_`RjL{DtPdgS`(u&!UNgMQ7_ulq#Vj;s9Xly20Xdu|6 z^?y!lUFjneJY)L96f2k8PW$%(O?@PdKnXCB#_iklC5KJcYjal{uhG5J?GLa3f+1!I z2-ERpXmz$d4@xvF{WlWi&K({%QYRHYVi=`rX1EMzu1?kDl@SJ$;lzBR62KxD3y){M zBcPQWib|*x3Q9PU?6x5Uj8}>qBYgr&rF!26CIiBaG5`m?efeXleC2()5cXHN!lfZK;xRH}HmmI>bX~I5GFYEO zieKrePw&a*b`3w?#&d?qoCm%gWDb7olu6A| z?)tN)lCJ*m^QeMy}wvV+5^@?q$shW1^J`=irnfTdHpthHEGDL`V`|hcAj-V2Z;Jckk-XG_KYw`~Nd`bB0~V zBF4q>HxZfThstvF3psOkDYATqQPUb0q;eucm*iczP!08dsO*Ma4XE^mTId z{U?mIj2#n)3uj!%K$*Pr@&;PH7#JXfc4Y=29-Jxt?XT;Oun=z)=w->0 z@3su*0Qb3+1+?|D-7Bbsj+X70zJI~mC)>$z~&fb#`fbJ&M<&DuM}1u`}?K+Eiz zs&(@dG}978clA^=ht0$rF;?eFSxHewtb>z-mH3vIFlOXl*mah)GL@VDy!b&a>~au& zAF`#boK8k@U`g^nFUMyi9_n>@_yyjyI+RPsZ(;&*o!pnWPNi9|oU?IlL*rMaeH?sf ziIS6{G@+R+>{nzC1@~HtLnS-Xmgz!Hzz!THpS~rcu%LjBII(nCz3Azl`_>o>VF2TP z#y76W;p6hh@Ua{;sEF(1U$`8*+8S^Y$ks-6@OF_*tN~4rXetHz!_pas)6`oO#H)xvCHx;9coLOf(i~48#ty8+*i~5 zd9fv`+eO>5TFM-!Fbqv*SXS>v-f&9Ho;?&kusQ2|*&OEaDaO1ZQv?MdNJptGpOIZUUdsc=MaS}2BpOdz`&9LNo5~B3CYh@ti=&9yWU!B=p$A~W$vbSB!$i!NJ$g)@ z`9+4^#U0Ap3wdo*1o`Kf@kD0ua~0B|iFH*~P+oM%RTk^w1|-5un9Uj&$*WK$?U7o_ z5u(ftS|Rf&R5RDrT+qV2cV?n6nN-R~n5Q*&ICwbj?WUoaP4SF~3O7D$;KpwfgGih3|AT zZqvQpa3SjmYNu;X+t(R5zu>|*+b(u>VVAG$-ighV9ocNM&hf&w5irF4;~YYaJ3z*< z_(-#5WG1cq9WaJq%k*RnD9i_b5c+k@tIdf^s=*S|-mPm4>Dm8toB%ls>4n06 z@KiEtaga;w<{N~dq46g5uVG56sFNat${wAyi662hS>fW#1OOS~iEAQ;k@kleCjCRY z+IpIAL9Af;+%7A^!rZ5&TbYLqo~c^9X2n4A;WViRUfiZRez*Bw%ujDDcuK?lf>;U7 z58pU4=-BD`GDwS2I>0=fFzL!`%V&KN2`CFjenAg7;olrKb`z$6^t+M=!gt5#orYh*@;{4`V7cTrNIo!oAq$DLg z7TIKQB(g6-3&8Q1^F%c*$1?W^fX26MOtp&tH+W8b371U)EP$SyrcweQ>$bBxxbImk zC&T4Uy0Hwois6lUQFI342{S&t#0X^zPO?Yppd$n!Shg*Fpb`iJ(#QA8^>Fpbe;XYo zLmhcKM2sk{T4w=%UsL3(-S+j2>C>T45L)r(oVsO98;W3AO2L~0miqKy1<|#(7#+gYhPSJp2xi4anQ~jd85lUJxQL zt`jG^Vgd`l^T(&xwt5yzowgJvOG6oNNfgO%%k*dxY094idfs1M1_&zB%Y(7r4dn%f z6Z!~TXM`=2%&XQ3v__yX9DFfGS1tVY6)T5-E^il+fdqjN2FEA;ZE752JjLppE^ zivVIGT0}4dPGeg_YfP~diCDQSS-C|ygX~Qp;%8ECef^Kmg<}S`Hk{UKsE-Qz&fnmS znaE~r8SC^ua#FJ2is6W`A)p&DeOvHznbRVW_#(#2c(yaT!&^LDyp-0P>&wH!M}rWN zd!laOE^Q7nZJZcMb?~`}&FAkV*NtbaFgB7{g&Q0?)XC9NsNjUzI0?7nQ0HBAxkl!LW7yI>&W9J4RAOe)5b@^rrIx?s2|z(Hp7NB@GSjXvKz z!5s3jT)M;<7B?HWGFQ!-dZy9F{k+bya88(%48Q}^3sXxJRl%B-GOQG>09SLK0U3!C z&Dw^$;dy4gV|#_M=^@^=m_d5rx46$z32;MlyQbF#7!2R4Q*CsL(u(4YJb@gz8Wgko z7ATtCzb(1|=ld6>>^t!r*6P};oEQN)LMK#+UYy0{@h@Iws9VwM#aR;r*2^k&wG zjDkBfH(${x8nOmMfXpbp+dX5w^WMg=mX(yQy)hes%x}p(ToGf|V+z;_!d5q2`K;qL zgr`zKhV>?xhbQaxf=5Uxzz|VDN*mKGQ_&c$N}%7saJQkeOtNgycu8rz)Tg#8J~2a_ zmP9MiEufVWB~J%aBU2-G+*Yb&Shj@KgtZ5>Jrm8Z7N+|seRZj!QF`6GxwQeg+A&Od zr)1#7@OU`iMokUBoF99__s*rEUpgoccZM(tT5bDe!8 z9x~a1sl-T9RT9vij{AM>SriPAi_^xsNao#Dr~~3!G;^U0Ese_Y*jHhES7s1zk-6<0 zAE*f^gq@DB%5m{ByGkb@r~x9U4XS{U(2+fZN;9egdR7r4b{(Hwh|LrVx<4afaUcY` z)e0gEnPSM|wDxPPtYNv*{ljiy&TI8?PQIu#FSH+>bjiG;;wxn?`e<5q`j*_MZ)s&V zjf^9igFnWrdcQnoFuJD1g(?4JX4GY@04Z5B2>omO*G7?9( z@Rv3lUKCRlzyA`g#Fu*dSrh(Ehf=>95M-epg}Is?pMJypP0RjGo9^ox)?>b~dt)B1 z$9P~3%+3sY1&Cus6>8rXAI!LFu+p0iv4^4CBNGpQ&chM{<{l_V9Px=<()7B#Y#BJn zfJ1OB2w~`ieqA=pOiYQMae0)N&@bTr*iRXdW=&K#1(RiH3R~<;7Mr$5=IZ`zu%2JwsZfWuo;W8^v}WZ$Il) z#(|zKQK^Xj@wx8*N_a}~BhCsVz(-;PvX=V&=~LIg`hT(aCSWn|@BeT|A%q;EL)&pe zlIl=YTI?iA(!L*}eO1~w3j5lGzI(r=wba&Qou{Z42T@!N_}@M{Pu$jz{{))u8@}# zL_!WB1b{2NauYsQmMfqQD){j);B3RGKDwdsKeFe&^G^J_%^l6)ku)%k#fc@i*8H=d zu`Kg{$qFb}cy&gg0$0Ov#aI_O`#jkn|QTGFMDufg5r|o%ij0&3AR?g_}QozvWWc6@(s09Kl4K zTq-a@E*sT}o-9rQDZb>&@!lq=TYV92!udfn-%A2`DBg8x1V z!eY+Wlb#UGFbr&*SGGCRD3+KaJjn_s{7oY51Pk`OzvR^r&(oK;7G9MqfGmSIQOIl} z1#KY;ZV2u)k>Wz??(H{B*#j(wpn^ag2_TFZnvHbPl~Fy>S9-Tm_y-<3rv={R5DKvR zfuQ4BWt%2ODHDm(L`x19+BOAsu#(9`|K}(HzWi}N3s@6L4wOL>IKHy3FZz`O@=w4d z1iUZ;M0dB#rt8|#yDRm_-=X<&c-Jy|?Jmm+C&JtP+;2NV`8W!vH=3%~nL+KCJZHpU zBqvH4Z^e%Bx%2%i09g}7T||(94l`H*qSjM0dCgFAxjWnfn148yZJr_h@Y^tIvma8I1$bErGfp9p9~)NQY{0xgv$eO0DOT0Gm2+npo1in&a=Ux={pf?;Xfc0 zNRyJvF(y~tqi&)(;WcOp zWdM&CnCBmleI1I}Lu?-5VaOtTaT^3p0CXGTiTUW5U!2v#9J?key> zg&R4k8=%ZY^Z)@z9vx2ZXsbfV{chCErZJc7`U{-$w#tQCL`N)07#uO8OFgoCL?lE2 z06361nHW%|1qxjg%#LNAU1{te)y0y0b>&z=a`Ee9xWQYeKM(_fZ13F9eW0QUX zuqWN5-_Yvh*#EAK{^7-T#Aqfo6*`Li1ZtrPs0;7`4ku>Ke#0OOJp7=`v*~z=nKa*Ca!t$imUaCZM~bVF0KW(k26JlEnsoZZ~;Wzr2f)nnQmY( zp`iLa1|VSi1JU1!+R+NEfz#`pm_`g5_PrTzfqw%uyCs8=6?aCo3xJYrEDO;Bx(dS! z^aWvX-RJi#DOC0y#Xi8WVk@+f7&Uv?H_FPABZe;Y+&E0kpaBv^>=wWwBxe`@g`@yr zCn!V+6cKUZ2nRb^LxDhA;D#&A%)pmHr6AUZ&28x`XfTe(;d6V)d`dwn6%Ii(kx2th zMLR7!=myoY3}`7pbVPH|IJk3;x+bPD5$#3*?)LP7o&kiI7>on!lS{!H@J@`zJS05)KoZcH-r-Y>q}61&VEAC1+OcIqJkD;Y zW{W+5B!MR58+6)e5+!+T2SuF6UI$+isGzF=U&Bel9UzlKAd>`Ii?;1xFvcwE^826) z2oQlGpxH?P9QqL{LISHs1TuHI8z>7PAC$euQ$Pg-YG#rG#z#?EOLRTNRB3<+2u6tH z>fe4iT)=iYrNO8XibvQ_)+25Ddd#zYdXzw2?y&Py@rgrJzg`4GWOEL3SFtlPJjV_cyfnml!8~QQrwB z1ynBjNM>dPHL=GVIx@;o6orSD(D1(s4FjtL1lpKXg|sQ~$&D!8N6<_m<~Ctk7|N6Y z8;LEjR%5cH2A+<9HHzIKs_JkC=9Zw>8nU3+4WdMZn7v3)LO_5@rT`>7w5teq*Sup( zG6VU{v==qN|1qJ+KO|GFEPqHNS#QqxO(svc#788t+z6EpZ45*QatG*~C~5)yg=n1& zcnioakt*-(d@u|S%S#|!IN`vvRug@&>s@|(T{|}Mk61o7<T;iSDiwbxKGHq@%l@adth1$?mH? zyiIuTu+us>zwr&i!{}S6+vQ`9vxbt_xs?ZPMe&_~JGyrD`#a{-JjM0^jjqFt+EmB8 z7#R)d83rg}|7t_N_gQj7q>x*F@4cHGt*ShNHyI51*Oxep?(T117}V_|>5tylaV|7? zP{ftV!>nl9T`(@_80F z8AVHP5ZbXz-=Z%vNw+ZS%8Q-YF4^aP|R>@avm`q9$NhO<7!53wcwrQ_L@nE4gE^kpJj7KbrfSQ?i*Bd^~pueUlhjJ{4%0OS{o*If9-?iL6%g*d|HgHD4zV%?j4y{ zr9^gic{r0*2t-s*%*)A12PPqTUienXAgRY#_aQ)e%s?4GJoDmR>ce!<1{{@^Lo&bm z6c2-&_^!*R)i24J7TZyTAHouNS{nN$rOV8%&s$GnrMbI5G`iAJWF{YnHPDVuv{wm6 z+r6|IDG|1ZH|Ep2zG`d}e$EcU>7aVIj*u{A?_9_71!srRO;JX>E;k4h&mgHO4oL;g zh6nSyck3UXS*GLGbGv6`U-VAxXc=9DUm|YegrSpi+s-Q|>K}|eZYj=4@9+0kzOFlS z+uyGF0Uv`Dbq!!3m6nfnjt_t6FGJV9cO5>>BSR$}<2QXeYGqfX@OW+i%45M=K>^9d zyZ!<+Zzyu+iQ?hxdbGeWNUeQ~{fCwVeIspa3$g)>KI~$w-3DC)+^1l##KNm2@%M5o zj+0k6K&UEg{=RWTygtuzX885jDG)q7W*e(etV&;4!0;LdKY`Hjf*o|%Ax(~aAm=?v zr(U?TC_|l6i?HM_QkRQ+=7ewECVZHQiC~VKu6v3x|MAu~D`fQeiP86vvkqrcbBl}D zRe8K?KY=^?&ZyvTDh)6OeXD^^r{ahxCsXvi$2`Q=3GGmg{Eg0rFjGnwrWTgT*ZTD^ zYkjFjQB^HE@z)RXmIO{N0%P^B!h!n}QlO%2MIBF`G@jH=7}t|&hI;4>IkKAJ1xI}m z>``=DtN|?#>TQXSoVAub#S2fQPQhuCBiEo1CtCXca-SeNwXKaUNt;(AWf9 z@jk694Q1EoSFT?=@2SUVMZ60wV|n0(TfWDyDd`&Mu3mh`_TJM!Znk4CS=#KGvsfj!%V6J<+^EacM_Q*A{J6~Fn)^tn`7o6E;xSuokv(X3xx zZ=3KJ&|1H;N22e1boagWSDsP&3zm8=%>@<$7%CjU_7lSuek^rku`1|vjJxq49efO@ zAY@Z=i^};H69uc1gT%w{^-Ch>b;(MNs`>>;V&=p6I0-A#5J>CFD{9IhSX-;xYX!o| zW1145HF`9!{$x_An0D-I`UyO7PO}xew!}JGj(kX1z)WuGzUa^Y1jv!2JBpwV^hJVl zlzAC!lS>fVv0G~J(uQ*Xl1s{DOjtBhempQTCVSd1Hm@eyRy2Xa1cnYZ>LFH`kq(0Z z%{0r9&aQLb1kC_d9jF;6{hH}@sp^RG^7(Zg-cYta#g@4fv9@%a5C2km3JW#xSEbL* zIu(X2ttMKZ^IwI!BX5{Li+>E0z3we{rX!uXR{Q7pl(kfEG}AuO)2so(Sf#y^&TjRy zs*@Sx=%L>kb9tc8hx#5))zx<;)9{(-fR$y;t9tBAGb**Bf{&eB zR+jnEU8cQ%qbn^hke)e%1^sAPyopCz{{2V#SI3SWZo)Vfw1F)v4v>Xy>)jzHdg9jt zX=tr3Rt1ZNll?j?iTOfN2+IO>4_$UF`#ys7nakH)IS1kq%5%5p3Mn`FRpH$vs z$E`|yZ?>QC|0EjOVy~I(?rWUiG?N!8#ZobQpd0lE(P?z<< zF%!4InVhJ`*1Wj;H>^9=JKe@Nwe~NjpX8{8s@^ZYS}xhWySka_;9p%$xvzF35_$P~ z-o;H{b3OI&X`pD~QU}GVjMaECgnySRE-Zqv8SQEyG=_N2gp?YkhuhQaWU_19VfMaK z{1Blb7{Lpt27Xl#2=0X7u`D86?$LY5-j=O?85}o6S{s@Q))guMNKH)72n>%yf^6gw zaWOHN3!J#Z4D-??yLc$!S*I%Y?0D7EXM!{lYsey_BMmM7sb%B2OE193fR)7egF=n# zLYg+jfBEudFT64Qzu(`I)B_Jq2R@Xp0mS}5>TgvZSMBQ0zJ;v8>*JW{hf;zl1^Km7QKaf|O6`5{+hbn;2RO&s zpmIC(D0~Jd1-ctuGv%_m3$`Xm zOf#O@@jHuEp_%KuiVEU{OKV?vy8TqeRzOEtayk*Vga~hlw|{IR6L|z$0@78fcv$2N zP!L#Mv8q%@rN|Z&ydU&>bRwrddlrF6;I_1Ony}L^G8)YmiOfPAl+Q&O!($|ZKH(CL z{8AttzV5#0JC0_tR%b6KYXqdA<(rGEt7AaG$UnnzuOV57BWx4XvpEzZioPH5c;A6dDw;zAKu`0Ah=7aJaUup zScFFf*2@w>iA%fTBEgw^3$GK(4aX-?lj^P#=q5D?y<+GK^^T|#POg5_SpesRkAitO zDXpIOvDH{aycC5pohAO8W|8NIUl)0HKY5d}Hja3TgJYqEEXdaW{rfNji|VT$dq-AO zJ(&{X^J*wCqt92aRte0hbVa7S%GDMXcw^txGv~5nyYjW8?LiX@37ObAsG|MzD(yqc z6%q?gLB9YPAx9R8uw5dQw14$-p;<6Usb**8$Q=LKwa$Nqz_#24t%+`%`{m~!T&^Ob?%6@L#%^73CTGY^6=sv za~sa7()$bH?IQ`D_OxE<_17^;v$6|~lx3!DHUO0SuQlx>hrH&W z-GO^e{_m15`@8g5S@ic}Ogf#I$PXFW48qz*@xQGy(`(!J`ipm}zh;stSG#T+uh))_ zs?N*KhA~6fVW?am>yl%4P5Jyvh%jEdtFIWGIY@uX9&?-z1SDMQ-gd&MMIO;jC43sp z($;IX`8q;^>jGtoSGbhQq6NCOISFnYWwRGnI+_y@)Op8ztWl?U!{(va`bijDfOWDbN`3cA~4x7FKED66utfiq0%; ze{~7to2_V@xexIHmS!$nLTh|vc{rK%@a0&iUlI%zGzy{?=!i7MM|ZxeYO1NFy=Od_ zXe;e0X>v7&#R8Kfz}Ga8&KYoT9}r*;KH0?I)}x=6#rX&R7jZ9l+#u94am8LR1FYcv z2)M3x6v28E!3M(cgRxyXs6CW9Kkvr6^D9AKf*k~X1idlfSiMvf0{#!jvehs6Bg^6h z>C`EH?aazQVIA^ZoZvD1^j;-WFEFFtISp2Xr4DCeW(p!u_~u&>rJrvp7!LF!RoF;r z&kf&-TvUpKQO~7YdmR#z>^(^bh_eQj2E!&S0AGUKdI(Ad$fx5e`Irj&*jUgQ2HzCJ z^a)kIIidD2qpNF7vq!UJtWZjjfC6X(=la!~3q}U}j==a!2||Ox&q!k8tgTsof^HYn z%~Xl(|MYZTsc+wQAPtbRkt@u?XD!gx4oj4HfX089jj_5QcB^}RL9rtfGolgoXeM`H z7(}wo28Timgb$YcblMHq@+V6I+UZB1*X@wQxdk}FL!?Zw_9?6bIm%_?&*dh-AWJR| z8ZzL7S&4=7^PGiat@1~Ynfj!;e|2DgbU{7X?VHV>u}Zp>z|j_?_F(xPOt?Z+4c@%` zzxTDwbA1>i>&82ldVGrCU8d}HQ_A8y|J0kruA=3oN7O^ z_Zy_l^M0~c^5w1X{r4MPqnzdl=w#Bi1kvMKRE(F-m7lcpE$dV@4?xKv{4o~UMd-Pi z6sFgth7yMp0ga?6YQZi@eg|5a^tn8<{~~aF?#HN01?5)+XiyNmX32FNib`e{Moa69 zRps!9ti&b*L4p6wiO2TTbo8>1Za|Y9B2iDk~ z1GB+K7V*|sb3bS#`kxIA{{5BBng8#7M3LL~;`p>iE3Nr$J+4xiWrB6kOR1fKk z$e#aHRQ2Ct{lH_L7bI-pfC$d;OIiacq9`L;y6|E>pG_-*W(w=GN+jIHTQWu6e{okW z)SRLyku;J`Ze_=*Or?(9RprG5t>oiya2yg|dWd-!TdvaHE+blgoRZh|b=j|<{jA2! z1-akC@U)a>jNGr@by<}AGvoFrVK%54+zV{PBQb3~OetAKiSGfdxE(F8M#+%y?*@zZ zOi^cC@96z9SSl-7H0#L|?$;FD)0rcX>X24hxnqcAooknn;`hthS)nm8%tEBGm&{r% z{B-(nVUH;FShXK>5|d7 zKp<4>5=e8Q+^;L|+QIXd#O1#-I`(YkE+1pyS02WI=AXj7j$24tr%!QrUQ>?;E-R-9 z&mhGM&gOc)fk}eXCmM#%-NvBI{%G@KF+wS`c@B)L>h|*FvV8WnX*czX@H`d#xzpN$0M0;7XY$kL)bCZaMv8dMZO?AspObHYBU#Cpm9RFe0(O`PKWgs zwn%V4s-nSr9ntXUg7rczzp}UdxV!ST!qc_1%^Bgye_VD>TaHQ|(R?&RaOVtxPuy!Z zpXCuM>P2DcUgOg(+}{UB^8Wr$#&5l@0#hP2exCRDryu>+>!H5p$KCPq2u#7!(_&$@ zx7Ty`s?mq%_k;fkZkoZY%;t)S>pGst58N)WJnX{9UuOLHdcf5-KmLwCH5Y|nEN@l3 zcvfBS$4?nXJcqk{%~sz<1%c|u+~4OtQWy!1u!SQ3CBmbgPW`1ly_ z*Y1FgU{lq(YP-CyFlXzd78Q7L867XZE#p_WR`R)?h0a1sgb3aaMN3mPrrMzx39U~l zuiJR>>}l?PmmlvqG@}A=1g#5d9}e9u8T*$4?oKALcY*83kw1@+R$sw46?H%6-pZqu zg8cl+3tYI8%JA}D8)gGgOC=IM;~$R?MoTLlol&5S%?-Ug+kk6U< zV~Im4uVMcHu5T*qZ!~6?Vs;D$Py6!xOdbvpC(_Tg6pvN78bZmeNYqvps7xo!_QxU z+H(CQCIO54eGIYUiXb$=b^)W=l;3`=!l2k?lOOkwpD})3q$yw&Wvl+Vo_nRu%XtJt zeooj9o7}#OKLjWAIgtQu>Q%O|Ks;Ye+4!1?~U#m;rOWWOu|KZcjU*i5bt8J zj*BkLwFnMrI`ZQ~e23vdI-AAh6%?s*DHco#qJyB%Qo#YoiTOiEMe!Dp@yAU;@?mSEMxp2M?mTn`Gu^ zvGg`jB8CTOZa5hcl{WzukE1SvwY{q3QirdB<yIiA`LPe_FY{b} zxP;ClpYq?jSX5pADmyTZaxPDw7#%d1e{EXk=0D0@HO@BATqJC@Yno>8M(_V*JJT#V zr`Bq;+P{q| z;0A@=Peayy)3vTDLw}h#X<`uYFgm__zOJ?CQa64)!QHuX-ECWXWw+?^4XzHO z_>ji?TC~l?MCZyw`h zL8!q0?3If2Sr%2(q_wJ_U*E!kGIC}e_! z1nG+Yksyc_aK=g7pyB_0FW5syBD)Z*gpA~eAdLtl7Z9Jfudm6O7*w;QBX$ky%#Ee7E;z)MIzB7Uv$!P`Kqp@H-S0_Imn_qFE0g;DV zz_SB>M@bI=c?gSk^m9ca4d33a^x%<)((ROx@$6t!VJ@cjA^l1GJi-=iuRZ?XKEF68 z#RkNB6DN*fY1FnqXQ{7`(?Jres}kF{OvCTnP9Pbl*ijA%IQpPGc{Wk$V!)fx=m!WG zm_QaCEKEWMd5Zx1BadhV4Ic(g@QO4rbRq-r`WWaSNM4SOv&*#l)zfn(h~a&yxw*;! z_#rjqEP&B~vVjfse``e-mb+lYZ3lP;Ku2aS#0@xa8AyKq-@m-2Jo$7ysQSmz;p2Od zc0nL4vnJJf_W0!uV*3>b4Q{UDN=KlhKsa6I)M>kWZ;?neTaT~8Herrp^}#QF>=BXz zk&TUj&Po@nzKE;UaWy#09?>J-F&JiWUl=Ly6)eN?GFXKF!> zaiKA-lBKngKx*j91H2BJh_AX)LJE$0Lu2FB9s;%5SNnZG>p{)EIq??T}|gqpT({;0>@$kT{$X&EF}e$=Qa^$y_(7+!2l`RpZI}ey!#}mtclQ zzkwJt2bQO4BKGk3{_=^S8>brQPBQ|jJCQ|2o)>u_WZ~262$)n+h=BJ1MzbUA=U2Y* zEJV5k3k)R%Kk}_TXhcYkJ0gAOmt-7<#(wk1i2hI@aI~nr%z7~F>^B}K^0JkN7MS3t*SU{^!wr~NeKYtUDW`mis>JtR;X z?kAWb+xST()`e!cm8zs`8ru)j$@&M{(T8<&Ao(1u*8p1$9`H`7cUcd<>YZ`5J-ZxTKTDqRjCe9b0Qg$o4IH$kyiI#?y!A;g>qxG?fKp2N|v1UdE)34fo z4tF2((=#<4bqAX0g_0d zGYM55gu(}nfIpq1k&uCc96~5{4Q6hWYR`1ZZylOnc~E3mkL6jIpaZG5!F7R^!s+Lv zKb6o8+b@YjX{hVh!8|PkEHtuPH~{^HxGa<(UdW)r5Fkc@sg>Pt3CLexq#b!p0`UJ{ z-CqF51j<+%N!|{*70yxXtQA&VpI><|BNICGtvxbzSPKwpAXxT=7KnCCV^`7HdUf~A zGj{xPRQ^;0A_k!y&>miydZH1Af6`d$(3WH+T^K9iVZfTzDKXM7w(wW;)-Z&r-=2n#hb#h|6{(>ez0;M`=)Jk@u`vIjMge%P=>Xf3)XqXrr zG&g!;8uIx`N%UIHZuUx%z+Azput+a#Ywrq}Csd!p0YPFC5)HcXnfU1kq5?ltuaEUxDXg8?_5GkV9!#%x?l(X-dY6BC4B`^+<6-Ws*uVJ|c z7#e4H5GK?iA+6pSK_ZT-12jDHVMUQF&EwO8n_l^dzTToe1-qIT@P|AQF9<$=WSE)Q zy4sAy>~WaKJ>J1PO4{mDSp}WX>N=I9B(iqbLKJbT}6-G%O*pRjAvn6Gn0qyr+Oq z*I=?`&)*uh5EcM;?psjVTmuOSiLZJg!?lzK-OWhc+8oQL)uFQ;xM1rjtfMray2J*@ zT;y87+R_(+G$32~k%2L6*ftA!P`#q1Ro}Xgf}mq~MmbxKXj@Quc1>AfDfG3>`OihJ zORk||qT!obY@`{Q!}KGY16!0Ohg1Qs@0h;+{B|VzgesHI1cC5`)@I7)U<%S#OBRzn z$Ie#XB-&i=8+M{~5v~v@QVDJ6f`%?<&D@LxF1TsOGkZEa69a+}<$zYl@q^O4F>Nai zY0lEXp+oRWSVN=lSb@$#n*HOF$@xOflE`bo8N^5>@&=d!xB+Nft{t8B2vs{&q@Rz9 z?)J{WeoQ+sWZ9e<#Z(OdD2jt=Z+@PN4wi>8jv!Y}wA0&b>t$j76ow7ZZ!)$~$fMfWB_;y29^lMQDj_-dbz8S7BCQ{$E3#(E5&y=S69yk9vjHoUZ96jG4&E$3Dtgrnwe#z51lu|#I z>8Mc@A~bck^ct#mM44Zc3?ZMn(K^yBa4K`gv@jzE#K0>l_W{I1mi-aIpnqU>fPtb{m$fGrc zcLok4g$2k8DGS(rRPKV?z9)0&2&+`*m+C|wcASr8WDC4<}t;jqEdFK@HW07Y#rks6FHd@$9~nh=3~oX06>ONS`NPpVIwXM-!A$N4gs_x3>DUmFp`eS*m0Fef~1Fm zEcR+Ql2jsERIt7XX+}6J2TF5Q-`K*C+b6&dj64D^m?atqM|UK(=F21>jyMF4E|gB1 z%~yAo2Lpf0%+&@{5J7Ef!p8F}e~I`N{422Di6(?jbv^9#%lBGTQeB1RPgUC4jCPHD zS=o_|UlZ&Igoe1oVN7j$fEuc8h;ZS?5#8a26Md4{rgz86{={;@{1YK8X>7n8RJo03 zp8P-!`Fj0+=Y$u>zxs+6u#(tHbQatAqdzIruT=BDfa?fCFTvX^j=l5{A4VbP?O)qCn1|VG@I(5WPSOou7a@Ye zQ^IN(@^^U_zC--wyr~up(2gR)W5x`LhmcoJ4iV;8WEcDh!b#u1dQG*Z)recjh&h2? z2wPx;bjugo~)@0n)FXr_3-w*4MPNYWmnkfs02TVk0A00FTu$e9Ru2NJxD3NFr7dK@UMrH*|+bw(iCfu4OH-ly49UY^okUvNZVtvZ57ao|UJ}~21d7QWqqt0bk z$IL)en+5X*H>H+#dWZJg{`Bx;pUVRNPCEvfPxFC%c-^dhhD^S4V@jw%$JUIeB96_waXT0w+OTMdUr{sMf5VWby%_vY@`6anpfp zA1$q>s;Zw}cPOCJ`C} zwS38B0mlJgg7v}=5$YfX!VUuGQ6*AYYyl2Rt6%fZOV0o!)H@R{IPtY0f}`R9CmS5U zXlYYED#$l1zg_y$xCVIa2;~T8G9bVl6&II#MogtP3(n{t8QXZNYAs@al}OYL{s!xL zEX)SPOX+inc)-j@t<+NgE}vq;O+jY&Rf12Jvo=} zJtP`|T~2X0_R|OLYFc04V}iYupPvf)5F$Ms3xw-16ZFxx%^PndM`?|lpjFH~7I6y9 z*28YKv>XW<>B_dyt{B8 z+R;)AS}FSK%u{=AznvWrx%)T7RTu&`f7U5Ev3gie(^zC*Fpm65sRQ3zc96KFWCwZ= zAAeE>xSJh>_>a&xw$C^u!|bI^N}_3g6)S&m0UA5{u?L7^SF0_=iVY5O*ymzZvLbV3 zn z$J+a~d$h(RlKO-4NTlG^0-D;QV%*c3{z5Tn?{J4*)Z^{rhia-%JEdO``>ZiAgH1Z; z!!SK&PDAB!aUepLmOurIi13$`*`jDWlq4N;h zqo~;RZ>9Nr^Uc<4A1b5Aeb0MQfNp|OIy1Lj*2v6RHSqnFL+|$!_8VQ2yVxU8u*VbP zYg-_e0*`BGX^qrVu8n z_YWV+H3L^0c4T=})%jar8mi)Bn0|mSQTR+0Yn*?2E3GaKUxRjn+p|!R_-bfxGEsUb zjfj5V-IcXUG1BM2MzmLgt(O#Iu_#(#ltIuT?i+MBIt`-nkZCzP=ew_i6dZ1%Rwa(S z_FtyZ5`l{NDujwqR`)UIc0N=wXoF90!KY&S)k`p6kB~sdBxlJjxv&c*QJi`<*jY?p z$fZznR>yNz^MWFI}4!ql5EQiN_DctT?v$ahcB?PE| ze^OYxw~};4GZ^tYePWz#c>l17z_z8Ee8l!3`Z%+F8;k(3M-{PP;hm(t!UA){Z9WyI zs%m9l^GUkU_)ouejzjZtQqSV48M2{w%B%+X)_qb)wXn8)F2c-CHQCZdUFT_#k^EFQ zm%nUU-m`o|<3DQ-O1fJblX~cT`CV}=f+pSzEPfn_PVy-Z$hl#;FozE>)yON#izT7H z08fnthkTmGPYA8E5!H*;cyivaOkDINovsiNx&;SXK7gAyc0arR?yn}%>J;GF0Kp3z z|L{ba3K9~2N%@z@JT^lQmin=FBh`IS=DCtCjsY}2M+13#dftN9VU(WR=`pnzjL4#U z;sqWRUB*+j$Nv?pPO{XR^2cO*-^Z!BoT!$5?cFC{;&P;(5_rr3?v-de3jlbKIO@} zUABy3`-V&-xwbuTDf?U*qF1D*X()r(rGk;Sh`a?}ku7r4!uy3qnWzPZY9LEY&q**r^4sWNBfMbtyw)Q#*Odp|@Uu8|Kad#F3a%R5qvfrQ2 z>|Fc4nd-x^dD$PbrrlcMkIONbH1$Kj?(bNwd2Ys!f8o#aLvmT-CebyLVH3qOCAsN6 zMlf%Ve5uXx10z=nd2ctlMiL)X+Fk*)C6z3}-HFCur(sKce3vJmXYL?br`yTGJ-~a> zDU0%?KOU;KHgE1*a`E%5NDs>?D4uUAZ^}Nt%WfgxD}9yLVFy}R(>&KTBI;|+rg$B9 zco;XKs)L(T$D8A8lL)iDxLX-$L5}_Gap5U|Ak>eCaZtb(YI%{h$KDQ6s zp%!Gc(-VgSjvok!#nSCBh|_rLk?MlYwJyDOM_fhO?1M=9>8Y!@$BYc#=TfL}?Q7|J z3Wb>v?z&wk*63^db%-26qDovPA}<=>lqI!6ZbWm6o=gBlFDTRZjb2OYDX2neF>*c zq~8hIa@N!+?tkco^=cT$jxa>uLWuQ{M_@+P;2|<017)C6}Y!~E&$hCJ=uM6 z?mso|0j#7h0S~Y+LuZHDj}0on;iLcOk^+YpS+jj9&`Ji=7H7YctCrgefiMo`Q+DZv z4MIn(%=yb7GK=J2fBEqRd<9iLZ8>^=;mXyg1L_ULPBvufH9nKnE_3QCpD?W{Gyf)( z|6dkhL;hBGpvISW8;{kj<9-((AK8whp5_+cb13w`ZO~|$|4`g_dEjxbUbk7~+M@S< znr3m|k*7BngojY++7w*(M}rT;p>STaB=dJ(t+6t{&tQ7rCiml=uN;!U2{OPuHe}&F&c=qs&X-E~-sG4-KesOGMw%`rjTrx{wf?zFF;QQ;b|dDErL< zE|5yNi&KaiU!wtSS>t#}(-)Cz?_BI>lP+0j3#M9>-Sc5+&u<=~=LlFo44z6&RaI#{kv05F46{Rm?vnP&7t&nRA8Lzb|eIVMT@l~HSv!wU6Xzv8}1re@X%$;HL`I~x)(Ks zEo;fk;n#C?e>#>kbw(^&Hw8ORfBN|j7Vaa~*q@X1UCh3s)i8+9T%2Z;Tv8lnOS~TX zFyT-=@^ERTZ&Bx>A6G2@nTNXCI1eK6BqsfhW5BYjOsLy|yJ^O5v8*It)n;0(k~6;GJVm@bK-0X6~8s>bRpQcZ%4a|Pnoe5j1?H5oYF zVYCef&*oQyy|-s}aSKjD(|Hf^#!Ge!L(RKLK`~Emu!7+PT0>68qgbL-n(1z1zTOnU z6LjT0Fgp(}YZX5`vh3{w=jsj5XRI>Fu+H^ibSDmQ&sKpok5x5_wN?Ksr?m5TDQ#JJ zTyq-pGxZyvO)UmrB9jypJ$*5#%QvdGxwUBsbU%a>le%k5Z;&m|G8EYIDhUhXw*J<- z{_*$f(;b~_2b7LqYuPksSpmC*7M6Kx>Qeu)1P`PueK(x7dqOw0 zPyVxv8}+tKk@UTL9i}*@d^TsGTS5bzV`CY9MJ`?CFRpgLGg**gp>_Xl_<-&W>Vi?v ziYo3R76kCDM)$dAs`-X)fpZh9;@xkQzFFJ7@MQJK+bI!zWY6<#>#7u!UfITd3z!&j zYkR~DVjUmuRd3#XVSHGitNhAsajt*LQ0M)eV?aIs*68o4UJb_+d~Cc9R9WoGzb{_k zUC|Y?rA3Z=I?^xjo?m;p06vT7rr-M9%(wKqJih2-v%iv8nV;f)p8H={v11ep#@v=F zRs|_$#xk@U`+XShFZ(M*oZO1U2RASgE|okBp1Pt-cXBxJGazFpkOeGi|}v&I<}oKk(nqO`mivc9Ge zBUmSTCnzw4zl79$S?6Ee%Xa@ooaD9=Hhu3f+pBpo`SF*YP12Cwqg#5FcF$|ryldKy z=d^u$5o>2^W^QhPd<8;sJ5$z&rF`8v=!>-P?k!{lkvFf3|4x0)y`M+Eb#@Eg^~T!6 z*~zq?TbWYW*7+F)$T`o6W9TskP_MG>OV69-uU# zyoQs9aF{hK6zNMTI(vx#aE#cN{ln{++||7n^{-f$^ysZ2)Oc@h$YOCFx7p)=R8(za;?suWcMC@KsJ>h=E_eJbewu?~GKb^P*f_H0&a7P!7<9pPw&udFa_-$; z;fZNXex;?3yEc~h+0P-7E^MzD2#-8TYY0mvqM;JYx&?vp?;BINpDl>umH)p8`FC@k zTjFM=TX9mqpuDtcW_M6hx3a{Dp4XIHW3zU8ZP&l7%yzFhoZt$g6zy>2@yLKHumdHr>2zr)+Ea-3<0 z+1!OTQRY!OClmfv^gDHWOLUjdO%k#GYfyOFvY(upJhjDqd`A0Q5@~6hPdIO{V0oL$ z3~tKhS7=b`DrfDUb`o9OlQqT7|0L$4N8drW$**xB7)GbYCmRM?ZEnA!r}-8`{bb2m zL{R{sb_%*sQX8&xOu3VC+u>$CJ);+PVKnT{#6&`X+%UW5%3A8#dGVjtp9~AIrndVq z$}AhEqQ{eHG~|R}Bl@W6`{*_CnZ%V|pgqCdnH)u9mp~0E_LwSJ49(`)Zk4KuHSHl* z1H$Xt7G9N3z7Y^*JNiB1xapGHbKYBUgh*N9g0{u?B(5Z-pWE8I7AXg!ISN4+MXURJ z=nWnhFtV-!^aB;Kuf4qok=*egA1wr>=ROic?fKhY8$M2>bOeQbTNAQUdw94bv!9sN z?(J6PfsQ^XsRJPAOd|~>v0IC{SK_O!*X%!e#KFMkF?^%KpPow((KCD-Z}C^+WGqNA z38?KVZ(94lX37e=izB;&B3)!D(H8*eN^lj?LPGJPGe%btG(?eI$X-QfU1q8`Si4=p z5)o;Z=%tGvy=JmZw>$#ASum>BP4?!BFkf4`|BIm3ITF^1pG?l2oT&M^L~z-ea0OPd z%?HJC|D@lV^snUbTUXh2m7kvS(~oUS>%9S1*uS(O&}XqAAjizD%rAT8L_kcs6-d); zQ+%z^`za3{3jUG?b3(yys=9@*b_3yZe*}GT8N%FsHSa1IhoeU`nVo*-JD!l8XG>Uk zOhD?^J}Kv}SN2NbQOxHuxO)g~tbqI@wK>_Z0jTx10+0X>%ObZ(Fi4Rr0?G%FF*#@; z{@YNAG_X`KuaUq&E|LRT#}16TlROI_z;b}x7&K5%TX^A=)Eh8y@apU9yBoDxXa@?0 zQN?}tkT$SNP_WOh1hMh;!(RpodxOtPjxuJ0+=JghcV?sm%lkLi;l#aVn_ME|k$WNM zQn5@`{O2TjG4(Ds9~@$ZCmR~VtmOxU{aA1(T)T}|6~{#K_ih{>3CG~CQ4rQ*rJ+e6Na zcy={$boJT~hAtANz#foB3IQg7Bn)vzF^AyMx0586I5M+nD>pgT4Sj9!Deao>?hN7} zY`+OBgDz~}!BzJE>h-;^O0ol2BKl8&{RbNf)Yo0*-v%7tyYlHEY<-Oy{}Khb8vOu> zoE|!#i!E4HI^WvO*_j}pT4e%%80$Q)b-lerG~%Y7=Fm>iitbCdTaUL|fA6PSC&!Nv z%4AJ)AR2aj@2uYl7RJhXCzzh99A=>LsQ5M`UQF6NEy8hKF{7gA&P}q?15+6hH1T=Y!&b(GIf*FB{4GgV zJt~o4W1t_K9C{1LCR5;ad_{6kO-(JD!!Snzl*p$amIZP~q>f^9UChx1^YG1s1aTHg zu}LQgtMlIG`mAvDr9`tGFkNr<4)C|3YwPQ4&2FZ#m_l;QRW|aLjME;XVli}7B#77?6Fa}P1%@zc#ay{Qo52VC#+8H*QLkd_{Z$q1+}j(u?}FFc68^k7IOFK-Q<@%r9)ZziIoyiWG+cA6{&YW zGFYc7;M9M(;og+h5H^ROLR)Fo^W&T|e0~!i#n3!LUXWwncQ=~oNrYAjI541uT8rLN z7TSU815OX3GpPH>I(+{y_LOMKgZ47J^smlblAoX7e@b$6D^Nr%1`?oAx>4wddbg>` z63Pt>QuOTbEKHnfIQtanIr);9Q=m)~Izf{me-36BZLBM^@4cfkc<{&OKTo}WK6O#V zO$%z>ix=lWeO&5`LKgpCMi`pXeD+I1?s4DULrZ@~(=+r!pO*UsIv5i3%4Uruq!S8okaCo`MRS?}DrM0UC!ZEpOz#5bwp;;S*ehUaf zSJn=cYQfSWCyZm*#=2rfNC_Nbne*rtn9V|MOn>jk9mwzyxfc{H>=zy@VJUa@>zap4 zY6i+wd7N-}2U{~8xl7u>-JU6m-h)jXu-Tm~cWb?hcYxU`wUdScHQzupy?gF=r%e zcfZ?y^KY=Lp`v5RcHp?85gGI?^gftrp#I{~E#y=2rNq7wCSC}q=a-1Tl*w%z-3Rl7 z=76w6VC2h@#$vwaJXO{p*@~L}){|57x3jPSR%a-1eE(w4hNZ-4x!0h1Ax+y{;&-LX zKsJWZRPu4zDIX8rSE}{%8~S$FnO2?T*6=34%s-@|(OL9ad`H|Yr`k|&V7%-R)7yQ1 z;2GfDgJfwa9|w6QUP$0CzXXj3#_RiAiL+kTG7G_AJ#_!72D+UbyOfUt91s^EqVNt( zzo>(hw)B;zs_I{NyePkG_xZK0jyYwJO7ZG!bImHR;xL%3RKbL&4g zoQs+w$oJR&B7zYD3IvQj66k37u@`(o+of+VwwQo;f>x8LDFIV~V0%zKyW=GePYfv2ndKQ`?!eHb0kNO;# zN4%hH(dE>tqbQY{IcdnEp&HRI;{AbuBoN%7jb>)+QP6qm{eluSNzNVG=VtDWiVUl zdYxz`2Rk924HEvpoE?QGxAz?|CVJMXVB|mbkRxZw2PRJojLOaHbh_8+^$vb2ndNu( zV6U#}QF8$K=zaV+WzH;xTKjNimgDTzf?P0M(Jg$)LxwyyXQxzv4BYQf{OTX|4xoFK~@Ec4SKB%@Q z+9#q~Ffg!H2<>#^$ad=s9YU$>ASgpA zK}|se|D@Ck8z~q)U||QLg@0$G$c5|)v!Pv- zo-{_!u46$>dY`_J$ieeMj6%Ed-S|G&b#OmPpq9V9{kcRaqVL4bn>OuPHtq#)S`HkG z_F;6`myX0KP63{`>m9)QiAB#i$WmHWGZZaAf2+@6rzE(#3Lh3n9Ypq z?ox?_D@AltI^auzVyBd<2)RQT#HstT$_x=^99Ut2Y9aYJ15CY)jLTaR!rVfK z7=lYvKcr1zKVYu1gEa|Rw*JQE(q|6*z0y%SE4*7&G&kEId*f4& z#4|?>YLk)!HIp^UzL%L+mPcm<1u1wnk8sa9A6u8eC*mF`=sjX8X_cE_`{UtG8DCY` zTk8kA#;FcMNn?9RnQlnM#EmI#+{U<`^M%S?!h##Iv5D(M=FXeXuX6}oorc|O<_MHg z*g-cE6TiT%M>tI%X=Z;%|F<-RC!oHIWb8kcRYQC9$8z8O{MVTp6SZtmH~@};DE7A* z1X6BkKPp1u5NSuF9uVG>&ja|Ilg1q?A5k1AeDh}+2Zf#_fhdUHfaaH;(H7Q(u&&eZ zHMv4bpxlcv^6)9=h^1CxASPT;kcm+<2X-BJiN}{;IIf{U_Ebv@dRxLgcWW=&ZK4cL zQS=LVj{fMw8;(ZVJw!`WO!Ff`{5(W(M@^qaN}I0~qe1@e*+(0A6>fzfwjG3snh z?Q=n?!w`UZPSrKr@dI$^!L!7{M-{q@Ih1Y^)9&>>6d7U;BFEMdv1{TAPR~Lj0*V7; ziKr5Dn$WEzL1E$(P^$O~f-Dpz);m{=31;AZG5bsp_Jf&|95RmH12sM4-92eIM;7wQ zb37JsoF;4tILT+wb`THIZAhqh&vTEUFxXY)Z)90sJrzKCQem$>lkl&%=cD5)#E zly8A44!Cn?u4oS)79=IZ#=ux-Ilg4ZMGl|%cwV8~acCH{iA3pxU!DoAl`6k9ZU3Da zE(mQum1*JH1XtKb!!rOMQ!F5|U4-Ky@t1%9L&AtKfuJCzhk%qAAi7H>GS;XwMRufO}PQX5PDcH2?< z6WPyJa=No^H;nGi{2DQXGYF;k{zBG_4%16yHtcJ%)VJj=yoEJD%&LE0RHw4%!BsW! zTUUt{WwSBGpz#2@!`~3I08)-XUmR9@3?MgXHkKPP4{v3$*b5LBC_DMhy$}kpE(Z)- z1(%kfRv*T@$p$g!07F1g@sF!J-;`&8`sL9c-zmazFnkZhXrM5q_UvHTiqdp!%K(9! zMnGub8;zt4*DG07*g*&&PIX?Q2U1`GmY7P9@8~LawE%YjY&cvvm}z0oV4emfm|2T9 zHa3Ps3~yPVEtHVSc7&^Rze=@d)8AvsBe3I;9}Ve>8e`M2t&rH|(1N`WZH>N0yWW+> zv^zk;+DG6fgG&cPg2Xz|Y5_I+LH01@Y(UZ)Y}0(cDf|lB%2c?VfHJS2ew4 zKTw{-6}HY~tF4CeY7+XHd>Vp!(1Dm%iSBs#?Lg_E zuNC-EP@(!eTta}qpbB}A0x4Ft7{CD_HMlYfId!^_orzt;2IUEQ2=X;S*+9QY5Oty< zTr&abRpti=nH5>97*r6yw2my_3)&1+#Nv$S z0SqX-%W_OET#%t<1Bk(#sD14!LOu_NpaJU0?;3f)@8=*i2LTk~#hI#ojnKrI>*7&} z^Wae_3~T@-dYb~&hn8Sh0vPj^8b=@k1Xrms8H5{JU zfkjyS8!*8TmWGO_3b($SNI%!8wLZ*A3xkxr~$Brt9Be5Hw5+JpJ4U!o8cBE;B5Q&gHn-N^bj$3jfW5lgXI5) z^HrV@SB7{G1k&J6L#nGEWT`=BaL-%d%Z3BpC!flFsZCg5LmyMpw92%k4}0q0rBtxD zBkc=h9W|R!K^4}632Fc?;pmFi`52oYEm}dl*8C(Te1)wINC^)`g!ZF|1 zxI5=DV|Y4N(EChui|I|f&`q5#x+97C7v{n~_+0eY?e5^vRSZO+BF@vCnm4cr= z@__ma3z|Xz3$y1&LDHW>FFpWi{oibL^(EmsCz7`gg+jIWnDXB9FRM!p%>s|YHJZ~R zM6FfN4R+Up)_!NnJP-w6ysNr`;x}^wcmZ(wVgtawn5%u{B}fQd>ViV~9TU+buG(e) zMVLk-;ZtAKRJn{Rx@$?cTCHE{*<5q^DkWc=`6MLzy9a!yd)}6g>NA+O_LkC(az!m`f zGcB`Uz8A|jhno+9I*?EY)y6@n<0~-z091zdFL0@d)^}!gP4yW~rSA4Bsv`@y01k&j zu|z{SyXOrQ-1P~I_n$U}Q9+AA^!J7g7#1jCpkHyg@dkKYug_M*o$e~eviI^7WT6Lf zMS)Y5C)`kzg(POk+Mkawz|~`5b7I95BMJjy5c7hnwg62*eSBcLsDI@s98%YY^##5F>X$qIawsllUL?lC~qoZu#@IZt3^jxE|<)PGsyV3k0u3@01o zyQ~!A)mqEHd;WU(Gtgn;+t19GS~#Aaq_`VP6lVm{O4|qTTQ@+3hO>`Bg8Ck|dN+-_epPwoJ3>C2;p6q|k8qRs3 zGH!w&Mce2Rg!O;Z%{|Ue{h;5v*q>_or~VJ;)XGG0wl4#< z7!Nr$u*DWeOlwT3mYb$`sdAC?ul-E~x&37IxTf~RXG2+CI99BIc5G#94gd2V;Seaax|N1WDaG;|LmEG$vj<{zlGeQ?XWLBqaU=fjedp^3B>U9y9yvTT`Fc!jX(UXdZc>o-k*TvtD3G#K&q zl;t>)a9DP$OrerYc-F+9Tkh3I@!exozOo$jeoXSJsd9dmvyMCs|P##_LxE7WsQuDYE1Dtl^f8_{MU$Ax`~2JS;mqIJi&{xdAnBy z^KG32-Lh_vlkLL=ZF3^Rb2PCqL5Cao1~Pm>9mB8?9vZKTEL;w^ECDW@G8KW&fY3p` zEq=B;-e${#kC%4bQZZ2K8`Gx@>kq+=5SI)tQcC+PygDleM4$)ymYf< z*vTCaOPr@mocAOgxn7|)r2t@{9nQ`+i}K6Jz(tNdIka0!zTNtRmHVakMn4~1=|8yg z4lGSCVZ5=fP{e9(vaTIUN>%o`tx+s;lfg^61kA}WM!C)LA8m3Nb$7aOdZTZ z(^~}8f2D)!Q**^q4`L|xHLaQ9>hT!LL<|q57MFwq!;uIYB%#m$FZCo;?}{K^GE2pd zv9LKpbA%Jg``|w=$(exsS{8XEH7(&@~VJ5RBeL+*1Oj-$J#HWd1X zoK%gBEpsGZBAb}uQK;U`TbzNV?dPEn@r$~i!yE6{?x=;NJ24Mu&>H_%t8fSHgx+%> zfl6NQByFSjf$y=4;<*g!Y=nLI@L`_jv_9aaO6FMm__#XMX@|LCdyh@-<*^&^uM#X9 zC#s?g2FEBQBcnCdn`t9}Q9;XB4EO?wIpHG-{|}(FJxEY1*f?ngG+%@UN6;4!?nU1@ zZA$c=RDoZiX;}#{#gWS~Yh<9G9|)~zSm_|d$M!`(-yBnJ1MK0VZa#AbSGS}^HQv4Q z%lY#U;K@QR4`H=yWPm{+{s5b1znDu&BqXZ*%;pCN`U@%=8cZRDNE@5YeZ2(U7A^l- zB7h!@{fv{&ySNC!WT7$uCQ#79wjG?^^U$Oas08}|>zT}RStky^Jcg~Y@X!6!kI-@z z3=l>lc;TEX#pBPV%!ZqQ37`W7CVJqup7-z&1y2BKt;yI_L{8M6u?4Qd7&sp8Ys9CHzQ?UX@BM2|Q}4nkH-oL{!?I+J zsi5F-r5L;-ds?w7lwcS4hN2>{91sb4O+TR7!-FTcJZO5mO1&-QD)iG4jfak*33Zs% zzz0BgK!h7AA7RskGaisqU}1Gl&0AO#$W76nYg$xhu@f+$2}qvDpP#zZGxRxMs2;U1 z**N6L%jF=S!1pj+;H|x6571~p6pVRy(q(mTq{9{Tkn?cI9X12wkE9w_X3lw@7Xy?nmVW3Y} z`n9fv(=m8wqoscJn{h2rm0_jleS9P+lSvdY*b{gZfWvqPWQB|md=ON+RSjyg!2vk$ zrd<|1kk8Kj>rHt&Hk~rX$TXq+E{|!7h6xMcsczJA*pI?g|R8@N`^uR{0GafJUQ}m z4=ff`6NC(x?FbD9);{m&CkfI7t!}(?kd0JStr^Yd+f|e{GpmC{L*7i7w`Hwq#EP9z zOXkKSXCOeMAKh&*OQGz>_W`hlaFG`7UI)A7ufqH-V7>iGH8+4~?tXs8tDk5q2E8sp zTR=zA@xd^E3CMM7&}at%h&r%tFf)Ee0m3)YsW>pDD%zzC*2nM(S=A^`N5;>|i{kJk z|A6$kBMG1vvL%;#F5vDH-N0Ob3fhkoC zFtrICF2R)rSWwwB()RPGtc(ok*J-TbIA)!I(m+&9sfgXeNDBFqVl#f)GBN;Gu>Xf) zz%Bz2y#*yJXxKxlF?+#Ter|iSwEcz7d|C^AZ^buy-4zaEqV3vgstBfC9 zcNbGR?d@<%qop_s*R#V0JtV5TRiHU07q&zbv>Olj;?a{5Hc;4c0+hM&{~=R*h6RXamcO={)H7%G+M(GqD9UAIuL_ zAP`qp_XwwbVzq4n+<3;!A1ru739u@h(8R{amqL|F5Ov7;hFj~afbQ_en@4@L!r*uu z)1<{V!eDnn>}8=W^^upNN>W3LUH9R{B11vG5Vv*pZxp67{|%=%Q9s4lO}$Gx{o$kk z!~`XEeM<&izULz-1lSTV4q(=A)Sb;V-F&cRsZY$%mx|W)+7r5i zL-TeTDe(H^je)JL(}@O#ERLwR<|vX^mJAC~9teYYFIY}CDh1|ZVNv;ix&Tqg$bbz5 z#P;IYM!*38Qvf4j*4n2R_ycPvZ_auB+6Qa+!Ml7VYj8&E8|QR`i?`a9XmSM(ADA0w zjP^{Yf+g(Dn_DiNx@H%y^3o&%7%PB^ptYbSc2?MZp3BEi?|8_EqAF&ur*zsC#QS~E zCGjrnk`fU)sbH~=A+p*L*d(f|C+`-2y2fU@!;Btk5oTyWmVaw#i$i6cy zE6^(yz?uPp;0DRP%j(GnSFUGqeHfXQm6>Et8zUm|u~*@c1uVuRX4{ZWFWo06+A>n8 zoQC^5|6Rq&`Lz-TpNsijyuU)CJyoX7|20EBO>p+-m*dDOOJZl&Q$y{Xy*R|vx|IiM zmjgX}|NI^F9h)?yZusW!w$dfD`+2#C)GeC%B_89r+$8zS^Yv&KdMeG^(<7G%Ilpan ze<-!=)9#`b^2P9kS6%RzA{DDFjdtl=)Ow-atk#|-~>m8R6@nXX__%R#t?Ge0>-Yp;WJZnIMx?+$wY+k83TVOw|kcRC^WFk|e8U zP6S+JuW%XftT~JWz6s0-s_IM(mH}qic>l>-57n4t9~@N>qGRHyaVO3~nLExP)1037 zR5U;zocH7v{Nu68nqeL;TdXd)f_Ju4jq;DMu=dpZ@XiwRD;5vcSnH#nj&T}q z&JN>JSf(Jtf2L+uf6vY1F0pEVnPGQ*_I=7Nk6>k&ej7P;=f#$Zb|d9&a1>!VWs>wJ zX{f~&Rt|0eU;$xaKm%7lB*l5*(~P-u+Fw-nkM|f+&$pbG`u_QckF9}1!l)-*6-hS{Gq1p6{I=X_2AtRq{%6AxeY8n!qLjbIpMp)$2k5eXVf`c(TYnhre>WSluDU1~~M_hWk zbB5|x7OoWNT-RgAtif*p5EX38RvmeH42wL>;~>DU;tMk;dIbORumqZo73SKTxk(r6WisO>1dd=!jJ-DdmFEq2oxa`#FnKUh)#Pnt&2^<6;ZtfN0G)Oz+|urU|@I)<{+zU1Hc+)6%-VtuB}aRmCmiP zD~4w8U;^P2;9uU5K9^btKxQSBi`keB6{4}vvRy?C7mM9$gX|Fu_d})c5|@695`%DA zI3HnIQ7pXGW}xE=wjM*F2GzmA?j`Tae85Rp!aOB_-jG-epy_+8X7*W>nR3gjtv0NGFUEjs?c*@+pTDaXb7MJVPV`w^*9K71Zz0| zPUcAzlqlVp(y(93_gGV;UIIX(4G;wa2nhTEj+%Mw*_NxmgT5D7jt>UUhQ2Vp0ilC5 zz`WtI8U&j#UI)&B(8XAxAWOO(k~*KSABZqen|7j=E>K;eP%Bn2H%pXpvaGXQg|z({ z2O6I=6Z2K8yMgVM0$+OSW6_Y{U_hlGT8#`}z4L*phQi<@0RKL-J#qOYfFtbR()L*= zRAS1pC;>#sj%@@iUaST4!JJa?+rSB}JohI2{6){j7Xa;v1?odCZ@4);3c;MB@25MV zzcNJQFy4SPF9)f?=MVA#sDZNv(W}?IIY2NO53lAts{(HDbRnbwetvMU{_1`r;UUkx z#Om&ZAt)rc_qM7t{avmz%or%A8*V;nF*`&1bG3VVN>&$woGfPx{3>+`OTJ3W_27<* zN(`=h@sOTm?&thxDvpQNH8hbxcK1^P{0%bMHdfvlBY6Jj>R(WtF^@U1d1&m1j}~RQ ziPfkUi>ztB8NGsQbp4p2-ZgavGaJF!#?ors7)iW5%rI@rz>MPuYLY#V2=2(M?v(c6 z`MIDRq%koukrky|Vesm{UWak{8)0XT2f&gT$$)?$2cJ(JxqKFYwJ_B|HD<7gM{4=w zdO)t=4g-6FWOClmZ*~G0j+F!2SCfBJTzKz`Z3fj4&Hywi%mJz8Sdg>X3BpW!r9B=@ z7=ynFBKgZ*HD<+v?;w`&H5dlG9Eh0!?zACFt{h9C^@(iCQM(domTU|i8WjMc!Y%1PFqG-dzPiE8%cW2rz^92>2ctS@<3l3=q`C zsq=nkdA{7ak-0IXSOycnRlw9Bc>2~fP(c8Hu}9kqpJR^pGhX#B>#lCTr*ySeIC=i5CVw42walVcMrG12oy*tjE(o;x{o(dZV zZ*!D^8i8OQ>@URY&b5_)$ebwjTpPB zLv$84W95<}&nbt*>J6a};^(#uL5VIP4l&RZVr zq0F7xsUkR4=xa4Zb}F=(&G+e_@TGW2{juL7ArL~qLYSXV{b}ieXbaemw&7@%3=IKc;4|k*4K)UTY zt#a{8D?|1Q-}`E2q?A3OC=#N7>$P(X7~JzREthq}LPLSUM6AHok1p>_5Mr_pX2!my zf&tGuaqG;`??flMTT=+&f(8fU%jAm>4<_>Rc3EYeaC|u-DJ5mKqpkoNUncL%GY?fU z0RDSo0T=XET7fUko8GyzE9qz7Cr?H$JOIXR-_!H9ei%Dtyz{X1+&u`P4~YD{yXdI4 zg0m1VzyJ^Xd6%_5KD%r87Qb@NzQS+ z57nY;Ap#AW3JyTPYP)#U*ck0et&?vw62G+%(?3{boX*@2n++BX=7-rAIJfc9Qff4! zt3sF$NEn5ts-A}fdemS+k*tDPjLzkZ+sbN~x~zIkY0198s@coSk6CoNWT zhX!LoTaTRwf zsm_*r%DV2Ut?k;`a=KF^rd)9*1&k_0D51Br`IwKNX~bR-xInPU+d!|LzuEJ%JR5?Y zR?hnKQ5Zl3Pdc`1m-@`ozH=5FWZ}R!v_60=ku678R%i;IrTEgF`8bulaVMpoXJnf* zg^#v$ouyF{Q|rw_x@<`BoM*B9-0#)vGZLJ{3AH#-c3Bp{*c74mv9@PK?!F#CxF=w@ zSC@oXGYX1E-$t|qt3Cb#KpbLM5SznJ%zz6)pL1G(c`5LlnJjt4EY|5Xu>P8V)B7&* zljmlIm@vv$pK6s`VEx{3yXQAQ+2#g8x=TAYrFhPI!P|f$;cNPxIw2pMHD|6mWn)Ug&pz1veTVw~-P&Yxc zky6%7r@FS7=+CK_+zhF8om# z!xI6X3t$a#+A-&?{Ta|~t7_=sJkVu9c=_m%A^W&%zM$R#^vI~kKDXt+_5q-sTAa6J ze|d}pPteZuw^HqA$}8jrUv?%}Li7+&;Q2V_Vm?aq3$vHU6uHjTtrqN1)PQiCeB;xu zAy0@1LA)_kp{(uKuNS%3Jfk!O4|J7NnEA|=n-B{y+_Do|$FwAOCox|s0hmXkT_`;DSzz4v5kXx7) zz%Z9okp%4yaIP!^J12#}ESx|W#^@6LrT7Q78fe3ew*wFLyk*@I*5G93! zYET&f@@0l&a(D%=eQ3*qBRcF<4-S(-Z$S7H5{yoU^a6Trbj18*z&gh^0vmuLx393* zieG762Ou6C5eN-~4w88W7k}2sv@GJba#)#CV+Twm^=#0s;gE|T90iJ4(|bbi(#NLX zb_TRf7g8J>ri2EcuiD`s26GYL&0rY>VnLEwAoHzA-gINfpV8F^eD072`!+!wYR>GB z`rJC1S-SVP(=$DvX$Hs@grHB(G06jF z3g<@Za1h*m>BQxAL&{E)H^0UhRjX_Y`~-mb+@LRwGf)SQe*lUH{Dqynm307h_I~Is=59UR8F|feP1})Wmn798 zpB=-2Dq<^kb`TZfc&uZ}&s5HOA1-um5u1T~WBNNAroDc)(@TqoCgFG}Wa)AzM)hf%E=pm`i7Cg<&``L?%IiJ0bm?36Z)zkZLRzjh&h7cnAPej^noH-5!8Hpojoem9!_V`~f+S$t4!GzMc61m!3C( zf1n!!N2@GuEYu>F8 zVaCJ>jyj-AO1+x4lVpaUMl2*nwLt;{oSkE*+~6c)TsIz_v)C}PIzim`BFqFZA9f@H zzX?Cb6q$s8^D17@UVu8`r1cT64)lV+d`R$S0DFN{1N4P02Pn|$GXBlCBGrD+@_?Uy z6e&2AG5@WF=XpSTjI=0w$$HiS5Gmih84T3a_kpCEvDW6f1>c+%<_Jy~*ioW&LInwb zq@hImO@O#?ZurG`g&0Pe7v=5;;ahMr6{ zUGtfvIk3(YsH!hhJ*SN7uPG1i4J^*z4`o=-;i2ekC`%DBnNOL@H*?kiX#i`}-QBH` z3Ks?TgPG+>_pHC5EV>8EH#c4$`fl&{I#{U@gLpUhJ=I@83t8PzuxV@uLUIPWMNnwt7H1_xAdg}a~F@7S@E1_yV*2pjL0`D5JWpz3`Vta&l;D(os? zLNMu-9rx6nI@LxeFJjozV!dGEo{IR?;`*|j2;GvMJ7W51|n zx&EAB&y3*D7KuLUSRxAS_!)DJH9;~#Wt!OF-l#2tot;Hh1)kdkZ|o~rbhb0#8w774 z!;#mpb4khZZGsdi|KIG@bUOJ$VbARxAqd}rCdW=y2JHN1LC-Fs84l3q z#>|>RbxO#Tl>~Eox*%5)y!b}96?M<(%uZ z9P2I~@|k+u*)>_%=j<&7q5|eeG>lHvoopLqDH?{~5(=$Kpx~9URUb9SeJbn6NR_~} z7Lo%;2s}E!_k%n4yf3wWcnGc)RJWY=1iN97k#irM&vu2aVW%;T(UtGyNuF2-bH-9U zvQG3>c_#uI17w1!V(p4Enet(o{XKUm6~^0298)XcPF&yU#1!lNhTJ^haUyWjl=f>! zZVZ|d>#vpmd z$ZBYGYF85T1MQj{<216tCRcR^y8he-AJNiG&O|ioH27W+H-hXD2*NCX%m8QxN2r+V zz3wiiz$EE!sCd`hu>BD;ezKqaO@`!Zmh|P{8r3v;y=pwe!izLw^fvLnsK$Ias2-xkNz@!^ERIFS01_aYT@jI6E_d;rb=U2X|#h#x28_)1~%)bkA_Oc-NZQthLSC8LjjrpIF(!hYqw8+pW!B+q|^W z80d4L&FyXsprpM;)UaR|GEz8Qvzn39VNeuU^!f;Edvgb3CQCZspM0`v+!}GsXhos+ zEBtuTEFEkA0yheSs27}N9e@2jJ?2AY_q2Q&s&qVuD$u=~4kvC&fZS_JqJodP#@u*cy!oB)>jqg&)zn-&ZczE9`K8 zrt_XX9dT3p&++)3OY$&Db7RyXH?lXyJ5}_IsR%1C0L!}qBn6@!bug!ULpni_X(Q?Z z5(Ak(6VqCT^m)5arqN0Vn66p}!^aP1J155xpD9c=7AG6=TU1{2T@a*450t@4M&5Fv{^DjN?pX2`ML}ecjd+$d0JMMWiO<#WkV3j21(FI&CDpj!0x*)g3}?{spQ@`F!OeUe zR>R}bfu5AoMuCD*p>(8Pd)OIp=8racY|mLj=yKb}>r**6l(H9C80W?c$0p{knK7fT*B8`#J}%_|iG;=yKwsc9}r zNmw<0YX*fp>fPB%6FF%Wa7p4Sd5xr%(D)ODsfWSAs|302|LJ7&fkQar1L`K{nu5>!TadITk(J^0nS~W&lyI>=%%~nOm2^PB~9j=zh;O6PI2NXjAN6-bOlZh zlRIH`qMzPU^4)plB^jcxe|OW+*(>L znqdFVQxHFwetmismv&Ch7fb3jnHmRskZi}@;1besmLjya4sKW25w4eQsMbW{>!sMA68xp=Sk%M6ka%T7{?>qRr= zNDDf3M!omU--Xu@`*BECZ+8|lJfeLS_`h!IC?e_42wH<)?~;%D@b+~!wuFCi;xf36 z7vVdp>Hx{Nbn_3J8w9N2rjNaqw&J5z9(9cYT=)u{&RQKG$0_UfWg|yHAT&!D;`|9OV6wqT}IyA_yqNxEPj$@d+uRlr<8=4sj$w zXSE;?^Puv$mWa5dzZ#9rAChrMgAfKn`vkb{dHkY$6_;`1vLX?HPldqHqpjjE3^T}C z=$u*hckwan(W=6z?wJbUF2%%gr+xTCa{2iDL(x=~0MY6Kk>-p{fr#;l5edpS{%u3{!iw=&SECHCTh9| z`K~9mDtz|bt~548?0QQGB+B2osHA&edwKyudUqhcO!EHVz}E8}rA5LAEqJtwt$U~z zW6o>nEN0P>Vn3!?I5BQ1${!}KEg)&L%`4cIF}I3gAKR9eJ9bEhH5T~-FX~1jaB7pN zz}fBNtJ8a^=+`Za(65pMo|A6`eDs&|mx`~0;Z}L@b|fzhA+tXEWj&If2ObTs-95!J zlQ%cQBrnmjPIZm9tqZt91!}=xBSHv!O;utC$y#~%qKUM_ zDv$V-sjfx+tVgA9AT|An$G%AU^`OY*o)bJG1Qz#v4PpPf{;=}RvuZ+xStiq6A0t1e zEL@DwuG0W*zcSfbt1xvt|7>;XSo{^s1yYxq>GUZ&Q}9ZeDNp}ff3ZOvR_m=ryzB9? ztNUoLcd%Gp;M|+JqtfDnSKiY^Lj1M=BnjjQD2%gWAdKaR>QQ$hEg}mo6`oYQurxS4 z;KWa?*)i~{XPwvD&KEtin=zmIIbI>*|NOBm`;Y$^L@V-(ut(HKBMLcu zm8;)~t0W>oB(e4B4=%x4=gq3VkroMbR1$5>7!FmNUQOus6^r| zJaYzoi{^br@y;`9!sy2WHj(A1j-m>9&I}<+YB5>`&m_^8?_y#`)1(Qm-U-Tvh}2Op z5g+{O%fDA;_#{m9`-QngoB+yO(Q{;y3<>{3$u)?eQBeeZ@5m?F0=jQh$s+QIUUyQl z%s48jtH<60(RxRM`Q3_jUJs3~&^1^ik_*`s-#<=F7%uW#}63}j23WVnZ1A+NyctIJ-`yXOMroDNY`FOqr>O;*QiULm$!>-vQDl#t67 zkZ1S)+$1NXw{-LjpWqpCgti$w`&H^Iw-r;ZPLI!&S8CAL;~(`#N}1I8k@|BFYSJ}u z60ua&W;RWJ8Y0=+x|X~DY>jK>`eS8!h{Y|~Z>-*|oVN_WT}tpl@=kv6c{8_Sd2DTf z_F^B$!{{|0^qRD}ZV&b14WuVN1tDUcku7y4ZB`CTM?Jr!an2?;2rV4jS}HoORLSEb z%ZIaK=Rx)?rD2wen+L4iT8`-1E_463>IsRpY|^*5eRw_q;if)+lObJnJc*w&mm^?7 zXnA%o;-n-8>p|*M1$?`8ia%1D8ZG8DRk#c9e>jqM9}Sg)Hs{bw*wCtB3!cy)>u1X! zp!Q->Ymg#2CRZn#`^=S5Wy9FO!H_TxJh~K7X=q4glJ!LbN%AB0!*%oNW|&7nD!zlt z`$Ym5%s=pG%l4FoApuhlY-Ywpw<~a&Ekf(wgVw!5c;VPxeg0T8LaaQZv+_zr=NFen zbIsRsQ@6k7O#DlLS1YnJZv^f}hy2W1uM*efKJo zOLAavz*NZ102noPF`~_(WoY2#yZD=|1j?uvg99(J$9{1gaU$RCllKXZ<+hg;=j{4@h62qH8Ao%Qu8lZ2#RnSu1SpFj?clD{fKm_Xq4dvzRP=ys)J{@ zlK!D6Tyc^1VrMf z)tg;zx`*s#%8i3not3Zta)-Z%|IVg}4ILltvmr?iIX$9>S;YyxQp|3NFmq-NL-@(IF$FD}J9|ivyitwu#`I9=xNS-{e^+%H%&tn#my|(va)|NvtpaT5Ypx zJbipjC{m+hr6xu4>m9UJQEY>KP{jyJZV+0!E_6-U{H$wF$-GyUvy6hr*a)YQ)KC|d z+zcocAdlrWO^(a_;+6|QazomPzNcPwd$Y@>>HLb0Z$o>jOD-zc=)umkA|*-QfcBKZ z>wA*Xw2wS&^LnqOIyQeJ_Sqp}DZrgt!@co%_qyX;|C2wD7s zTf~t`)k$72VZ`+(ksC&poSWR-KXnbDW%#6dKhcV_Ntx#?;sC;TwwEO^ETxeY&(Ydy zG5?TIxt_}eTl?)sN`QKu^L=|hi=OIN(B6o$_l8?WNV1MK?X^2Xv`8h0?Wl(k!zeEe z7|3N&QMAldubcJ@*Hwg1SJCR%{T(KajcSxR(ds#cdBb{O_hlGYwXT{h(h_eI@?wJ= zU$h_6%Q#xkvsOd(VehCGn#b)8?0Gzrq|{H2SG>1kF(HYB*hB8Jj)ysGr+RnjZ}6qH zGq=QA5eMkb?R0HJcN`WgvmJQ3d+g_xZ(XyYI~wDW9`e6v$QLlAgpzh4MR&tHUXPih z#(~KT>8}3{9ge14j-U2GY{JlSRMcBNkcaI#$qkb=LA(H>Qp=KGc*=%{K zNfC2XEB>9oNQGg!mh=7LLFaz?qNCy((+0X|wqIk=2zv&!FjPbc@s_}IFHVVkx(B1~3e(7t~tM0ONcIil`ojlU|*c3Yyp=51?u^?I7Pfc4Tk zgck^MU;FjaYDla7?KYOLpVz9Ft6XzqQ@8Q)ic ztJp`DXx~W}x@u43UuCcvjJ#`a^1@WkX4Wu-yU17(R@<2^D!h)Y5ZMmR2Y-+t&2|dN z0bi}xdKfii;5F}c7NNQ5L|r_;0e_(vp*@1=cR;)NM4}?l=JB3hn=2vDwq-(S+UR?q zqS#L!r|AtCAcRUdwij_L)iUYJ6GzqyN97T&B>ZWU|NrMc z?Q@hbQe!KuLtKcL2!bb!L~L;kaqnL=3uPIE3kbvPHD_FKx+8)wr5~wAf=lXsb=+=B zFGe$v*2qfcr0+orbPJx!NHH#7+|IFpb+JMg@3BQXwjSwyxdid3HE?*yrmndHTna09u4 z2df;SFTE?!nqZmpi_qFjLe{tx>Y=_TKVL#<~bHz&VU zWXB8okxbsO(TvIyCiox0818@Qgd$tZ%e|Dh{UbB$m=b-@Pm8)4U!aaEzG*s2<|MS-fp@`vf@7rWh}rq{?Q2z>kC`=5}LC6ex>3YN;rV9*4GlZn;Qhq zI0*Mp%8l+;Rjl_n#lJ;5g%3cAc+Io*4a6r>zU3>vvw%3E==|;uEJ;dPT!=qSE=441 zi8i>K5?NPyb{JN9auFWUB#>Vs!ErMYJQF#tru|n~k!wVitPSc){7A^nvpYh|t~`6_ zkx|{=c>D`gNi-iJwDx^xHFFPwm-(ag|8*^fNt(IDHo8}=yMfxTY!zXY=ycj}P@@ikpUF@@0DLc6Di z1ohzijC-JZ(eIr||KCh$FxhtHaaOW2(%_xDkZIsLmvVUoA5i%?zOxM3KKY(!I{$#s zPF~*GEHRju^ktTgGi4{+0m5G+{DbWOUD%o7MTc%>XMOj5y*-7~3{UGNytsxe<(4}0 zMZfWpNC+WNKQ|sX)(f}*;(;q}st?i$pMk=O-;dMy1}7MuceKr}$K!9mLR&|^x$XEa zFMKjl8n`9;u0QqV#D+tmLti2;O0k1bkJy^vNO?))25OY?{k;|Vt4PU6o0ZozlM5X; zT@ZYn%@(!aNYp$&F|`X9oa$YN0%C-H=qE3GtD3wMkiR_9d`-}gGQ@rR<^7S9tl`NU z5zl-E#XosDh-gKm%n5!hI`Am(?G*EwV-G*`Y3y;4ZzmA@y`Zrj@e8%n!%p`+XXthA z+SY1(v$XTf$JfR-Y}2-{nw;3NDPHj@TRp2IP$If#>Xm_WA>gzUMr}oH_sCePzekI} zW&h=vnf+EgCCH49cjRi;o*FpF|226iMc;X4$gi#3hiPrdX1Jp%v(%<{U}Ipfh2X(% zCus!%$qFaJbY||+lJD7$!;3hVA@s_|z9i||p!LJEvm^8qD#y!b4Q+(BCv%4Q;k)M% zjI_mp|7V(ElVCr2R1Xmf{0a}2y^-i#rylSN2j}FYh%uDsRk`=B20l|kt@p4RJ4=Gr zKG6N^T{w%B@I7Vwd+mVC^*;!?Emq$8-QEQrP*zpdQ`YZKlhnLGC0)MFxeq=|uy%lFj zp;rwm-CmxC!+Lt%WF7UK5yqHQ%ObL2wR!W5XO5%zC1gf!vu&ED>Rje^tyW7~z(C6g z<6ep2W%q@Q1MT#YAy>8rC#*-(7i9l`&&PH6AMN^l$=Ir99gkr3@pmJ*GY1wG?W_xn ziZ;ffTfTUu5j?g4?^E*~X^-@A3X+?UTx`5l_iOwh@%@!4?XvAt{~bqIx?Z(li6{j8@$Q$CyD z$z4-D;a-(hA|>crmZP{Y?i3Mj-&v0UFV&>BqONki#IwA&>#OkSGGQEPJC|o8nOkIz z;?pPmMxmB{)tHtfx4lXB$2}AR6`cu4iog>#U{uDI~e+o_Y zuLDE6)R(m5xM5wz7n8@xzCLmYB+Wl5t$|I}+HzLRmzq{vQO27Ulax>Cax^=uj;a2E zx!z_?r!}V0S1WaPEKoZo-ht?0|KqG5LS{L4JwbBoO{ zoM@RaM+m$Q3GhwQ6s=Sg2PFhT3EIpvHSaDZ7&7W9ddOZ9sM`_o-w=N`SY`U2qs%=P zfMuDS`<9;*-<7iL6B@n?5$m9OXix>$dm87MphM~#zb)UM89L$C;kMzqiD2kb zLfyvs&GFK*-bq~93(U)59Eg#Wm{z%(B33e zy!dvur)V2D^`&8gA^sF0szxaRlVImG{fAcR1`i;~f@?7y}`8KEO8=O4vLE$j3@hf2NkXWI8uTU;%Q*ebsfry5!4 zZDa@6U77ELj+SZ(hPj^xT;EpX_TcwpkG4W z!q(Hj#&dekna2kayJA;c;%s^0DD3 zUpsk7?a0DsL9#81JX32XI=Q5Ts;cyo0ruE}=?XWcRZmZr+l8MT z^l;|Kv=0E_jaeFN-fjN23~CR(0v zIF&aZkE;MF0=BU$ zB21LK_QCtQJ&Dq+;Zlgt6pw*|B>`8BEs8Hzr{CK>-_ru1n8bp@tQD`7erxB>X_nRA zjoe-CT$Hp)qIU?7SDu^Wr`d;aev)~4JdW4pXpL*(;4ZKHG0?!6bRwX|%==jZ&9da3 z85bfW-MX!YDjG2$ry_OWmehrst0DR7a~0CW1x}gks}depSH4xBnylF;2kQs}y%NlF z;AqF}EsT1nUnTiTV}%1=rDEms=zw>_@*4auULSoqet{D{!$MRy;KMr2)wi0Q^sT5V zX+>dFR$N-~wUFbYp}UkeH%4HwX(6XVb>*rGD*d`YnHbVgvmsUuT#bi%!T8C+E-T9_ zqWd=%kCgTX!s2ZH!si7?FR+ zva@eb14Eeu+PZH#18z6JesO|vasgGX5t5Ro(U4oyXx$*zQkmDhzB-{S#T8N=iVBd^ zBH1n&RYJ_~z>5>14odK7vjE#@O~&rV$;FLrtyg?YFc0WZLW@8MJrs^0{)1|43C3Lcet#@@dc`eD93e>_c2++-%<(W!0{6OT+kh~ zX(V8F(lgPHsXOkN!)}U5*lde)VgtiTO$75GdmfIBt}B};ZZup>i2o+vX>xR?K8 z$}gjEAGtdw9?fD{n9h9%damNCymq&!OYRxU7P?878|Q7y zhv*lX0!QqXDI9IFddy#BE4DMg-EygKb~h_{kuD*#~7$IVJ{H91g+^RV~+roj%82bWyE|=@PhF+r$9Gt#?P>_rb9Rp5%aTao!VAWa5~@ zs5m=)gxnbo?gGOX_TXqoyfu`S+*>w>#EO-$K^qoi*)+&R`+=di`Ua3`4p^Ti&qPf{ z`35i5|EgD|O#eh!gFskG%xz(>QVl?_-5{MhELP`7eff0Utq5=w@}8Jo)N`Jq`uy>F z)u%$~frTQs<5CHCNyk}zaJyu-&VhyuL6tqYftY6NZ=sVdm%1yl;1`)2oDhxNrk+@Z z4=xdHf|o^~Hl%+@sHbq(!y{l^Pu;j>0_5d4qxuFp7@_)Nsfm|zRfes8&2+PGchGRc z$O$IMD?OnwiqkPF9RZVhq=;s;nRBUwb`K+%9T9`hJ;Q_X)RYDAVm6m5w3}{C>~RW) z?GH+yY6X{Sy7F4=LmYe(Qx zI`u#*K2J%+E{?>+sO&jfF0)P)5?YN(c3%yZ!$Ju5w40Ebh9b_m5RN~P(EtIU@Z#na z2xk-{>YU#-Ix}wYl*g9g0B9jltWFKSnzPoqwXJeh;t#`*$QnDTasrz%s`o`7Uaj7k zElm=KT`6n#2X$k9bG+ly{&ojw>~y_z19JjZU<5*TBWI(?x{;k)+7oikCvWQkf_D@8 zq=O&g^%{UgYDYnDgL@64N|3n*P7wJ&G7(2lLMkc%LV9|y=SA)$8b+@Mg9^%c^Sd6G zVJ7#bXPGF7Ie4oV-6!*an9cU0rU;^1XeM-e>4L_KHi2O&{IUG#Z}_x&odrKS>Rh8p zPDTUQfl z4f@DzA!{plJdU6p z$`S6j0b@cig~0@Bd$prrgOx$f?rpFha+)em;VCe$7kykI{5WB{bw7njHA53joZ>hG~|N;vGxJ1ATT1@=JjF2fse+3_hH z0;R&ZT@jDtOL8AO2+I@1>Iq_P2eDAVLIDc}h=l^gLIGl-fQ14U3YcvI8x^onz(N5F z1uPUW9QeP00zu37_PYF#@P2uO`M8IJbl31lx#KrdKm+|(c&iVz+?7#q+3{&+x)~o= z;C6Np#PS5OdcxWcVxfSA0_M39)@876^ov7550jY9%tOzOx>^8zI$O>tOYSl zU~z@1!DrDHA=U>mVF2ry*?2`CEq$3F;pg1kRwsCnP(Ln0VedYzYY#Ny95p`po$`kM PjhfRY&zzJtf!h2VCl855 diff --git a/tools/cross-compiler/src/main.rs b/tools/cross-compiler/src/main.rs index 6e0b0d431..ff26c8c0f 100644 --- a/tools/cross-compiler/src/main.rs +++ b/tools/cross-compiler/src/main.rs @@ -13,38 +13,19 @@ fn main() -> anyhow::Result<()> { let targets = [ Target { triple: "aarch64-apple-ios", - crates: &[ - "fj", - "fj-export", - "fj-interop", - "fj-kernel", - "fj-math", - "fj-operations", - "fj-proc", - ], + crates: &["fj-export", "fj-interop", "fj-kernel", "fj-math"], }, Target { triple: "aarch64-linux-android", - crates: &[ - "fj", - "fj-export", - "fj-interop", - "fj-kernel", - "fj-math", - "fj-operations", - "fj-proc", - ], + crates: &["fj-export", "fj-interop", "fj-kernel", "fj-math"], }, Target { triple: "wasm32-unknown-unknown", crates: &[ - "fj", "fj-export", "fj-interop", "fj-kernel", "fj-math", - "fj-operations", - "fj-proc", "fj-viewer", ], },