From 17efc2b2b2073e3b4f5802333fb1702c5c815935 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 15 May 2023 10:44:16 +0200 Subject: [PATCH 1/4] 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", ], }, From 24165d3b8f4fd6b82ea8aa50463b2710cd88b08d Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 15 May 2023 10:49:35 +0200 Subject: [PATCH 2/4] Move Nix flake to fornjot-extra --- nix/default.nix | 10 ----- nix/flake.lock | 113 ------------------------------------------------ nix/flake.nix | 83 ----------------------------------- nix/shell.nix | 10 ----- 4 files changed, 216 deletions(-) delete mode 100644 nix/default.nix delete mode 100644 nix/flake.lock delete mode 100644 nix/flake.nix delete mode 100644 nix/shell.nix diff --git a/nix/default.nix b/nix/default.nix deleted file mode 100644 index 2cccff28d..000000000 --- a/nix/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).defaultNix diff --git a/nix/flake.lock b/nix/flake.lock deleted file mode 100644 index 7bbf7adbe..000000000 --- a/nix/flake.lock +++ /dev/null @@ -1,113 +0,0 @@ -{ - "nodes": { - "crane": { - "inputs": { - "flake-compat": [ - "flake-compat" - ], - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ], - "rust-overlay": [ - "rust-overlay" - ] - }, - "locked": { - "lastModified": 1678152261, - "narHash": "sha256-cPRDxwygVMleiSEGELrvAiq9vYAN4c3KK/K4UEO13vU=", - "owner": "ipetkov", - "repo": "crane", - "rev": "5291dd0aa7a52d607fc952763ef60714e4c881d4", - "type": "github" - }, - "original": { - "owner": "ipetkov", - "repo": "crane", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1673956053, - "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "locked": { - "lastModified": 1678901627, - "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1678949004, - "narHash": "sha256-8c0hnhTjmLRxtYp7wLr2nN4sRa61quL9PBSaGuYaBL0=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "67712ddbdcfa2790dc0d58c56b83eb980ab43213", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "crane": "crane", - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" - } - }, - "rust-overlay": { - "inputs": { - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1678933473, - "narHash": "sha256-UY19R278O9bwneLWC7ady8VMoQ+UlAWy8SkUsfDZvQs=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "5c1af9b9d618e02a87cdd30a3022aec0b78cd9aa", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/nix/flake.nix b/nix/flake.nix deleted file mode 100644 index f0511f9fa..000000000 --- a/nix/flake.nix +++ /dev/null @@ -1,83 +0,0 @@ -{ - description = "Fornjot is an early-stage project to create a next-generation, code-first CAD application"; - inputs = { - flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; - nixpkgs.url = "github:NixOS/nixpkgs"; - flake-utils.url = "github:numtide/flake-utils"; - rust-overlay = { url = "github:oxalica/rust-overlay"; inputs = { nixpkgs.follows = "nixpkgs"; flake-utils.follows = "flake-utils"; }; }; - crane = { - url = "github:ipetkov/crane"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-compat.follows = "flake-compat"; - flake-utils.follows = "flake-utils"; - rust-overlay.follows = "rust-overlay"; - }; - }; - }; - - outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, ... }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { inherit system; overlays = [ (import rust-overlay) ]; }; - rustToolchain = pkgs.rust-bin.fromRustupToolchain ( - # extend toolchain with rust-analyzer for better IDE support - let toolchainToml = (builtins.fromTOML (builtins.readFile ../rust-toolchain.toml)).toolchain; in - { - channel = toolchainToml.channel; - components = toolchainToml.components ++ [ "rust-analyzer" "rust-src" ]; - } - ); - craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; - version = (builtins.fromTOML (builtins.readFile ../Cargo.toml)).workspace.package.version; - # Only keeps assets in crates/ (currently shaders and fonts) - assetsFilter = path: _type: (builtins.match ".*(:?wgsl|ttf|png|obj|fj.toml|mtl)$" path) != null; - filter = path: type: (assetsFilter path type) || (craneLib.filterCargoSources path type); - buildInputs = with pkgs; [ - pkg-config - fontconfig - cmake - wayland - libGL - xorg.libX11 - xorg.libXcursor - xorg.libXi - xorg.libXrandr - ]; - - fornjot = craneLib.buildPackage { - pname = "fj-app"; - src = nixpkgs.lib.cleanSourceWith { src = ../.; inherit filter; }; - inherit buildInputs version; - }; - - wrappedFornjot = pkgs.symlinkJoin { - name = "fj-app"; - inherit version; - paths = [ fornjot ]; - - buildInputs = [ pkgs.makeWrapper ]; - - postBuild = '' - wrapProgram $out/bin/fj-app \ - --prefix LD_LIBRARY_PATH : ${nixpkgs.lib.makeLibraryPath [ pkgs.vulkan-loader ]} - ''; - }; - in - { - checks = { inherit fornjot; }; - - packages.default = wrappedFornjot; - - apps.default = flake-utils.lib.mkApp { drv = wrappedFornjot; }; - - devShells.default = pkgs.mkShell { - inputsFrom = builtins.attrValues self.checks; - - inherit buildInputs; - nativeBuildInputs = [ rustToolchain ]; - - LD_LIBRARY_PATH = "${nixpkgs.lib.makeLibraryPath [ pkgs.vulkan-loader pkgs.stdenv.cc.cc.lib pkgs.lib3mf]}"; - }; - }); -} diff --git a/nix/shell.nix b/nix/shell.nix deleted file mode 100644 index 6234bb4d6..000000000 --- a/nix/shell.nix +++ /dev/null @@ -1,10 +0,0 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).shellNix From 4ced1d38f046b283923eddd8c21b5a7c2f3cc19f Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 15 May 2023 10:59:17 +0200 Subject: [PATCH 3/4] Update README --- README.md | 126 +++--------------------------------------------------- 1 file changed, 5 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index 6eee32b11..2593ea1f3 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,7 @@ ## About -Fornjot is an **early-stage project** to create a **next-generation, code-first CAD application**. Because [**the world needs another CAD program**](https://github.com/sponsors/hannobraun). - -![Screenshot of Fornjot](models/star/star.png) +Fornjot is an early-stage [b-rep](https://en.wikipedia.org/wiki/Boundary_representation) CAD kernel written in the Rust programming language. For an introduction of what the project aims to achieve, [please check out the website](https://www.fornjot.app/). @@ -21,132 +19,20 @@ Fornjot is supported by [**@webtrax-oz**](https://github.com/webtrax-oz), [**@re ## Table of Contents - [**Status**](#status) -- [**Features**](#features) - [**Usage**](#usage) - [**Community**](#community) - [**Get Involved**](#get-involved) - [**License**](#license) + ## Status -Fornjot is **under active development, but still experimental**. Efforts are currently focused on providing a [stable set of basic CAD features](https://github.com/hannobraun/Fornjot/milestone/1). - -If you are interested in Fornjot and are considering to use it, you should fully expect to run into limitations pretty much immediately. Unless you are willing to contribute to its development, it would be better to wait for a year or ten, to let it mature. For more information on current limitations and improvements that could be implemented in the near future, [check out the open issues](https://github.com/hannobraun/Fornjot/issues). - -To learn about the project's longer-term direction, please refer to the [roadmap](https://www.fornjot.app/roadmap/). - - -## Features - -### Code-first CAD in Rust - -Models are defined as Rust code. To ensure fast compile times, they are compiled separately, and loaded into the Fornjot application as a plug-in. - -``` rust -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() -} -``` - -This is the code for the [spacer model](/models/spacer). - -### Basic modeling features - -At this point, Fornjot supports basic 2D shapes (sketches made from lines segments, circles, and limited combinations between them) and sweeping those 2D shapes along a straight path to create a 3D shape. - -The short- to mid-term priority is to provide CSG support, more flexible sketches, and more flexible sweeps (along a circle or helix). Long-term, the plan is to keep adding more advanced CAD modeling features, to support even complex models and workflows. - -### Supports the major desktop platforms - -As of this writing, Fornjot runs on Linux, Windows, and macOS. The project is primarily developed on Linux, so the other platforms might be subject to bugs. If you want to help out, regularly testing on Windows and macOS, and reporting bugs, is a good way to do so. - -Short- to mid-term, the plan is to add support for the web platform, so Fornjot can run in browsers. Long-term, the plan is to additionally support the major mobile platforms. - -### Export to 3MF & STL - -Exporting models to both the [3D Manufacturing Format](https://en.wikipedia.org/wiki/3D_Manufacturing_Format) (3MF), which is used in 3D printing, and STL is supported. +Fornjot is usable for some toy examples, but currently lacks the features for something more advanced. Work to change that is underway. ## Usage -### Installation - -Since Fornjot uses Rust as the language for defining models, a [Rust toolchain](https://www.rust-lang.org/tools/install) is required to use Fornjot. - -To install Fornjot itself, you have the following options: - -1. Download a binary from the [latest release](https://github.com/hannobraun/Fornjot/releases). -2. Compile the latest release yourself: `cargo install fj-app` -3. Compile a development version from this repository: `cd path/to/repo; cargo install --path crates/fj-app` - -While the Fornjot application is a graphical application that opens a window and displays a 3D view of your model, it can currently only be started from the command-line. The instructions below assume that you have the Fornjot application installed somewhere on your path, under the name `fj-app`. - -#### Mac OS -If you download in [latest release](https://github.com/hannobraun/Fornjot/releases) binary, two problems need to be noticed when running on your local computer. -1. The mac will prompt that "fj-app-x86_64-apple-arwin" cannot be opened because it is from an unknown developer. - -The solution is Open the Mac system preferences. In the preferences interface, click to open **security and privacy**. In the security and privacy interface, click **General**. In the call settings panel, click the **Still want to open** button. In the pop-up window, click the **Open** button. - -2. Failed to open the document "fj-app". Text encoding Unicode (UTF-8) is not applicable. - -The solution is enter the following command in the terminal, (we recommend changing the file name to fj-app) -```shell -chmod a+x fj-app -``` - -#### Via Nix - -There's a Nix [flake](https://nixos.wiki/wiki/Flakes) in the subdirectory `./nix` which contains a devshell environment (via `nix develop` or `nix-shell`) and the package `fj-app`. -It can be run/tested with a [flake enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes) nix via `nix run github:hannobraun/Fornjot?dir=nix` or with legacy nix in the directory `./nix`, `nix-build`. - -### Defining models - -Models are Rust libraries that depend on the [`fj`](https://crates.io/crates/fj) library, which they use to define the geometry. Furthermore, they need to be built as a dynamic library. Just use the examples in the [`models/`](models) directory as a template to define your own. - -### Viewing models - -To view a model, run: - -``` sh -fj-app my-model -``` - -This will usually compile and load the model in the `my-model/` directory. If there is a configuration file (`fj.toml`) available, it might define a default path to load models from that is different from the current working directory. This is the case [in the Fornjot repository](fj.toml). - -Rotate the model by pressing the left mouse button while moving the mouse. Move the model by pressing the right mouse button while moving the mouse. Zoom with the mouse wheel. - -Toggle model rendering by pressing `1`. Toggle mesh rendering by pressing `2`. Toggle rendering of debug data by pressing `3`. - -### Exporting models - -To export a model to a file, run: - -``` sh -fj-app my-model --export my-model.3mf -``` - -The file type is chosen based on the file extension. Both 3MF and STL are supported. - -### Model parameters - -Models can define parameters that can be overridden. This can be done using the `--parameters` argument: - -``` sh -fj-app my-model --parameters "width=3.0,height=5.0" -``` +This repository is currently [in flux](https://www.fornjot.app/blog/a-new-direction/) and the documentation previously available here no longer applies. This section will be updated in due time. ## Community @@ -163,9 +49,7 @@ If you are interested in helping out, just fork one of the GitHub repositories a - [Main Fornjot repository](https://github.com/hannobraun/Fornjot) - [Website repository](https://github.com/hannobraun/www.fornjot.app) -If you don't know what to work on, check out the [`good first issues`](https://github.com/hannobraun/Fornjot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). To get an overview over current priorities, take a look at the [open milestones](https://github.com/hannobraun/Fornjot/milestones). - -If you need some more guidance, check out the [contribution guide](CONTRIBUTING.md), [or just ask](https://www.fornjot.app/community/)! +If you don't know what to work on, check out the [`good first issues`](https://github.com/hannobraun/Fornjot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). If you need some more guidance, check out the [contribution guide](CONTRIBUTING.md), [or just ask](https://www.fornjot.app/community/)! ## License From 07849724c6731b1ae4fbdd0b8646fef77bf6b8a3 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 15 May 2023 11:11:30 +0200 Subject: [PATCH 4/4] Update CI build --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faaa0c24b..9a10e9bfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,5 +59,5 @@ jobs: run: cargo build --all-features - name: Run `cargo test` run: cargo test --all-features - - name: Run `export-validator` - run: cargo run --package export-validator + # - name: Run `export-validator` + # run: cargo run --package export-validator