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 0e4cc8d25..000000000 Binary files a/models/cuboid/cuboid.png and /dev/null differ 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 936a01a02..000000000 Binary files a/models/spacer/spacer.png and /dev/null differ 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 e667ef04a..000000000 Binary files a/models/star/star.png and /dev/null differ 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 4b0f5c34e..000000000 Binary files a/models/test/test.png and /dev/null differ 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", ], },