From 5e69dfcbda42affffb6206eca68f04d973e668bf Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Tue, 25 Jun 2024 20:22:57 +0300 Subject: [PATCH 1/6] Move to tabs and add loro - not functional yet Signed-off-by: Dimitris Zervas --- .rustfmt.toml | 1 + Cargo.lock | 430 ++++++++- applications/tauri/build.rs | 2 +- applications/tauri/src/lib.rs | 6 +- applications/tauri/src/main.rs | 2 +- packages/cadmium-macros/src/lib.rs | 367 ++++---- packages/cadmium/Cargo.toml | 15 +- .../benches/faceselector-report/draw.rs | 80 +- .../benches/faceselector-report/main.rs | 192 ++-- .../benches/faceselector-report/report.rs | 16 +- .../faceselector-report/simple_circles.rs | 68 +- .../examples/project_simple_extrusion.rs | 154 +-- packages/cadmium/src/archetypes.rs | 308 +++--- packages/cadmium/src/bin/gen-types.rs | 80 +- packages/cadmium/src/error.rs | 54 +- packages/cadmium/src/feature/extrusion.rs | 324 +++---- packages/cadmium/src/feature/helpers.rs | 330 +++---- packages/cadmium/src/feature/mod.rs | 36 +- packages/cadmium/src/feature/point.rs | 220 ++--- packages/cadmium/src/feature/prelude.rs | 2 +- packages/cadmium/src/feature/solid.rs | 244 ++--- packages/cadmium/src/isketch/compound.rs | 34 +- .../cadmium/src/isketch/compound_rectangle.rs | 136 +-- packages/cadmium/src/isketch/face.rs | 102 +- packages/cadmium/src/isketch/mod.rs | 280 +++--- packages/cadmium/src/isketch/primitive.rs | 318 +++---- packages/cadmium/src/lib.rs | 190 ++-- packages/cadmium/src/main.rs | 96 +- packages/cadmium/src/message/idwrap/de.rs | 124 +-- packages/cadmium/src/message/idwrap/mod.rs | 116 +-- packages/cadmium/src/message/idwrap/ser.rs | 52 +- packages/cadmium/src/message/message.rs | 90 +- packages/cadmium/src/message/mod.rs | 18 +- packages/cadmium/src/project.rs | 878 +++++++++--------- packages/cadmium/src/step/actions.rs | 40 +- packages/cadmium/src/step/evtree.rs | 26 + packages/cadmium/src/step/hash.rs | 42 +- packages/cadmium/src/step/mod.rs | 88 +- packages/cadmium/src/step/result.rs | 34 +- packages/cadmium/src/step/sketch_action.rs | 40 +- packages/cadmium/src/workbench.rs | 374 ++++---- 41 files changed, 3246 insertions(+), 2763 deletions(-) create mode 100644 .rustfmt.toml create mode 100644 packages/cadmium/src/step/evtree.rs diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 00000000..218e2032 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/Cargo.lock b/Cargo.lock index 805bd043..19559379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "append-only-bytes" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac436601d6bdde674a0d7fb593e829ffe7b3387c351b356dd20e2d40f5bf3ee5" + [[package]] name = "approx" version = "0.4.0" @@ -111,12 +117,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "array-macro" version = "2.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "220a2c618ab466efe41d0eace94dfeff1c35e3aa47891bdb95e1c0fefffd3c99" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "arref" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ccd462b64c3c72f1be8305905a85d85403d768e8690c9b8bd3b9009a5761679" + [[package]] name = "atk" version = "0.18.0" @@ -140,6 +167,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -194,6 +230,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + [[package]] name = "block" version = "0.1.6" @@ -296,12 +347,11 @@ dependencies = [ "convert_case 0.6.0", "crc32fast", "geo", - "indexmap 2.2.6", "isotope", "itertools 0.13.0", "js-sys", "log", - "paste", + "loro", "serde", "serde_json", "serde_with", @@ -478,6 +528,12 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "cocoa" version = "0.25.0" @@ -601,6 +657,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -738,6 +800,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -924,6 +997,48 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "enum-as-inner" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1220,6 +1335,20 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic-btree" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210507e6dec78bb1304e52a174bd99efdd83894219bf20d656a066a0ce2fedc5" +dependencies = [ + "arref", + "fxhash", + "heapless 0.7.17", + "itertools 0.11.0", + "loro-thunderdome", + "proc-macro2", +] + [[package]] name = "geo" version = "0.28.0" @@ -1463,6 +1592,15 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1488,13 +1626,27 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32", + "hash32 0.3.1", "stable_deref_trait", ] @@ -1784,6 +1936,42 @@ dependencies = [ "utf8_iter", ] +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps 2.1.0", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "imbl" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be8d8cd36f33a46b1849f31f837c44d9fa87223baee3b4bd96b8f11df81eb" +dependencies = [ + "bitmaps 3.2.1", + "imbl-sized-chunks", + "rand_core 0.6.4", + "rand_xoshiro", + "version_check", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144006fb58ed787dcae3f54575ff4349755b00ccc99f4b4873860b654be1ed63" +dependencies = [ + "bitmaps 3.2.1", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1997,6 +2185,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libappindicator" version = "0.9.0" @@ -2106,6 +2300,118 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "loro" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c89fb56a17df4430afcadb6053dcefb9762f115e238aebe2daaf0798212ab4" +dependencies = [ + "either", + "enum-as-inner 0.6.0", + "generic-btree", + "loro-delta", + "loro-internal", + "tracing", +] + +[[package]] +name = "loro-common" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb709c7dbf66c98f27b7a762d5dbe963b1552ef95ac3deb4eb7a1e3dc2a1248" +dependencies = [ + "arbitrary", + "enum-as-inner 0.6.0", + "fxhash", + "loro-rle", + "nonmax", + "serde", + "serde_columnar", + "string_cache", + "thiserror", +] + +[[package]] +name = "loro-delta" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba334fa0e4a39cf2a1d3ecd8e1c24e2e8b085826b3397964efb48bb83a9f288" +dependencies = [ + "arrayvec", + "enum-as-inner 0.5.1", + "generic-btree", + "heapless 0.8.0", + "tracing", +] + +[[package]] +name = "loro-internal" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937457140d74326dc198fe4a781d865d861a37cb0ef91511fb2f3582c243550b" +dependencies = [ + "append-only-bytes", + "arref", + "either", + "enum-as-inner 0.5.1", + "enum_dispatch", + "fxhash", + "generic-btree", + "getrandom 0.2.15", + "im", + "itertools 0.12.1", + "leb128", + "loro-common", + "loro-delta", + "loro-rle", + "loro_fractional_index", + "md5", + "num", + "num-derive", + "num-traits", + "once_cell", + "postcard", + "rand 0.8.5", + "serde", + "serde_columnar", + "serde_json", + "smallvec", + "thiserror", + "tracing", +] + +[[package]] +name = "loro-rle" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea296adbce1675c89df38798e1f5f1a46e49012870086f27b5f8f38c0b52d81b" +dependencies = [ + "append-only-bytes", + "arref", + "enum-as-inner 0.6.0", + "fxhash", + "num", + "smallvec", +] + +[[package]] +name = "loro-thunderdome" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3d053a135388e6b1df14e8af1212af5064746e9b87a06a345a7a779ee9695a" + +[[package]] +name = "loro_fractional_index" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eead5ac567d2c71340ff48bd4c853173dd37d42c7629383b881e232a4c6b529" +dependencies = [ + "imbl", + "rand 0.8.5", + "serde", + "smallvec", +] + [[package]] name = "lz4_flex" version = "0.7.5" @@ -2188,6 +2494,12 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "1.0.2" @@ -2352,6 +2664,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2362,6 +2680,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -2398,12 +2740,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-bigint", "num-integer", "num-traits", ] @@ -2863,6 +3217,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "heapless 0.7.17", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3049,6 +3415,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -3209,7 +3584,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "133315eb94c7b1e8d0cb097e5a710d850263372fd028fff18969de708afc7008" dependencies = [ - "heapless", + "heapless 0.8.0", "num-traits", "smallvec", ] @@ -3369,6 +3744,31 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_columnar" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7947238638e841d935a1dadff54b74575ae60d51b977be75dab16e7638ea6e7d" +dependencies = [ + "itertools 0.11.0", + "postcard", + "serde", + "serde_columnar_derive", + "thiserror", +] + +[[package]] +name = "serde_columnar_derive" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5eaacabbc55a397ffbb1ee32523f40f86fdefea8a8d9db19630d8b7c00edd1" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "serde_derive" version = "1.0.203" @@ -3552,6 +3952,16 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps 2.1.0", + "typenum", +] + [[package]] name = "slab" version = "0.4.9" @@ -3566,6 +3976,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3639,6 +4052,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/applications/tauri/build.rs b/applications/tauri/build.rs index 795b9b7c..c1ea3732 100644 --- a/applications/tauri/build.rs +++ b/applications/tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/applications/tauri/src/lib.rs b/applications/tauri/src/lib.rs index cfef8d12..a8e1845a 100644 --- a/applications/tauri/src/lib.rs +++ b/applications/tauri/src/lib.rs @@ -1,6 +1,6 @@ #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + tauri::Builder::default() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/applications/tauri/src/main.rs b/applications/tauri/src/main.rs index ad5fe839..3f018e95 100644 --- a/applications/tauri/src/main.rs +++ b/applications/tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - app_lib::run(); + app_lib::run(); } diff --git a/packages/cadmium-macros/src/lib.rs b/packages/cadmium-macros/src/lib.rs index aa314eb4..33bc104c 100644 --- a/packages/cadmium-macros/src/lib.rs +++ b/packages/cadmium-macros/src/lib.rs @@ -4,72 +4,72 @@ use syn::{parse_macro_input, DeriveInput, ItemFn, Meta, NestedMeta, Type}; #[proc_macro_derive(MessageEnum)] pub fn message_handler_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; - let data = match input.data { - syn::Data::Enum(data) => data, - _ => panic!("MessageEnum can only be derived for enums"), - }; - - let variants_typescript = data - .variants - .iter() - .map(|variant| { - let syn::Fields::Unnamed(field) = &variant.fields else { - panic!("MessageEnum can only be derived for enums with unnamed fields"); - }; - - let field_type = &field.unnamed[0].ty; - - variant_to_typescript(field_type.clone()) - }) - .collect::)>>(); - let variants_typescript_type = variants_typescript - .clone() - .iter() - .map(|v| v.0.clone()) - .collect::>(); - let variants_typescript_additional = variants_typescript - .clone() - .iter() - .map(|v| v.1.clone()) - .collect::>(); - - let variants_type = data - .variants - .iter() - .map(|variant| { - let syn::Fields::Unnamed(field) = &variant.fields else { - panic!("MessageEnum can only be derived for enums with unnamed fields"); - }; - - let field_type = &field.unnamed[0].ty; - quote! { #field_type } - }) - .collect::>(); - - let variants = data - .variants - .iter() - .map(|variant| { - println!("Message Handler: {}", variant.ident); - let variant_name = &variant.ident; - - quote! { - #name::#variant_name(msg) - } - }) - .collect::>(); - - let variant_names = data - .variants - .iter() - .map(|variant| &variant.ident) - .collect::>(); - let variants_clone = variants.clone(); - let variants_clone2 = variants.clone(); - - quote! { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + let data = match input.data { + syn::Data::Enum(data) => data, + _ => panic!("MessageEnum can only be derived for enums"), + }; + + let variants_typescript = data + .variants + .iter() + .map(|variant| { + let syn::Fields::Unnamed(field) = &variant.fields else { + panic!("MessageEnum can only be derived for enums with unnamed fields"); + }; + + let field_type = &field.unnamed[0].ty; + + variant_to_typescript(field_type.clone()) + }) + .collect::)>>(); + let variants_typescript_type = variants_typescript + .clone() + .iter() + .map(|v| v.0.clone()) + .collect::>(); + let variants_typescript_additional = variants_typescript + .clone() + .iter() + .map(|v| v.1.clone()) + .collect::>(); + + let variants_type = data + .variants + .iter() + .map(|variant| { + let syn::Fields::Unnamed(field) = &variant.fields else { + panic!("MessageEnum can only be derived for enums with unnamed fields"); + }; + + let field_type = &field.unnamed[0].ty; + quote! { #field_type } + }) + .collect::>(); + + let variants = data + .variants + .iter() + .map(|variant| { + println!("Message Handler: {}", variant.ident); + let variant_name = &variant.ident; + + quote! { + #name::#variant_name(msg) + } + }) + .collect::>(); + + let variant_names = data + .variants + .iter() + .map(|variant| &variant.ident) + .collect::>(); + let variants_clone = variants.clone(); + let variants_clone2 = variants.clone(); + + quote! { impl #name { pub fn handle(&self, project: &mut crate::project::Project) -> anyhow::Result> { match self { @@ -120,128 +120,129 @@ pub fn message_handler_derive(input: proc_macro::TokenStream) -> proc_macro::Tok #[proc_macro_attribute] pub fn message( - attr: proc_macro::TokenStream, - item: proc_macro::TokenStream, + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let args = parse_macro_input!(attr as syn::AttributeArgs); - let input = parse_macro_input!(item as ItemFn); - - // Extract the function name and arguments - let fn_name = &input.sig.ident; - let fn_args = &input.sig.inputs; - let mut parent_opt = None; - let mut rename_parent = None; - - for arg in args.iter() { - match arg { - NestedMeta::Meta(Meta::Path(path)) => { - parent_opt = - Some(path.get_ident().expect( - "Parent type mut be an identifier (e.g. ISketch, not crate::ISketch)", - )); - } - NestedMeta::Meta(Meta::NameValue(name_value)) => { - if name_value.path.is_ident("rename_parent") { - let syn::Lit::Str(ref rename_parent_val) = name_value.lit else { - panic!("rename_parent must be a string literal") - }; - rename_parent = Some(rename_parent_val.value()); - } - } - _ => panic!("Invalid attribute argument"), - } - } - - // Create a struct name based on the function name - let parent = parent_opt.expect("Parent type must be specified"); - let struct_name = if let Some(rename_parent) = rename_parent { - format_ident!( - "{}{}Message", - rename_parent, - fn_name.to_string().to_case(Case::Pascal) - ) - } else { - format_ident!( - "{}{}Message", - parent, - fn_name.to_string().to_case(Case::Pascal) - ) - }; - - // Generate struct fields from function arguments - let fields = fn_args.iter().map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - let pat = &pat_type.pat; - let ty = &pat_type.ty; - quote! { - pub #pat: #ty, - } - } else { - quote!() - } - }); - let parameters = fn_args.iter().filter_map(|arg| { - if let syn::FnArg::Typed(pat_type) = arg { - let pat = &pat_type.pat; - Some(quote! { #pat }) - } else { - None - } - }); - - quote! { - impl #parent { - #input - } - - #[derive(tsify_next::Tsify, Debug, Clone, serde::Serialize, serde::Deserialize)] - #[tsify(from_wasm_abi, into_wasm_abi)] - pub struct #struct_name { - #(#fields)* - } - - impl crate::message::MessageHandler for #struct_name { - type Parent = Rc>; - fn handle_message(&self, parent_ref: Self::Parent) -> anyhow::Result> { - parent_ref.borrow_mut().#fn_name( #(self.#parameters.clone()),* ) - } - } - }.into() + let args = parse_macro_input!(attr as syn::AttributeArgs); + let input = parse_macro_input!(item as ItemFn); + + // Extract the function name and arguments + let fn_name = &input.sig.ident; + let fn_args = &input.sig.inputs; + let mut parent_opt = None; + let mut rename_parent = None; + + for arg in args.iter() { + match arg { + NestedMeta::Meta(Meta::Path(path)) => { + parent_opt = + Some(path.get_ident().expect( + "Parent type mut be an identifier (e.g. ISketch, not crate::ISketch)", + )); + } + NestedMeta::Meta(Meta::NameValue(name_value)) => { + if name_value.path.is_ident("rename_parent") { + let syn::Lit::Str(ref rename_parent_val) = name_value.lit else { + panic!("rename_parent must be a string literal") + }; + rename_parent = Some(rename_parent_val.value()); + } + } + _ => panic!("Invalid attribute argument"), + } + } + + // Create a struct name based on the function name + let parent = parent_opt.expect("Parent type must be specified"); + let struct_name = if let Some(rename_parent) = rename_parent { + format_ident!( + "{}{}Message", + rename_parent, + fn_name.to_string().to_case(Case::Pascal) + ) + } else { + format_ident!( + "{}{}Message", + parent, + fn_name.to_string().to_case(Case::Pascal) + ) + }; + + // Generate struct fields from function arguments + let fields = fn_args.iter().map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + let pat = &pat_type.pat; + let ty = &pat_type.ty; + quote! { + pub #pat: #ty, + } + } else { + quote!() + } + }); + let parameters = fn_args.iter().filter_map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + let pat = &pat_type.pat; + Some(quote! { #pat }) + } else { + None + } + }); + + quote! { + impl #parent { + #input + } + + #[derive(tsify_next::Tsify, Debug, Clone, serde::Serialize, serde::Deserialize)] + #[tsify(from_wasm_abi, into_wasm_abi)] + pub struct #struct_name { + #(#fields)* + } + + impl crate::message::MessageHandler for #struct_name { + type Parent = Rc>; + fn handle_message(&self, parent_ref: Self::Parent) -> anyhow::Result> { + parent_ref.borrow_mut().#fn_name( #(self.#parameters.clone()),* ) + } + } + } + .into() } fn variant_to_typescript(field_type: Type) -> (Type, Vec) { - let mut inner_type = field_type.clone(); - let mut type_str = field_type.clone().to_token_stream().to_string(); - let mut idwrap_types = vec![]; - - while type_str.starts_with("IDWrap") { - let idwrap_type = get_idwrap_type(inner_type); - inner_type = idwrap_type.clone(); - idwrap_types.push(idwrap_type.clone()); - type_str = idwrap_type.to_token_stream().to_string(); - } - - let additional_types = idwrap_types - .iter() - .map(|idwrap_type| { - quote! {<#idwrap_type as crate::message::MessageHandler>::Parent::ID_NAME } - }) - .collect::>(); - - (inner_type, additional_types) + let mut inner_type = field_type.clone(); + let mut type_str = field_type.clone().to_token_stream().to_string(); + let mut idwrap_types = vec![]; + + while type_str.starts_with("IDWrap") { + let idwrap_type = get_idwrap_type(inner_type); + inner_type = idwrap_type.clone(); + idwrap_types.push(idwrap_type.clone()); + type_str = idwrap_type.to_token_stream().to_string(); + } + + let additional_types = idwrap_types + .iter() + .map(|idwrap_type| { + quote! {<#idwrap_type as crate::message::MessageHandler>::Parent::ID_NAME } + }) + .collect::>(); + + (inner_type, additional_types) } fn get_idwrap_type(field_type: Type) -> Type { - let Type::Path(inner_path) = field_type else { - panic!("IDWrap type argument must be a path type"); - }; - let inner_type_args = &inner_path.path.segments.first().unwrap().arguments; - let syn::PathArguments::AngleBracketed(idwrap_generic) = inner_type_args else { - panic!("IDWrap type argument must be a generic type"); - }; - let syn::GenericArgument::Type(idwrap_generic_type) = idwrap_generic.args.first().unwrap() - else { - panic!("IDWrap type argument must be a path type"); - }; - idwrap_generic_type.clone() + let Type::Path(inner_path) = field_type else { + panic!("IDWrap type argument must be a path type"); + }; + let inner_type_args = &inner_path.path.segments.first().unwrap().arguments; + let syn::PathArguments::AngleBracketed(idwrap_generic) = inner_type_args else { + panic!("IDWrap type argument must be a generic type"); + }; + let syn::GenericArgument::Type(idwrap_generic_type) = idwrap_generic.args.first().unwrap() + else { + panic!("IDWrap type argument must be a path type"); + }; + idwrap_generic_type.clone() } diff --git a/packages/cadmium/Cargo.toml b/packages/cadmium/Cargo.toml index 61b8ba0f..d925663f 100644 --- a/packages/cadmium/Cargo.toml +++ b/packages/cadmium/Cargo.toml @@ -21,27 +21,26 @@ truck-shapeops = { git = "https://github.com/ricosjp/truck.git", rev = "c84318b8 truck-polymesh = { git = "https://github.com/ricosjp/truck.git", rev = "c84318b8dec" } truck-topology = { git = "https://github.com/ricosjp/truck.git", rev = "c84318b8dec" } truck-stepio = { git = "https://github.com/ricosjp/truck.git", rev = "c84318b8dec" } -serde_json = "1.0.117" -serde = "1.0.202" -itertools = "0.13.0" +serde_json = "1.0" +serde = "1.0" +itertools = "0.13" svg = "0.17.0" geo = "0.28.0" serde_with = "3.4.0" crc32fast = "1.3.2" -indexmap = "2.1.0" anyhow = { version = "1.0.86", features = ["backtrace"] } thiserror = "1.0.61" strum = { version = "0.26.2", features = ["derive"] } isotope = { git = "https://github.com/CADmium-Co/ISOtope.git", version = "*", features = [ "tsify", ] } -paste = "1.0.15" cadmium-macros = { path = "../cadmium-macros", version = "*" } -log = "0.4.21" +log = "0" wasm-logger = "0.2.0" convert_case = "0.6" -xxhash-rust = { version = "0.8.10", features = ["xxh3"] } -js-sys = "0.3.69" +xxhash-rust = { version = "0.8", features = ["xxh3"] } +js-sys = "0.3" +loro = "0.16" [lib] crate-type = ["cdylib", "rlib"] diff --git a/packages/cadmium/benches/faceselector-report/draw.rs b/packages/cadmium/benches/faceselector-report/draw.rs index 3edba3c7..4d2a2cbe 100644 --- a/packages/cadmium/benches/faceselector-report/draw.rs +++ b/packages/cadmium/benches/faceselector-report/draw.rs @@ -8,59 +8,59 @@ use cadmium::IDType; use crate::FaceSelectorType; pub const COLORS: [&str; 6] = [ - "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", + "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", ]; pub fn draw_sketch_faces( - p: &mut Project, - selector: &FaceSelectorType, - sketch_id: IDType, - name: String, + p: &mut Project, + selector: &FaceSelectorType, + sketch_id: IDType, + name: String, ) { - let wb_ref = p.get_workbench_by_id(0).unwrap(); - let wb = wb_ref.borrow(); - let sketch_ref = wb.get_sketch_by_id(sketch_id).unwrap(); - let sketch = sketch_ref.borrow(); + let wb_ref = p.get_workbench_by_id(0).unwrap(); + let wb = wb_ref.borrow(); + let sketch_ref = wb.get_sketch_by_id(sketch_id).unwrap(); + let sketch = sketch_ref.borrow(); - let all_faces = sketch.sketch().borrow().get_merged_faces(); - let faces = selector.get_selected_faces(&sketch); + let all_faces = sketch.sketch().borrow().get_merged_faces(); + let faces = selector.get_selected_faces(&sketch); - // viewBox is min-x, min-y, width, height - let mut svg_doc = svg::Document::new().set("viewBox", (-50, -50, 100, 100)); + // viewBox is min-x, min-y, width, height + let mut svg_doc = svg::Document::new().set("viewBox", (-50, -50, 100, 100)); - for (i, face) in all_faces.iter().enumerate() { - let polygon = face.as_polygon(); - let color = COLORS[i % COLORS.len()]; - let selected = faces.contains(face); - println!("Selected: {}", selected); + for (i, face) in all_faces.iter().enumerate() { + let polygon = face.as_polygon(); + let color = COLORS[i % COLORS.len()]; + let selected = faces.contains(face); + println!("Selected: {}", selected); - svg_doc = svg_doc.add(draw_polygon(&polygon, color, selected)); - } + svg_doc = svg_doc.add(draw_polygon(&polygon, color, selected)); + } - svg::save(format!("bench-faceselector-report/{}.svg", name), &svg_doc).unwrap(); + svg::save(format!("bench-faceselector-report/{}.svg", name), &svg_doc).unwrap(); } pub fn draw_polygon(polygon: &geo::Polygon, color: &str, selected: bool) -> SvgPath { - let mut data = Data::new(); + let mut data = Data::new(); - for line in polygon.exterior().lines() { - data = data.move_to((line.start.x, line.start.y)); - data = data.line_to((line.end.x, line.end.y)); - } + for line in polygon.exterior().lines() { + data = data.move_to((line.start.x, line.start.y)); + data = data.line_to((line.end.x, line.end.y)); + } - for hole in polygon.interiors() { - for line in hole.lines() { - data = data.move_to((line.start.x, line.start.y)); - data = data.line_to((line.end.x, line.end.y)); - } - } - data = data.close(); + for hole in polygon.interiors() { + for line in hole.lines() { + data = data.move_to((line.start.x, line.start.y)); + data = data.line_to((line.end.x, line.end.y)); + } + } + data = data.close(); - SvgPath::new() - // TODO: Fill doesn't work! - .set("fill", color) - .set("fill-opacity", if selected { "0.5" } else { "0" }) - .set("stroke", color) - .set("stroke-width", 1) - .set("d", data) + SvgPath::new() + // TODO: Fill doesn't work! + .set("fill", color) + .set("fill-opacity", if selected { "0.5" } else { "0" }) + .set("stroke", color) + .set("stroke-width", 1) + .set("d", data) } diff --git a/packages/cadmium/benches/faceselector-report/main.rs b/packages/cadmium/benches/faceselector-report/main.rs index 8844c796..69e391c5 100644 --- a/packages/cadmium/benches/faceselector-report/main.rs +++ b/packages/cadmium/benches/faceselector-report/main.rs @@ -10,8 +10,8 @@ use cadmium::workbench::AddSketch; use cadmium::IDType; pub trait TestCase: std::fmt::Debug { - fn pre_selection(&self, p: &mut Project, sketch_id: IDType); - fn post_selection(&self, p: &mut Project, sketch_id: IDType); + fn pre_selection(&self, p: &mut Project, sketch_id: IDType); + fn post_selection(&self, p: &mut Project, sketch_id: IDType); } mod draw; @@ -23,108 +23,108 @@ use draw::*; use report::*; fn create_project() -> (Project, IDType) { - let mut p = Project::new("Test Project"); - let plane_description = PlaneDescription::PlaneId(0); - let sketch_id = IDWrap { - id: 0, - inner: AddSketch { plane_description }, - } - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - IDWrap { - id: 0, - inner: IDWrap { - id: sketch_id, - inner: SketchAddPointMessage { x: 0.0, y: 0.0 }, - }, - } - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - - (p, sketch_id) + let mut p = Project::new("Test Project"); + let plane_description = PlaneDescription::PlaneId(0); + let sketch_id = IDWrap { + id: 0, + inner: AddSketch { plane_description }, + } + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + IDWrap { + id: 0, + inner: IDWrap { + id: sketch_id, + inner: SketchAddPointMessage { x: 0.0, y: 0.0 }, + }, + } + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + + (p, sketch_id) } #[derive(Debug)] pub enum FaceSelectorType { - ID(cadmium::isketch::face::IDSelector), - Centroid(cadmium::isketch::face::CentroidSelector), + ID(cadmium::isketch::face::IDSelector), + Centroid(cadmium::isketch::face::CentroidSelector), } impl FaceSelector for FaceSelectorType { - fn get_selected_faces( - &self, - isketch: &cadmium::isketch::ISketch, - ) -> Vec { - match self { - FaceSelectorType::ID(selector) => selector.get_selected_faces(isketch), - FaceSelectorType::Centroid(selector) => selector.get_selected_faces(isketch), - } - } - - fn from_face_ids(_sketch: &cadmium::isketch::ISketch, _ids: Vec) -> Self { - unimplemented!() - } + fn get_selected_faces( + &self, + isketch: &cadmium::isketch::ISketch, + ) -> Vec { + match self { + FaceSelectorType::ID(selector) => selector.get_selected_faces(isketch), + FaceSelectorType::Centroid(selector) => selector.get_selected_faces(isketch), + } + } + + fn from_face_ids(_sketch: &cadmium::isketch::ISketch, _ids: Vec) -> Self { + unimplemented!() + } } fn main() { - // Create report dir - fs::create_dir_all("bench-faceselector-report").unwrap(); - - let mut results = vec![]; - let cases: Vec<(Box, IDType)> = vec![ - (Box::new(simple_circles::SingleCircle()), 0), - (Box::new(simple_circles::SingleCircleAddAnother()), 0), - ]; - for case in cases.iter() { - let (case_struct, index) = case; - let (mut p, sketch_id) = create_project(); - - case_struct.pre_selection(&mut p, sketch_id); - let sketch_ref = p - .get_workbench_by_id(0) - .unwrap() - .borrow() - .get_sketch_by_id(sketch_id) - .unwrap(); - - let selectors = vec![ - FaceSelectorType::ID(cadmium::isketch::face::IDSelector::from_face_ids( - &sketch_ref.borrow(), - vec![*index], - )), - FaceSelectorType::Centroid(cadmium::isketch::face::CentroidSelector::from_face_ids( - &sketch_ref.borrow(), - vec![*index], - )), - ]; - - for selector in selectors.iter() { - println!("Drawing faces for selector: {:?}", selector); - let case_name = format!("{:?}", case_struct); - let selector_name_full = format!("{:?}", selector); - let selector_name_variant = selector_name_full.split_once(" ").unwrap().0; - let selector_name = selector_name_variant.split_once("(").unwrap().1; - let name = format!("{}_{}", selector_name, case_name); - results.push(( - selector_name.to_string(), - case_name.to_string(), - name.clone(), - )); - - draw_sketch_faces(&mut p, selector, *index, format!("{}_before", name)); - } - - case_struct.post_selection(&mut p, sketch_id); - - for (id, selector) in selectors.iter().enumerate() { - let name = results[id].2.clone(); - println!("Name: {}", name); - draw_sketch_faces(&mut p, selector, *index, format!("{}_after", name)); - } - } - - println!("results: {:?}", results); - save_report_html(results); + // Create report dir + fs::create_dir_all("bench-faceselector-report").unwrap(); + + let mut results = vec![]; + let cases: Vec<(Box, IDType)> = vec![ + (Box::new(simple_circles::SingleCircle()), 0), + (Box::new(simple_circles::SingleCircleAddAnother()), 0), + ]; + for case in cases.iter() { + let (case_struct, index) = case; + let (mut p, sketch_id) = create_project(); + + case_struct.pre_selection(&mut p, sketch_id); + let sketch_ref = p + .get_workbench_by_id(0) + .unwrap() + .borrow() + .get_sketch_by_id(sketch_id) + .unwrap(); + + let selectors = vec![ + FaceSelectorType::ID(cadmium::isketch::face::IDSelector::from_face_ids( + &sketch_ref.borrow(), + vec![*index], + )), + FaceSelectorType::Centroid(cadmium::isketch::face::CentroidSelector::from_face_ids( + &sketch_ref.borrow(), + vec![*index], + )), + ]; + + for selector in selectors.iter() { + println!("Drawing faces for selector: {:?}", selector); + let case_name = format!("{:?}", case_struct); + let selector_name_full = format!("{:?}", selector); + let selector_name_variant = selector_name_full.split_once(" ").unwrap().0; + let selector_name = selector_name_variant.split_once("(").unwrap().1; + let name = format!("{}_{}", selector_name, case_name); + results.push(( + selector_name.to_string(), + case_name.to_string(), + name.clone(), + )); + + draw_sketch_faces(&mut p, selector, *index, format!("{}_before", name)); + } + + case_struct.post_selection(&mut p, sketch_id); + + for (id, selector) in selectors.iter().enumerate() { + let name = results[id].2.clone(); + println!("Name: {}", name); + draw_sketch_faces(&mut p, selector, *index, format!("{}_after", name)); + } + } + + println!("results: {:?}", results); + save_report_html(results); } diff --git a/packages/cadmium/benches/faceselector-report/report.rs b/packages/cadmium/benches/faceselector-report/report.rs index ebce82c0..9b9057f7 100644 --- a/packages/cadmium/benches/faceselector-report/report.rs +++ b/packages/cadmium/benches/faceselector-report/report.rs @@ -43,11 +43,11 @@ tbody > tr:nth-of-type(even) { const HTML_FOOT: &str = ""; pub fn save_report_html(results: Vec<(String, String, String)>) { - let mut report = HTML_HEAD.to_string(); + let mut report = HTML_HEAD.to_string(); - for (selector, case, name) in results.iter() { - report.push_str(&format!( - r#" + for (selector, case, name) in results.iter() { + report.push_str(&format!( + r#" {selector} {case} @@ -55,9 +55,9 @@ pub fn save_report_html(results: Vec<(String, String, String)>) { "# - )) - } + )) + } - report.push_str(HTML_FOOT); - fs::write("bench-faceselector-report/index.html", report).unwrap(); + report.push_str(HTML_FOOT); + fs::write("bench-faceselector-report/index.html", report).unwrap(); } diff --git a/packages/cadmium/benches/faceselector-report/simple_circles.rs b/packages/cadmium/benches/faceselector-report/simple_circles.rs index dec51692..8a465307 100644 --- a/packages/cadmium/benches/faceselector-report/simple_circles.rs +++ b/packages/cadmium/benches/faceselector-report/simple_circles.rs @@ -8,43 +8,43 @@ use crate::TestCase; #[derive(Debug)] pub struct SingleCircle(); impl TestCase for SingleCircle { - fn pre_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { - IDWrap { - id: 0, - inner: IDWrap { - id: sketch_id, - inner: AddCircle { - center: 0, - radius: 10.0, - }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - } - fn post_selection(&self, _p: &mut cadmium::project::Project, _sketch_id: IDType) {} + fn pre_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { + IDWrap { + id: 0, + inner: IDWrap { + id: sketch_id, + inner: AddCircle { + center: 0, + radius: 10.0, + }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + } + fn post_selection(&self, _p: &mut cadmium::project::Project, _sketch_id: IDType) {} } #[derive(Debug)] pub struct SingleCircleAddAnother(); impl TestCase for SingleCircleAddAnother { - fn pre_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { - SingleCircle().pre_selection(p, sketch_id) - } - fn post_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { - IDWrap { - id: 0, - inner: IDWrap { - id: sketch_id, - inner: AddCircle { - center: 0, - radius: 20.0, - }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - } + fn pre_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { + SingleCircle().pre_selection(p, sketch_id) + } + fn post_selection(&self, p: &mut cadmium::project::Project, sketch_id: IDType) { + IDWrap { + id: 0, + inner: IDWrap { + id: sketch_id, + inner: AddCircle { + center: 0, + radius: 20.0, + }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + } } diff --git a/packages/cadmium/examples/project_simple_extrusion.rs b/packages/cadmium/examples/project_simple_extrusion.rs index 5e5dd6f6..acc14f6e 100644 --- a/packages/cadmium/examples/project_simple_extrusion.rs +++ b/packages/cadmium/examples/project_simple_extrusion.rs @@ -9,89 +9,89 @@ use cadmium::step::{StepHash, StepResult}; use cadmium::workbench::AddSketch; fn main() { - let mut p = Project::new("Test Project"); - let wb_hash = StepHash::from_int(0); - let top_hash = p - .workbenches - .get(0) - .unwrap() - .borrow() - .history - .get(1) - .unwrap() - .borrow() - .hash(); - let plane_description = PlaneDescription::PlaneId(top_hash); - let sketch_id = AddSketch { plane_description } - .id_wrap(wb_hash) - .handle_project_message(&mut p) - .unwrap() - .unwrap(); + let mut p = Project::new("Test Project"); + let wb_hash = StepHash::from_int(0); + let top_hash = p + .workbenches + .get(0) + .unwrap() + .borrow() + .history + .get(1) + .unwrap() + .borrow() + .hash(); + let plane_description = PlaneDescription::PlaneId(top_hash); + let sketch_id = AddSketch { plane_description } + .id_wrap(wb_hash) + .handle_project_message(&mut p) + .unwrap() + .unwrap(); - let wb_ref = p.workbenches.first().unwrap().clone(); - let step = wb_ref.borrow().get_step_by_hash(sketch_id).unwrap(); + let wb_ref = p.workbenches.first().unwrap().clone(); + let step = wb_ref.borrow().get_step_by_hash(sketch_id).unwrap(); - let StepResult::Sketch(sketch) = step.borrow().result.clone() else { - panic!("Expected a sketch"); - }; + let StepResult::Sketch(sketch) = step.borrow().result.clone() else { + panic!("Expected a sketch"); + }; - let ll = SketchAddPointMessage { x: 0.0, y: 0.0 } - .id_wrap(sketch_id) - .id_wrap(wb_hash) - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - let lr = SketchAddPointMessage { x: 40.0, y: 0.0 } - .id_wrap(sketch_id) - .id_wrap(wb_hash) - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - let ul = SketchAddPointMessage { x: 0.0, y: 40.0 } - .id_wrap(sketch_id) - .id_wrap(wb_hash) - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - let ur = SketchAddPointMessage { x: 40.0, y: 40.0 } - .id_wrap(sketch_id) - .id_wrap(wb_hash) - .handle_project_message(&mut p) - .unwrap() - .unwrap(); + let ll = SketchAddPointMessage { x: 0.0, y: 0.0 } + .id_wrap(sketch_id) + .id_wrap(wb_hash) + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + let lr = SketchAddPointMessage { x: 40.0, y: 0.0 } + .id_wrap(sketch_id) + .id_wrap(wb_hash) + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + let ul = SketchAddPointMessage { x: 0.0, y: 40.0 } + .id_wrap(sketch_id) + .id_wrap(wb_hash) + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + let ur = SketchAddPointMessage { x: 40.0, y: 40.0 } + .id_wrap(sketch_id) + .id_wrap(wb_hash) + .handle_project_message(&mut p) + .unwrap() + .unwrap(); - AddLine { start: ll, end: lr } - .handle_message(sketch.clone()) - .unwrap(); - AddLine { start: lr, end: ur } - .handle_message(sketch.clone()) - .unwrap(); - AddLine { start: ur, end: ul } - .handle_message(sketch.clone()) - .unwrap(); - AddLine { start: ul, end: ll } - .handle_message(sketch.clone()) - .unwrap(); + AddLine { start: ll, end: lr } + .handle_message(sketch.clone()) + .unwrap(); + AddLine { start: lr, end: ur } + .handle_message(sketch.clone()) + .unwrap(); + AddLine { start: ur, end: ul } + .handle_message(sketch.clone()) + .unwrap(); + AddLine { start: ul, end: ll } + .handle_message(sketch.clone()) + .unwrap(); - extrusion::Add { - sketch_id, - faces: vec![0], - length: 25.0, - offset: 0.0, - direction: Direction::Normal, - mode: Mode::New, - } - .handle_message(wb_ref.clone()) - .unwrap(); + extrusion::Add { + sketch_id, + faces: vec![0], + length: 25.0, + offset: 0.0, + direction: Direction::Normal, + mode: Mode::New, + } + .handle_message(wb_ref.clone()) + .unwrap(); - let wb = wb_ref.borrow(); - let feature_ref = wb.features.first_key_value().unwrap().1; - let solid_like = feature_ref.borrow().as_solid_like().to_solids().unwrap(); - let solid = solid_like.get(0).unwrap(); + let wb = wb_ref.borrow(); + let feature_ref = wb.features.first_key_value().unwrap().1; + let solid_like = feature_ref.borrow().as_solid_like().to_solids().unwrap(); + let solid = solid_like.get(0).unwrap(); - println!("{:?}", solid); + println!("{:?}", solid); - println!("Dump example files"); - solid.save_as_step("example.step"); - solid.save_as_obj("example.obj", 0.001); + println!("Dump example files"); + solid.save_as_step("example.step"); + solid.save_as_obj("example.obj", 0.001); } diff --git a/packages/cadmium/src/archetypes.rs b/packages/cadmium/src/archetypes.rs index d6b94d3b..7405f4db 100644 --- a/packages/cadmium/src/archetypes.rs +++ b/packages/cadmium/src/archetypes.rs @@ -11,210 +11,210 @@ use crate::IDType; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub enum PlaneDescription { - PlaneId(StepHash), - SolidFace { solid_id: IDType, normal: Vector3 }, + PlaneId(StepHash), + SolidFace { solid_id: IDType, normal: Vector3 }, } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Plane { - pub origin: Point3, - pub primary: Vector3, - pub secondary: Vector3, - pub tertiary: Vector3, // aka Normal + pub origin: Point3, + pub primary: Vector3, + pub secondary: Vector3, + pub tertiary: Vector3, // aka Normal } impl Plane { - /* - - z y - - ^ ^ - | / - | / - |/ - |--------> x - - So "front" is xz plane with -y normal - and "top" is xy plane with z normal - and "right" is yz plane with x normal - - */ - - pub fn new(origin: Point3, primary: Vector3, secondary: Vector3, tertiary: Vector3) -> Self { - Plane { - origin, - primary, - secondary, - tertiary, - } - } - - pub fn front() -> Self { - Plane { - origin: Point3::new(0.0, 0.0, 0.0), - primary: Vector3::new(1.0, 0.0, 0.0), - secondary: Vector3::new(0.0, 0.0, 1.0), - tertiary: Vector3::new(0.0, -1.0, 0.0), - } - } - - pub fn top() -> Self { - Plane { - origin: Point3::new(0.0, 0.0, 0.0), - primary: Vector3::new(1.0, 0.0, 0.0), - secondary: Vector3::new(0.0, 1.0, 0.0), - tertiary: Vector3::new(0.0, 0.0, 1.0), - } - } - - pub fn right() -> Self { - Plane { - origin: Point3::new(0.0, 0.0, 0.0), - primary: Vector3::new(0.0, 1.0, 0.0), - secondary: Vector3::new(0.0, 0.0, 1.0), - tertiary: Vector3::new(1.0, 0.0, 0.0), - } - } - - pub fn from_truck(tp: TruckPlane) -> Self { - let o = tp.origin(); - let u = tp.u_axis().normalize(); - let v = tp.v_axis().normalize(); - let n = tp.normal().normalize(); - Plane { - origin: Point3::new(o.x, o.y, o.z), - primary: Vector3::new(u.x, u.y, u.z), - secondary: Vector3::new(v.x, v.y, v.z), - tertiary: Vector3::new(n.x, n.y, n.z), - } - } - - pub fn project(&self, point: &Point3) -> ISOPoint2 { - let minus_origin = point.minus(&self.origin); - let x = minus_origin.dot(&self.primary); - let y = minus_origin.dot(&self.secondary); - ISOPoint2::new(x, y) - } - - pub fn unproject(&self, point: &ISOPoint2) -> Point3 { - let x = self.origin.plus(self.primary.times(point.x())); - let y = self.origin.plus(self.secondary.times(point.y())); - x.plus(y).to_point3() - } + /* + + z y + + ^ ^ + | / + | / + |/ + |--------> x + + So "front" is xz plane with -y normal + and "top" is xy plane with z normal + and "right" is yz plane with x normal + + */ + + pub fn new(origin: Point3, primary: Vector3, secondary: Vector3, tertiary: Vector3) -> Self { + Plane { + origin, + primary, + secondary, + tertiary, + } + } + + pub fn front() -> Self { + Plane { + origin: Point3::new(0.0, 0.0, 0.0), + primary: Vector3::new(1.0, 0.0, 0.0), + secondary: Vector3::new(0.0, 0.0, 1.0), + tertiary: Vector3::new(0.0, -1.0, 0.0), + } + } + + pub fn top() -> Self { + Plane { + origin: Point3::new(0.0, 0.0, 0.0), + primary: Vector3::new(1.0, 0.0, 0.0), + secondary: Vector3::new(0.0, 1.0, 0.0), + tertiary: Vector3::new(0.0, 0.0, 1.0), + } + } + + pub fn right() -> Self { + Plane { + origin: Point3::new(0.0, 0.0, 0.0), + primary: Vector3::new(0.0, 1.0, 0.0), + secondary: Vector3::new(0.0, 0.0, 1.0), + tertiary: Vector3::new(1.0, 0.0, 0.0), + } + } + + pub fn from_truck(tp: TruckPlane) -> Self { + let o = tp.origin(); + let u = tp.u_axis().normalize(); + let v = tp.v_axis().normalize(); + let n = tp.normal().normalize(); + Plane { + origin: Point3::new(o.x, o.y, o.z), + primary: Vector3::new(u.x, u.y, u.z), + secondary: Vector3::new(v.x, v.y, v.z), + tertiary: Vector3::new(n.x, n.y, n.z), + } + } + + pub fn project(&self, point: &Point3) -> ISOPoint2 { + let minus_origin = point.minus(&self.origin); + let x = minus_origin.dot(&self.primary); + let y = minus_origin.dot(&self.secondary); + ISOPoint2::new(x, y) + } + + pub fn unproject(&self, point: &ISOPoint2) -> Point3 { + let x = self.origin.plus(self.primary.times(point.x())); + let y = self.origin.plus(self.secondary.times(point.y())); + x.plus(y).to_point3() + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Vector2 { - pub x: f64, - pub y: f64, + pub x: f64, + pub y: f64, } impl Vector2 { - pub fn new(x: f64, y: f64) -> Self { - Vector2 { x, y } - } + pub fn new(x: f64, y: f64) -> Self { + Vector2 { x, y } + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Vector3 { - pub x: f64, - pub y: f64, - pub z: f64, + pub x: f64, + pub y: f64, + pub z: f64, } impl Vector3 { - pub fn new(x: f64, y: f64, z: f64) -> Self { - Vector3 { x, y, z } - } - - pub fn to_point3(&self) -> Point3 { - Point3::new(self.x, self.y, self.z) - } - - pub fn times(&self, s: f64) -> Self { - Self { - x: self.x * s, - y: self.y * s, - z: self.z * s, - } - } - - pub fn plus(&self, v: Self) -> Self { - Self { - x: self.x + v.x, - y: self.y + v.y, - z: self.z + v.z, - } - } - - pub fn dot(&self, other: &Vector3) -> f64 { - self.x * other.x + self.y * other.y + self.z * other.z - } + pub fn new(x: f64, y: f64, z: f64) -> Self { + Vector3 { x, y, z } + } + + pub fn to_point3(&self) -> Point3 { + Point3::new(self.x, self.y, self.z) + } + + pub fn times(&self, s: f64) -> Self { + Self { + x: self.x * s, + y: self.y * s, + z: self.z * s, + } + } + + pub fn plus(&self, v: Self) -> Self { + Self { + x: self.x + v.x, + y: self.y + v.y, + z: self.z + v.z, + } + } + + pub fn dot(&self, other: &Vector3) -> f64 { + self.x * other.x + self.y * other.y + self.z * other.z + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Line3 { - pub start: u64, - pub end: u64, + pub start: u64, + pub end: u64, } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Arc3 { - pub center: u64, - pub start: u64, - pub end: u64, - pub clockwise: bool, + pub center: u64, + pub start: u64, + pub end: u64, + pub clockwise: bool, } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Circle3 { - pub center: u64, - pub radius: f64, - pub top: u64, + pub center: u64, + pub radius: f64, + pub top: u64, } // --- ISOtope wrappers --- pub trait FromSketchPrimitive { - fn from_sketch(sketch: &isotope::sketch::Sketch, primitive: &T) -> Self; + fn from_sketch(sketch: &isotope::sketch::Sketch, primitive: &T) -> Self; } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Point2 { - pub x: f64, - pub y: f64, - pub hidden: bool, + pub x: f64, + pub y: f64, + pub hidden: bool, } impl From for ISOPoint2 { - fn from(val: Point2) -> Self { - ISOPoint2::new(val.x, val.y) - } + fn from(val: Point2) -> Self { + ISOPoint2::new(val.x, val.y) + } } impl FromSketchPrimitive for Point2 { - fn from_sketch(_sketch: &isotope::sketch::Sketch, primitive: &ISOPoint2) -> Self { - Self { - x: primitive.x(), - y: primitive.y(), - hidden: false, - } - } + fn from_sketch(_sketch: &isotope::sketch::Sketch, primitive: &ISOPoint2) -> Self { + Self { + x: primitive.x(), + y: primitive.y(), + hidden: false, + } + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Arc2 { - pub center: StepHash, - pub radius: f64, - pub clockwise: bool, - pub start_angle: f64, - pub end_angle: f64, + pub center: StepHash, + pub radius: f64, + pub clockwise: bool, + pub start_angle: f64, + pub end_angle: f64, } // impl FromSketchPrimitive for Arc2 { @@ -237,8 +237,8 @@ pub struct Arc2 { #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Circle2 { - pub center: StepHash, - pub radius: f64, + pub center: StepHash, + pub radius: f64, } // impl FromSketchPrimitive for Circle2 { @@ -258,8 +258,8 @@ pub struct Circle2 { #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Line2 { - pub start: StepHash, - pub end: StepHash, + pub start: StepHash, + pub end: StepHash, } // impl FromSketchPrimitive for Line2 { @@ -281,10 +281,10 @@ pub struct Line2 { #[tsify(into_wasm_abi, from_wasm_abi)] #[serde(tag = "type")] pub enum WrappedPrimitive { - Point2(Point2), - Line2(Line2), - Arc2(Arc2), - Circle2(Circle2), + Point2(Point2), + Line2(Line2), + Arc2(Arc2), + Circle2(Circle2), } // impl WrappedPrimitive { diff --git a/packages/cadmium/src/bin/gen-types.rs b/packages/cadmium/src/bin/gen-types.rs index 47f991fc..808dce3e 100644 --- a/packages/cadmium/src/bin/gen-types.rs +++ b/packages/cadmium/src/bin/gen-types.rs @@ -3,51 +3,51 @@ use std::io::Write; use convert_case::{Case, Casing}; fn main() { - let mut output = std::fs::File::create("../shared/cadmium-api.ts").unwrap(); - - writeln!( - output, - "// This file is generated by the gen-types binary, do not edit" - ) - .unwrap(); - writeln!(output, "import {{ Direction, IDType, MessageResult, Mode, Plane, PlaneDescription, StepHash }} from \"cadmium\"").unwrap(); - writeln!( - output, - "import {{ sendWasmMessage }} from \"./projectUtils\"" - ) - .unwrap(); - - let all_defs = cadmium::Project::gen_typescript_defs(); - - for (name, fields) in &all_defs { - let iface_fields = fields.join("; "); - let fn_name = name.to_case(Case::Camel); - let fn_params_full = fields.join(", "); - let fn_params = fn_params_full.trim_end_matches(", "); - let iface_params_full = fields - .iter() - .map(|f| f.split(":").next().unwrap().trim()) - .collect::>() - .join(", "); - let iface_params = iface_params_full.trim_end_matches(", "); - - writeln!( - output, - r#" + let mut output = std::fs::File::create("../shared/cadmium-api.ts").unwrap(); + + writeln!( + output, + "// This file is generated by the gen-types binary, do not edit" + ) + .unwrap(); + writeln!(output, "import {{ Direction, IDType, MessageResult, Mode, Plane, PlaneDescription, StepHash }} from \"cadmium\"").unwrap(); + writeln!( + output, + "import {{ sendWasmMessage }} from \"./projectUtils\"" + ) + .unwrap(); + + let all_defs = cadmium::Project::gen_typescript_defs(); + + for (name, fields) in &all_defs { + let iface_fields = fields.join("; "); + let fn_name = name.to_case(Case::Camel); + let fn_params_full = fields.join(", "); + let fn_params = fn_params_full.trim_end_matches(", "); + let iface_params_full = fields + .iter() + .map(|f| f.split(":").next().unwrap().trim()) + .collect::>() + .join(", "); + let iface_params = iface_params_full.trim_end_matches(", "); + + writeln!( + output, + r#" export interface {name} {{ {iface_fields} }} export function {fn_name}({fn_params}): MessageResult {{ const message: Message = {{ type: "{name}", {iface_params} }}; return sendWasmMessage(message); }}"# - ) - .unwrap(); - } + ) + .unwrap(); + } - let message_variants = all_defs - .iter() - .map(|(name, _)| format!("{{ type: \"{name}\" }} & {name}")) - .collect::>() - .join(" | "); + let message_variants = all_defs + .iter() + .map(|(name, _)| format!("{{ type: \"{name}\" }} & {name}")) + .collect::>() + .join(" | "); - writeln!(output, "export type Message = {message_variants};").unwrap(); + writeln!(output, "export type Message = {message_variants};").unwrap(); } diff --git a/packages/cadmium/src/error.rs b/packages/cadmium/src/error.rs index 34111819..061f972a 100644 --- a/packages/cadmium/src/error.rs +++ b/packages/cadmium/src/error.rs @@ -2,35 +2,35 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum CADmiumError { - // Message errors - #[error("The project ID {0} was not found")] - ProjectIDNotFound(usize), - #[error("The workbench ID {0} was not found")] - WorkbenchIDNotFound(u64), - #[error("The workbench name {0} was not found")] - WorkbenchNameNotFound(String), - #[error("The step ID {0} was not found")] - StepIDNotFound(String), - #[error("The step name {0} was not found")] - StepNameNotFound(String), - #[error("The sketch ID {0} was not found")] - SketchIDNotFound(u64), + // Message errors + #[error("The project ID {0} was not found")] + ProjectIDNotFound(usize), + #[error("The workbench ID {0} was not found")] + WorkbenchIDNotFound(u64), + #[error("The workbench name {0} was not found")] + WorkbenchNameNotFound(String), + #[error("The step ID {0} was not found")] + StepIDNotFound(String), + #[error("The step name {0} was not found")] + StepNameNotFound(String), + #[error("The sketch ID {0} was not found")] + SketchIDNotFound(u64), - // RealSketch errors - #[error("The primitive could not be found inside the sketch")] - PrimitiveNotInSketch, - #[error("Couldn't calculate the 3D position of the supplied point")] - Point3DCalculationFailed, - #[error("The calculated 3D point was not found in the sketch")] - Point3DNotFound, + // RealSketch errors + #[error("The primitive could not be found inside the sketch")] + PrimitiveNotInSketch, + #[error("Couldn't calculate the 3D position of the supplied point")] + Point3DCalculationFailed, + #[error("The calculated 3D point was not found in the sketch")] + Point3DNotFound, - // StepData errors - #[error("The step {0} data type is not as expected")] - IncorrectStepDataType(String), + // StepData errors + #[error("The step {0} data type is not as expected")] + IncorrectStepDataType(String), - #[error("This function is not implemented yet")] - NotImplemented, + #[error("This function is not implemented yet")] + NotImplemented, - #[error(transparent)] - Other(#[from] anyhow::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), } diff --git a/packages/cadmium/src/feature/extrusion.rs b/packages/cadmium/src/feature/extrusion.rs index 5b994b51..7f006b69 100644 --- a/packages/cadmium/src/feature/extrusion.rs +++ b/packages/cadmium/src/feature/extrusion.rs @@ -21,199 +21,199 @@ use super::{Feature, SolidLike}; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub enum Mode { - New, - Add(Vec), - Remove(Vec), + New, + Add(Vec), + Remove(Vec), } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub enum Direction { - Normal, - NegativeNormal, - Specified(Vector3), + Normal, + NegativeNormal, + Specified(Vector3), } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Extrusion { - pub faces: Selector, - pub sketch: Rc>, - pub length: f64, - pub offset: f64, - pub direction: Direction, - pub mode: Mode, + pub faces: Selector, + pub sketch: Rc>, + pub length: f64, + pub offset: f64, + pub direction: Direction, + pub mode: Mode, } impl Extrusion { - pub fn new( - faces: Vec, - sketch: Rc>, - length: f64, - offset: f64, - direction: Direction, - mode: Mode, - ) -> Self { - Extrusion { - faces: Selector::from_face_ids(&sketch.clone().borrow(), faces), - sketch, - length, - offset, - direction, - mode, - } - } + pub fn new( + faces: Vec, + sketch: Rc>, + length: f64, + offset: f64, + direction: Direction, + mode: Mode, + ) -> Self { + Extrusion { + faces: Selector::from_face_ids(&sketch.clone().borrow(), faces), + sketch, + length, + offset, + direction, + mode, + } + } } impl SolidLike for Extrusion { - fn references(&self) -> Vec>> { - // self.faces.iter().map(|f| FeatureCell::Face(f.clone())).collect() - todo!("Extrusion::references") - } - - fn to_feature(&self) -> Feature { - Feature::Extrusion(self.clone()) - } - - fn get_truck_solids(&self) -> anyhow::Result> { - let sketch = self.sketch.borrow(); - let plane = sketch.plane.borrow().clone(); - - let extrusion_direction = match &self.direction { - Direction::Normal => plane.tertiary.clone(), - Direction::NegativeNormal => plane.tertiary.times(-1.0), - Direction::Specified(vector) => vector.clone(), - }; - - let extrusion_vector = extrusion_direction.times(self.length - self.offset); - let offset_vector = extrusion_direction.times(self.offset); - let extrusion_tvector = - TruckVector3::new(extrusion_vector.x, extrusion_vector.y, extrusion_vector.z); - let offset_tvector = TruckVector3::new(offset_vector.x, offset_vector.y, offset_vector.z); - - Ok(self - .faces - .get_selected_faces(&sketch) - .iter() - .map(|f| { - let wires = get_isoface_wires(self.sketch.clone(), &f).unwrap(); - let face = builder::try_attach_plane(&wires).unwrap(); - - // Can we calculate ALL the wires at once and not iter-sweep? - let sweep = builder::tsweep(&face, extrusion_tvector); - - builder::translated(&sweep, offset_tvector) - }) - .collect()) - } + fn references(&self) -> Vec>> { + // self.faces.iter().map(|f| FeatureCell::Face(f.clone())).collect() + todo!("Extrusion::references") + } + + fn to_feature(&self) -> Feature { + Feature::Extrusion(self.clone()) + } + + fn get_truck_solids(&self) -> anyhow::Result> { + let sketch = self.sketch.borrow(); + let plane = sketch.plane.borrow().clone(); + + let extrusion_direction = match &self.direction { + Direction::Normal => plane.tertiary.clone(), + Direction::NegativeNormal => plane.tertiary.times(-1.0), + Direction::Specified(vector) => vector.clone(), + }; + + let extrusion_vector = extrusion_direction.times(self.length - self.offset); + let offset_vector = extrusion_direction.times(self.offset); + let extrusion_tvector = + TruckVector3::new(extrusion_vector.x, extrusion_vector.y, extrusion_vector.z); + let offset_tvector = TruckVector3::new(offset_vector.x, offset_vector.y, offset_vector.z); + + Ok(self + .faces + .get_selected_faces(&sketch) + .iter() + .map(|f| { + let wires = get_isoface_wires(self.sketch.clone(), &f).unwrap(); + let face = builder::try_attach_plane(&wires).unwrap(); + + // Can we calculate ALL the wires at once and not iter-sweep? + let sweep = builder::tsweep(&face, extrusion_tvector); + + builder::translated(&sweep, offset_tvector) + }) + .collect()) + } } impl<'a> TryFrom<&'a mut Feature> for &'a mut Extrusion { - type Error = anyhow::Error; + type Error = anyhow::Error; - // The Feature enum has only 1 variant for now but that will change soon - #[allow(irrefutable_let_patterns)] - fn try_from(value: &'a mut Feature) -> Result { - let Feature::Extrusion(ref mut extrusion) = value else { - return Err(anyhow::anyhow!("Failed to convert Feature to Extrusion")); - }; + // The Feature enum has only 1 variant for now but that will change soon + #[allow(irrefutable_let_patterns)] + fn try_from(value: &'a mut Feature) -> Result { + let Feature::Extrusion(ref mut extrusion) = value else { + return Err(anyhow::anyhow!("Failed to convert Feature to Extrusion")); + }; - Ok(extrusion) - } + Ok(extrusion) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct Add { - pub sketch_id: StepHash, - pub faces: Vec, - pub length: f64, - pub offset: f64, - pub direction: Direction, - pub mode: Mode, + pub sketch_id: StepHash, + pub faces: Vec, + pub length: f64, + pub offset: f64, + pub direction: Direction, + pub mode: Mode, } impl MessageHandler for Add { - // Parent to workbench to add to solids and be able to reference the sketch - type Parent = Rc>; - fn handle_message( - &self, - workbench_ref: Self::Parent, - ) -> anyhow::Result> { - let sketch = >>::from_parent_id(&workbench_ref, self.sketch_id)?; - let mut workbench = workbench_ref.borrow_mut(); - - let extrusion = Extrusion::new( - self.faces.clone(), - sketch.clone(), - self.length, - self.offset, - self.direction.clone(), - self.mode.clone(), - ); - let extrusion_cell = Rc::new(RefCell::new(extrusion.to_feature())); - - let id = workbench.features_next_id; - workbench.features.insert(id, extrusion_cell); - workbench.features_next_id += 1; - let id = workbench.features_next_id - 1; - - Ok(Some(( - id, - StepResult::Solid { - solids: extrusion.to_solids()?, - }, - ))) - } + // Parent to workbench to add to solids and be able to reference the sketch + type Parent = Rc>; + fn handle_message( + &self, + workbench_ref: Self::Parent, + ) -> anyhow::Result> { + let sketch = >>::from_parent_id(&workbench_ref, self.sketch_id)?; + let mut workbench = workbench_ref.borrow_mut(); + + let extrusion = Extrusion::new( + self.faces.clone(), + sketch.clone(), + self.length, + self.offset, + self.direction.clone(), + self.mode.clone(), + ); + let extrusion_cell = Rc::new(RefCell::new(extrusion.to_feature())); + + let id = workbench.features_next_id; + workbench.features.insert(id, extrusion_cell); + workbench.features_next_id += 1; + let id = workbench.features_next_id - 1; + + Ok(Some(( + id, + StepResult::Solid { + solids: extrusion.to_solids()?, + }, + ))) + } } #[cfg(test)] mod tests { - use crate::project::tests::create_test_project; - use crate::project::Project; - - #[test] - #[ignore = "uses old filetype"] - fn project_from_files() { - let file_list = [ - // this file contains three shapes which are adjacent to each other and - // thus should result in a single output solid - ("src/test_inputs/three_adjacent_faces.cadmium", 1), - // this file contains one square nested inside another - // and thus should result in a single output solid - ("src/test_inputs/nested_squares.cadmium", 1), - // this file contains one circle nested inside another - // and thus should result in a single output solid - ("src/test_inputs/nested_circles.cadmium", 1), - ("src/test_inputs/two_Es.cadmium", 1), - ("src/test_inputs/lots_of_nesting.cadmium", 4), - ]; - - for (file, expected_solids) in file_list.iter() { - let contents = std::fs::read_to_string(file).unwrap(); - - // deserialize the contents into a Project - let p: Project = serde_json::from_str(&contents).unwrap(); - - // get a realization - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let workbench = workbench_ref.borrow(); - let solids = &workbench.features; - println!("[{}] solids: {:?}", file, solids.len()); - - assert_eq!(solids.len(), *expected_solids); // doesn't work yet! - } - } - - #[test] - fn step_export() { - let p = create_test_project(); - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let workbench = workbench_ref.borrow(); - let feature = workbench.features.get(&0).unwrap().borrow(); - let solid = &feature.as_solid_like().to_solids().unwrap()[0]; - - solid.save_as_step("pkg/test.step"); - solid.save_as_obj("pkg/test.obj", 0.001); - } + use crate::project::tests::create_test_project; + use crate::project::Project; + + #[test] + #[ignore = "uses old filetype"] + fn project_from_files() { + let file_list = [ + // this file contains three shapes which are adjacent to each other and + // thus should result in a single output solid + ("src/test_inputs/three_adjacent_faces.cadmium", 1), + // this file contains one square nested inside another + // and thus should result in a single output solid + ("src/test_inputs/nested_squares.cadmium", 1), + // this file contains one circle nested inside another + // and thus should result in a single output solid + ("src/test_inputs/nested_circles.cadmium", 1), + ("src/test_inputs/two_Es.cadmium", 1), + ("src/test_inputs/lots_of_nesting.cadmium", 4), + ]; + + for (file, expected_solids) in file_list.iter() { + let contents = std::fs::read_to_string(file).unwrap(); + + // deserialize the contents into a Project + let p: Project = serde_json::from_str(&contents).unwrap(); + + // get a realization + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let workbench = workbench_ref.borrow(); + let solids = &workbench.features; + println!("[{}] solids: {:?}", file, solids.len()); + + assert_eq!(solids.len(), *expected_solids); // doesn't work yet! + } + } + + #[test] + fn step_export() { + let p = create_test_project(); + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let workbench = workbench_ref.borrow(); + let feature = workbench.features.get(&0).unwrap().borrow(); + let solid = &feature.as_solid_like().to_solids().unwrap()[0]; + + solid.save_as_step("pkg/test.step"); + solid.save_as_obj("pkg/test.obj", 0.001); + } } diff --git a/packages/cadmium/src/feature/helpers.rs b/packages/cadmium/src/feature/helpers.rs index 022548ac..119f17da 100644 --- a/packages/cadmium/src/feature/helpers.rs +++ b/packages/cadmium/src/feature/helpers.rs @@ -16,200 +16,200 @@ use super::prelude::*; use crate::isketch::ISketch; pub fn geopoint_to_truckpoint( - point: geo::Point, - sketch: Rc>, + point: geo::Point, + sketch: Rc>, ) -> Result { - let sketch_ref = sketch.borrow(); - let sketch_point = sketch_ref - .find_point_ref(point.x(), point.y()) - .ok_or(anyhow::anyhow!("geo::Point not found in sketch"))?; - let point_3d = sketch_ref.get_point_3d(sketch_point)?.1; - Ok(point_3d.into()) + let sketch_ref = sketch.borrow(); + let sketch_point = sketch_ref + .find_point_ref(point.x(), point.y()) + .ok_or(anyhow::anyhow!("geo::Point not found in sketch"))?; + let point_3d = sketch_ref.get_point_3d(sketch_point)?.1; + Ok(point_3d.into()) } pub fn linestring_to_wire( - line: &LineString, - sketch: Rc>, + line: &LineString, + sketch: Rc>, ) -> Result { - let mut vertices: Vec = Vec::new(); - for point in line.points() { - let vertex = builder::vertex(geopoint_to_truckpoint(point, sketch.clone())?); - vertices.push(vertex); - } - - let mut edges: Vec = Vec::new(); - for i in 0..vertices.len() - 2 { - let edge = builder::line(&vertices[i], &vertices[i + 1]); - edges.push(edge); - } - - // Close the loop by connecting the last vertex to the first - let last_edge = builder::line(&vertices[vertices.len() - 2], &vertices[0]); - edges.push(last_edge); - - Ok(Wire::from_iter(edges)) + let mut vertices: Vec = Vec::new(); + for point in line.points() { + let vertex = builder::vertex(geopoint_to_truckpoint(point, sketch.clone())?); + vertices.push(vertex); + } + + let mut edges: Vec = Vec::new(); + for i in 0..vertices.len() - 2 { + let edge = builder::line(&vertices[i], &vertices[i + 1]); + edges.push(edge); + } + + // Close the loop by connecting the last vertex to the first + let last_edge = builder::line(&vertices[vertices.len() - 2], &vertices[0]); + edges.push(last_edge); + + Ok(Wire::from_iter(edges)) } // It assumes that the feature will start from the same plane as the sketch // To change this, geopoint_to_truckpoint should be modified to accept a plane // and calculate the 3d point from that plane on-demand pub fn get_isoface_wires( - sketch: Rc>, - face: &ISOFace, + sketch: Rc>, + face: &ISOFace, ) -> Result, anyhow::Error> { - let polygon = face.as_polygon(); - let exterior = linestring_to_wire(polygon.exterior(), sketch.clone())?; - let mut interiors = polygon - .interiors() - .iter() - .map(|line| linestring_to_wire(line, sketch.clone())) - .collect::, anyhow::Error>>()?; - interiors.insert(0, exterior); - - Ok(interiors) + let polygon = face.as_polygon(); + let exterior = linestring_to_wire(polygon.exterior(), sketch.clone())?; + let mut interiors = polygon + .interiors() + .iter() + .map(|line| linestring_to_wire(line, sketch.clone())) + .collect::, anyhow::Error>>()?; + interiors.insert(0, exterior); + + Ok(interiors) } pub fn find_enveloped_shapes(faces: &[Face]) -> Vec<(usize, usize)> { - let mut retval = vec![]; - for (a, face_a) in faces.iter().enumerate() { - for (b, face_b) in faces.iter().enumerate() { - if a == b { - continue; - } - - // check if b's exterior is equal to any of a's holes - for hole in face_a.holes.iter() { - if hole == face_b { - retval.push((b, a)); // (small, big) - } - } - } - } - - retval + let mut retval = vec![]; + for (a, face_a) in faces.iter().enumerate() { + for (b, face_b) in faces.iter().enumerate() { + if a == b { + continue; + } + + // check if b's exterior is equal to any of a's holes + for hole in face_a.holes.iter() { + if hole == face_b { + retval.push((b, a)); // (small, big) + } + } + } + } + + retval } pub fn find_adjacent_shapes(faces: &[Face]) -> Option<(usize, usize, Vec, Vec)> { - for (a, face_a) in faces.iter().enumerate() { - for (b, face_b) in faces.iter().enumerate() { - if a >= b { - continue; - } - - let adjacent_edges = face_a.exterior.adjacent_edges(&face_b.exterior); - - match adjacent_edges { - None => continue, - Some(matched_edges) => return Some((a, b, matched_edges.0, matched_edges.1)), - } - } - } - - None + for (a, face_a) in faces.iter().enumerate() { + for (b, face_b) in faces.iter().enumerate() { + if a >= b { + continue; + } + + let adjacent_edges = face_a.exterior.adjacent_edges(&face_b.exterior); + + match adjacent_edges { + None => continue, + Some(matched_edges) => return Some((a, b, matched_edges.0, matched_edges.1)), + } + } + } + + None } pub fn fuse + std::fmt::Debug, S: ShapeOpsSurface + std::fmt::Debug>( - solid0: &TruckTopoSolid, - solid1: &TruckTopoSolid, + solid0: &TruckTopoSolid, + solid1: &TruckTopoSolid, ) -> Option> { - let solid0_boundaries = solid0.boundaries(); - let solid1_boundaries = solid1.boundaries(); - assert!(solid0_boundaries.len() == 1); - assert!(solid1_boundaries.len() == 1); - - let boundary0 = &solid0_boundaries[0]; - let boundary1 = &solid1_boundaries[0]; - let fusable_faces = find_coplanar_face_pairs(boundary0, boundary1, true); - assert!(fusable_faces.len() == 1); - let fusable_faces = fusable_faces[0]; - // TODO: support the case where more than one is fusable - debug!("fusable_faces: {:?}", fusable_faces); - - let secondary_mergeable_faces = find_coplanar_face_pairs(boundary0, boundary1, false); - debug!("secondary_mergeable_faces: {:?}", secondary_mergeable_faces); - - // There's only one fused solid at the end. Create it by cloning solid0 - // and then removing the fusable face from it. - let mut combined = boundary0.clone(); - combined.remove(fusable_faces.0); - - // Meanwhile, make a copy of solid1 and remove the fusable face from it too. - let mut boundary1_copy = boundary1.clone(); - boundary1_copy.remove(fusable_faces.1); - - // Then, add every face from solid1 to the combined solid. - combined.append(&mut boundary1_copy); - - // Lastly, merge the two fusable faces together. This is complicated because - // one might be bigger than the other, or they might be the same size, or - // they might overlap somewhat. We'll need to figure out how to merge them. - // println!("How do I merge these two? {:?}", fusable_faces); - // println!("First:"); - // for edge in boundary0[fusable_faces.0].edge_iter() { - // println!( - // "Edge: {:?} to {:?}", - // edge.front().get_point(), - // edge.back().get_point() - // ); - // } - let mut outer_face = boundary0[fusable_faces.0].clone(); - let inner_face = boundary1[fusable_faces.1].clone(); - outer_face.add_boundary(inner_face.boundaries().first().unwrap().clone()); - - // Then add that merged face to the solid and we've fused! - combined.push(outer_face); - - // After that, we need to merge the secondary_mergeable_faces together. - for (face_0_idx, face_1_idx) in secondary_mergeable_faces { - let mut face_0 = boundary0[face_0_idx].clone(); - let face_1 = boundary1[face_1_idx].clone(); - face_0.add_boundary(face_1.boundaries().first().unwrap().clone()); - combined.push(face_0); - } - - // And then we're done! - // None - Some(TruckTopoSolid::new(vec![combined])) + let solid0_boundaries = solid0.boundaries(); + let solid1_boundaries = solid1.boundaries(); + assert!(solid0_boundaries.len() == 1); + assert!(solid1_boundaries.len() == 1); + + let boundary0 = &solid0_boundaries[0]; + let boundary1 = &solid1_boundaries[0]; + let fusable_faces = find_coplanar_face_pairs(boundary0, boundary1, true); + assert!(fusable_faces.len() == 1); + let fusable_faces = fusable_faces[0]; + // TODO: support the case where more than one is fusable + debug!("fusable_faces: {:?}", fusable_faces); + + let secondary_mergeable_faces = find_coplanar_face_pairs(boundary0, boundary1, false); + debug!("secondary_mergeable_faces: {:?}", secondary_mergeable_faces); + + // There's only one fused solid at the end. Create it by cloning solid0 + // and then removing the fusable face from it. + let mut combined = boundary0.clone(); + combined.remove(fusable_faces.0); + + // Meanwhile, make a copy of solid1 and remove the fusable face from it too. + let mut boundary1_copy = boundary1.clone(); + boundary1_copy.remove(fusable_faces.1); + + // Then, add every face from solid1 to the combined solid. + combined.append(&mut boundary1_copy); + + // Lastly, merge the two fusable faces together. This is complicated because + // one might be bigger than the other, or they might be the same size, or + // they might overlap somewhat. We'll need to figure out how to merge them. + // println!("How do I merge these two? {:?}", fusable_faces); + // println!("First:"); + // for edge in boundary0[fusable_faces.0].edge_iter() { + // println!( + // "Edge: {:?} to {:?}", + // edge.front().get_point(), + // edge.back().get_point() + // ); + // } + let mut outer_face = boundary0[fusable_faces.0].clone(); + let inner_face = boundary1[fusable_faces.1].clone(); + outer_face.add_boundary(inner_face.boundaries().first().unwrap().clone()); + + // Then add that merged face to the solid and we've fused! + combined.push(outer_face); + + // After that, we need to merge the secondary_mergeable_faces together. + for (face_0_idx, face_1_idx) in secondary_mergeable_faces { + let mut face_0 = boundary0[face_0_idx].clone(); + let face_1 = boundary1[face_1_idx].clone(); + face_0.add_boundary(face_1.boundaries().first().unwrap().clone()); + combined.push(face_0); + } + + // And then we're done! + // None + Some(TruckTopoSolid::new(vec![combined])) } fn find_coplanar_face_pairs, S: ShapeOpsSurface>( - boundary0: &Shell, - boundary1: &Shell, - flip_second: bool, + boundary0: &Shell, + boundary1: &Shell, + flip_second: bool, ) -> Vec<(usize, usize)> { - let mut coplanar_faces: Vec<(usize, usize)> = vec![]; - for (face_0_idx, face_0) in boundary0.face_iter().enumerate() { - let surface_0 = face_0.oriented_surface(); - - if let TruckSurface::Plane(p0) = surface_0 { - for (face_1_idx, face_1) in boundary1.face_iter().enumerate() { - let mut surface_1 = face_1.oriented_surface(); - - if flip_second { - surface_1 = surface_1.inverse(); - } - - if let TruckSurface::Plane(p1) = surface_1 { - if are_coplanar(p0, p1) { - coplanar_faces.push((face_0_idx, face_1_idx)); - } - } - } - } - } - - coplanar_faces + let mut coplanar_faces: Vec<(usize, usize)> = vec![]; + for (face_0_idx, face_0) in boundary0.face_iter().enumerate() { + let surface_0 = face_0.oriented_surface(); + + if let TruckSurface::Plane(p0) = surface_0 { + for (face_1_idx, face_1) in boundary1.face_iter().enumerate() { + let mut surface_1 = face_1.oriented_surface(); + + if flip_second { + surface_1 = surface_1.inverse(); + } + + if let TruckSurface::Plane(p1) = surface_1 { + if are_coplanar(p0, p1) { + coplanar_faces.push((face_0_idx, face_1_idx)); + } + } + } + } + } + + coplanar_faces } fn are_coplanar(p0: TruckPlane, p1: TruckPlane) -> bool { - let normal0 = p0.normal(); - let normal1 = p1.normal(); + let normal0 = p0.normal(); + let normal1 = p1.normal(); - if !normal0.near(&normal1) { - return false; - } + if !normal0.near(&normal1) { + return false; + } - let difference = p0.origin() - p1.origin(); - let dot = normal0.dot(difference); - dot.abs() < 0.0001 + let difference = p0.origin() - p1.origin(); + let dot = normal0.dot(difference); + dot.abs() < 0.0001 } diff --git a/packages/cadmium/src/feature/mod.rs b/packages/cadmium/src/feature/mod.rs index d6cc3379..d9fe4497 100644 --- a/packages/cadmium/src/feature/mod.rs +++ b/packages/cadmium/src/feature/mod.rs @@ -15,31 +15,31 @@ pub mod solid; use prelude::*; pub trait SolidLike: Debug { - fn references(&self) -> Vec>>; - fn get_truck_solids(&self) -> anyhow::Result>; - fn to_feature(&self) -> Feature; - - fn to_solids(&self) -> anyhow::Result> { - let truck_solids = self.get_truck_solids()?; - - Ok(truck_solids - .iter() - .map(|truck_solid| Solid::from_truck_solid("".to_owned(), truck_solid.clone())) - .collect()) - } + fn references(&self) -> Vec>>; + fn get_truck_solids(&self) -> anyhow::Result>; + fn to_feature(&self) -> Feature; + + fn to_solids(&self) -> anyhow::Result> { + let truck_solids = self.get_truck_solids()?; + + Ok(truck_solids + .iter() + .map(|truck_solid| Solid::from_truck_solid("".to_owned(), truck_solid.clone())) + .collect()) + } } #[derive(Tsify, Debug, Serialize, Deserialize, Clone)] #[tsify(into_wasm_abi, from_wasm_abi)] #[non_exhaustive] pub enum Feature { - Extrusion(extrusion::Extrusion), + Extrusion(extrusion::Extrusion), } impl Feature { - pub fn as_solid_like(&self) -> &dyn SolidLike { - match self { - Feature::Extrusion(extrusion) => extrusion, - } - } + pub fn as_solid_like(&self) -> &dyn SolidLike { + match self { + Feature::Extrusion(extrusion) => extrusion, + } + } } diff --git a/packages/cadmium/src/feature/point.rs b/packages/cadmium/src/feature/point.rs index d45a418f..c95c7165 100644 --- a/packages/cadmium/src/feature/point.rs +++ b/packages/cadmium/src/feature/point.rs @@ -12,95 +12,95 @@ use crate::archetypes::{Plane, Vector3}; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Point3 { - pub x: f64, - pub y: f64, - pub z: f64, - pub hidden: bool, + pub x: f64, + pub y: f64, + pub z: f64, + pub hidden: bool, } impl Point3 { - pub fn new(x: f64, y: f64, z: f64) -> Self { - Point3 { - x, - y, - z, - hidden: false, - } - } - - pub fn plus(&self, v: Vector3) -> Vector3 { - Vector3 { - x: self.x + v.x, - y: self.y + v.y, - z: self.z + v.z, - } - } - - pub fn minus(&self, other: &Point3) -> Vector3 { - Vector3 { - x: self.x - other.x, - y: self.y - other.y, - z: self.z - other.z, - } - } - - pub fn distance_to(&self, other: &Point3) -> f64 { - let dx = self.x - other.x; - let dy = self.y - other.y; - let dz = self.z - other.z; - (dx * dx + dy * dy + dz * dz).sqrt() - } - - pub fn from_plane_point(plane: &Plane, point: &ISOPoint2) -> Point3 { - let o = plane.origin.clone(); - let x = plane.primary.clone(); - let y = plane.secondary.clone(); - - let pt3 = o.plus(x.times(point.x())).plus(y.times(point.y())); - Point3::new(pt3.x, pt3.y, pt3.z) - } + pub fn new(x: f64, y: f64, z: f64) -> Self { + Point3 { + x, + y, + z, + hidden: false, + } + } + + pub fn plus(&self, v: Vector3) -> Vector3 { + Vector3 { + x: self.x + v.x, + y: self.y + v.y, + z: self.z + v.z, + } + } + + pub fn minus(&self, other: &Point3) -> Vector3 { + Vector3 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + } + } + + pub fn distance_to(&self, other: &Point3) -> f64 { + let dx = self.x - other.x; + let dy = self.y - other.y; + let dz = self.z - other.z; + (dx * dx + dy * dy + dz * dz).sqrt() + } + + pub fn from_plane_point(plane: &Plane, point: &ISOPoint2) -> Point3 { + let o = plane.origin.clone(); + let x = plane.primary.clone(); + let y = plane.secondary.clone(); + + let pt3 = o.plus(x.times(point.x())).plus(y.times(point.y())); + Point3::new(pt3.x, pt3.y, pt3.z) + } } impl From for PolyTruckPoint3 { - fn from(val: Point3) -> Self { - PolyTruckPoint3 { - x: val.x, - y: val.y, - z: val.z, - } - } + fn from(val: Point3) -> Self { + PolyTruckPoint3 { + x: val.x, + y: val.y, + z: val.z, + } + } } impl Add for Point3 { - type Output = Point3; - - fn add(self, other: Point3) -> Point3 { - Point3 { - x: self.x + other.x, - y: self.y + other.y, - z: self.z + other.z, - hidden: false, - } - } + type Output = Point3; + + fn add(self, other: Point3) -> Point3 { + Point3 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + hidden: false, + } + } } impl Sub for Point3 { - type Output = Point3; - - fn sub(self, other: Point3) -> Point3 { - Point3 { - x: self.x - other.x, - y: self.y - other.y, - z: self.z - other.z, - hidden: false, - } - } + type Output = Point3; + + fn sub(self, other: Point3) -> Point3 { + Point3 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + hidden: false, + } + } } impl PartialEq for Point3 { - fn eq(&self, other: &Self) -> bool { - self.x == other.x && self.y == other.y && self.z == other.z - } + fn eq(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y && self.z == other.z + } } use crate::message::{Identifiable, MessageHandler}; @@ -109,48 +109,48 @@ use crate::workbench::Workbench; use crate::IDType; impl Identifiable for Rc> { - type Parent = Rc>; - const ID_NAME: &'static str = "point_id"; - - fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { - let step = parent - .borrow() - .get_step_by_hash(hash) - .ok_or(anyhow::anyhow!( - "No step with hash {} exists in the current workbench", - hash - ))? - .clone(); - - let StepResult::Point(point) = step.borrow().result.clone() else { - return Err(anyhow::anyhow!( - "The step with hash {} is not a point", - hash - )); - }; - - Ok(point) - } + type Parent = Rc>; + const ID_NAME: &'static str = "point_id"; + + fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { + let step = parent + .borrow() + .get_step_by_hash(hash) + .ok_or(anyhow::anyhow!( + "No step with hash {} exists in the current workbench", + hash + ))? + .clone(); + + let StepResult::Point(point) = step.borrow().result().clone() else { + return Err(anyhow::anyhow!( + "The step with hash {} is not a point", + hash + )); + }; + + Ok(point) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct WorkbenchPointUpdate { - x: f64, - y: f64, - z: f64, + x: f64, + y: f64, + z: f64, } impl MessageHandler for WorkbenchPointUpdate { - type Parent = Rc>; - fn handle_message( - &self, - point_ref: Rc>, - ) -> anyhow::Result> { - let mut point = point_ref.borrow_mut(); - point.x = self.x; - point.y = self.y; - point.z = self.z; - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + point_ref: Rc>, + ) -> anyhow::Result> { + let mut point = point_ref.borrow_mut(); + point.x = self.x; + point.y = self.y; + point.z = self.z; + Ok(None) + } } diff --git a/packages/cadmium/src/feature/prelude.rs b/packages/cadmium/src/feature/prelude.rs index d0cd17ad..c9b29737 100644 --- a/packages/cadmium/src/feature/prelude.rs +++ b/packages/cadmium/src/feature/prelude.rs @@ -21,4 +21,4 @@ pub use truck_modeling::Vertex as TruckVertex; pub use truck_topology::Solid as TruckTopoSolid; pub type TruckClosedSolid = - TruckTopoSolid; + TruckTopoSolid; diff --git a/packages/cadmium/src/feature/solid.rs b/packages/cadmium/src/feature/solid.rs index 4847f55a..35e47207 100644 --- a/packages/cadmium/src/feature/solid.rs +++ b/packages/cadmium/src/feature/solid.rs @@ -18,129 +18,129 @@ use super::prelude::*; #[derive(Tsify, Debug, Serialize, Deserialize, Clone)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Solid { - pub name: String, - pub crc32: String, - pub vertices: Vec, - pub normals: Vec, - pub uvs: Vec, - pub indices: Vec, - pub triangles: Vec>, - pub truck_solid: TruckClosedSolid, + pub name: String, + pub crc32: String, + pub vertices: Vec, + pub normals: Vec, + pub uvs: Vec, + pub indices: Vec, + pub triangles: Vec>, + pub truck_solid: TruckClosedSolid, } impl Solid { - pub fn from_truck_solid(name: String, truck_solid: TruckClosedSolid) -> Self { - let mut solid = Solid { - name, - crc32: "".to_owned(), - vertices: vec![], - normals: vec![], - triangles: vec![], - uvs: vec![], - indices: vec![], - truck_solid, - }; - let mut mesh = solid.truck_solid.triangulation(MESH_TOLERANCE).to_polygon(); - mesh.put_together_same_attrs(MESH_TOLERANCE); - - // the mesh is prepared for obj export, but we need to convert it - // to a format compatible for rendering - // We have to brute force this. Go through every single triangle - // and emit three positions, three normals, and three uvs. - let mut index = 0_usize; - for face in mesh.tri_faces() { - for v in face.iter() { - let vertex_index = v.pos; - let normal_index = v.nor.unwrap(); - let uv_index = v.uv.unwrap(); - let vertex = mesh.positions()[vertex_index]; - let normal = mesh.normals()[normal_index]; - let uv = mesh.uv_coords()[uv_index]; - - let pt = Vector3::new(vertex.x, vertex.y, vertex.z); - solid.vertices.push(pt); - solid - .normals - .push(Vector3::new(normal.x, normal.y, normal.z)); - solid.uvs.push(Vector2::new(uv.x, uv.y)); - solid.indices.push(index); - - index += 1; - } - } - - // compute the crc32 of the vertices - let mut hasher = crc32fast::Hasher::new(); - for vertex in solid.vertices.iter() { - hasher.update(&vertex.x.to_be_bytes()); - hasher.update(&vertex.y.to_be_bytes()); - hasher.update(&vertex.z.to_be_bytes()); - } - solid.crc32 = format!("{:x}", hasher.finalize()); - - solid - } - - pub fn get_face_by_normal(&self, normal: &Vector3) -> Option { - let truck_solid = &self.truck_solid; - let boundaries = &truck_solid.boundaries()[0]; - - let mut candidate_faces: Vec = vec![]; - - boundaries.face_iter().for_each(|face| { - let oriented_surface = face.oriented_surface(); - - if let truck_modeling::geometry::Surface::Plane(p) = oriented_surface { - let this_face_normal = p.normal(); - - if (normal.x - this_face_normal.x).abs() < 0.0001 - && (normal.y - this_face_normal.y).abs() < 0.0001 - && (normal.z - this_face_normal.z).abs() < 0.0001 - { - candidate_faces.push(face.clone()); - } - } - }); - - match candidate_faces.len() { - 0 => None, - 1 => Some(candidate_faces[0].clone()), - _ => panic!("More than one face with the same normal!"), - } - } - - pub fn to_obj_string(&self, tolerance: f64) -> String { - let mut mesh = self.truck_solid.triangulation(tolerance).to_polygon(); - mesh.put_together_same_attrs(MESH_TOLERANCE); - let mut buf = Vec::new(); - obj::write(&mesh, &mut buf).unwrap(); - - String::from_utf8(buf).unwrap() - } - - pub fn save_as_obj(&self, filename: &str, tolerance: f64) { - let mut mesh = self.truck_solid.triangulation(tolerance).to_polygon(); - mesh.put_together_same_attrs(MESH_TOLERANCE); - let file = std::fs::File::create(filename).unwrap(); - obj::write(&mesh, file).unwrap(); - } - - pub fn to_step_string(&self) -> String { - let compressed = self.truck_solid.compress(); - - out::CompleteStepDisplay::new( - out::StepModel::from(&compressed), - out::StepHeaderDescriptor { - organization_system: "cadmium-shape-to-step".to_owned(), - ..Default::default() - }, - ) - .to_string() - } - - pub fn save_as_step(&self, filename: &str) { - let step_text = self.to_step_string(); - let mut step_file = std::fs::File::create(filename).unwrap(); - std::io::Write::write_all(&mut step_file, step_text.as_ref()).unwrap(); - } + pub fn from_truck_solid(name: String, truck_solid: TruckClosedSolid) -> Self { + let mut solid = Solid { + name, + crc32: "".to_owned(), + vertices: vec![], + normals: vec![], + triangles: vec![], + uvs: vec![], + indices: vec![], + truck_solid, + }; + let mut mesh = solid.truck_solid.triangulation(MESH_TOLERANCE).to_polygon(); + mesh.put_together_same_attrs(MESH_TOLERANCE); + + // the mesh is prepared for obj export, but we need to convert it + // to a format compatible for rendering + // We have to brute force this. Go through every single triangle + // and emit three positions, three normals, and three uvs. + let mut index = 0_usize; + for face in mesh.tri_faces() { + for v in face.iter() { + let vertex_index = v.pos; + let normal_index = v.nor.unwrap(); + let uv_index = v.uv.unwrap(); + let vertex = mesh.positions()[vertex_index]; + let normal = mesh.normals()[normal_index]; + let uv = mesh.uv_coords()[uv_index]; + + let pt = Vector3::new(vertex.x, vertex.y, vertex.z); + solid.vertices.push(pt); + solid + .normals + .push(Vector3::new(normal.x, normal.y, normal.z)); + solid.uvs.push(Vector2::new(uv.x, uv.y)); + solid.indices.push(index); + + index += 1; + } + } + + // compute the crc32 of the vertices + let mut hasher = crc32fast::Hasher::new(); + for vertex in solid.vertices.iter() { + hasher.update(&vertex.x.to_be_bytes()); + hasher.update(&vertex.y.to_be_bytes()); + hasher.update(&vertex.z.to_be_bytes()); + } + solid.crc32 = format!("{:x}", hasher.finalize()); + + solid + } + + pub fn get_face_by_normal(&self, normal: &Vector3) -> Option { + let truck_solid = &self.truck_solid; + let boundaries = &truck_solid.boundaries()[0]; + + let mut candidate_faces: Vec = vec![]; + + boundaries.face_iter().for_each(|face| { + let oriented_surface = face.oriented_surface(); + + if let truck_modeling::geometry::Surface::Plane(p) = oriented_surface { + let this_face_normal = p.normal(); + + if (normal.x - this_face_normal.x).abs() < 0.0001 + && (normal.y - this_face_normal.y).abs() < 0.0001 + && (normal.z - this_face_normal.z).abs() < 0.0001 + { + candidate_faces.push(face.clone()); + } + } + }); + + match candidate_faces.len() { + 0 => None, + 1 => Some(candidate_faces[0].clone()), + _ => panic!("More than one face with the same normal!"), + } + } + + pub fn to_obj_string(&self, tolerance: f64) -> String { + let mut mesh = self.truck_solid.triangulation(tolerance).to_polygon(); + mesh.put_together_same_attrs(MESH_TOLERANCE); + let mut buf = Vec::new(); + obj::write(&mesh, &mut buf).unwrap(); + + String::from_utf8(buf).unwrap() + } + + pub fn save_as_obj(&self, filename: &str, tolerance: f64) { + let mut mesh = self.truck_solid.triangulation(tolerance).to_polygon(); + mesh.put_together_same_attrs(MESH_TOLERANCE); + let file = std::fs::File::create(filename).unwrap(); + obj::write(&mesh, file).unwrap(); + } + + pub fn to_step_string(&self) -> String { + let compressed = self.truck_solid.compress(); + + out::CompleteStepDisplay::new( + out::StepModel::from(&compressed), + out::StepHeaderDescriptor { + organization_system: "cadmium-shape-to-step".to_owned(), + ..Default::default() + }, + ) + .to_string() + } + + pub fn save_as_step(&self, filename: &str) { + let step_text = self.to_step_string(); + let mut step_file = std::fs::File::create(filename).unwrap(); + std::io::Write::write_all(&mut step_file, step_text.as_ref()).unwrap(); + } } diff --git a/packages/cadmium/src/isketch/compound.rs b/packages/cadmium/src/isketch/compound.rs index dfe021a8..07cbf2ee 100644 --- a/packages/cadmium/src/isketch/compound.rs +++ b/packages/cadmium/src/isketch/compound.rs @@ -7,30 +7,30 @@ use tsify_next::Tsify; use super::compound_rectangle; pub trait CompoundLike: Debug { - fn references(&self) -> Vec; - fn created_references(&self) -> Vec; - fn populate_created_references( - &self, - sketch: &mut isotope::sketch::Sketch, - ) -> anyhow::Result<()> { - for reference in self.created_references() { - sketch.add_primitive(reference)?; - } - Ok(()) - } + fn references(&self) -> Vec; + fn created_references(&self) -> Vec; + fn populate_created_references( + &self, + sketch: &mut isotope::sketch::Sketch, + ) -> anyhow::Result<()> { + for reference in self.created_references() { + sketch.add_primitive(reference)?; + } + Ok(()) + } } #[derive(Tsify, Debug, Serialize, Deserialize, Clone)] #[tsify(into_wasm_abi, from_wasm_abi)] #[non_exhaustive] pub enum Compound { - Rectangle(compound_rectangle::Rectangle), + Rectangle(compound_rectangle::Rectangle), } impl Compound { - pub fn as_compound_like(&self) -> &dyn CompoundLike { - match self { - Compound::Rectangle(rectangle) => rectangle, - } - } + pub fn as_compound_like(&self) -> &dyn CompoundLike { + match self { + Compound::Rectangle(rectangle) => rectangle, + } + } } diff --git a/packages/cadmium/src/isketch/compound_rectangle.rs b/packages/cadmium/src/isketch/compound_rectangle.rs index 39c2e7e1..c82631b3 100644 --- a/packages/cadmium/src/isketch/compound_rectangle.rs +++ b/packages/cadmium/src/isketch/compound_rectangle.rs @@ -17,93 +17,93 @@ use super::ISketch; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Rectangle { - start: Rc>, - end: Rc>, - new_points: [Rc>; 2], - new_lines: [Rc>; 4], + start: Rc>, + end: Rc>, + new_points: [Rc>; 2], + new_lines: [Rc>; 4], } impl Rectangle { - pub fn new(start: Rc>, end: Rc>) -> Self { - let start_point = start.borrow().clone(); - let end_point = end.borrow().clone(); + pub fn new(start: Rc>, end: Rc>) -> Self { + let start_point = start.borrow().clone(); + let end_point = end.borrow().clone(); - let other_start = Rc::new(RefCell::new(Point2::new(start_point.x(), end_point.y()))); - let other_end = Rc::new(RefCell::new(Point2::new(end_point.x(), start_point.y()))); + let other_start = Rc::new(RefCell::new(Point2::new(start_point.x(), end_point.y()))); + let other_end = Rc::new(RefCell::new(Point2::new(end_point.x(), start_point.y()))); - let new_lines = [ - Rc::new(RefCell::new(Line::new(start.clone(), other_start.clone()))), - Rc::new(RefCell::new(Line::new(other_start.clone(), end.clone()))), - Rc::new(RefCell::new(Line::new(end.clone(), other_end.clone()))), - Rc::new(RefCell::new(Line::new(other_end.clone(), start.clone()))), - ]; - Self { - start, - end, - new_points: [other_start, other_end], - new_lines, - } - } + let new_lines = [ + Rc::new(RefCell::new(Line::new(start.clone(), other_start.clone()))), + Rc::new(RefCell::new(Line::new(other_start.clone(), end.clone()))), + Rc::new(RefCell::new(Line::new(end.clone(), other_end.clone()))), + Rc::new(RefCell::new(Line::new(other_end.clone(), start.clone()))), + ]; + Self { + start, + end, + new_points: [other_start, other_end], + new_lines, + } + } } impl CompoundLike for Rectangle { - fn references(&self) -> Vec { - vec![ - PrimitiveCell::Point2(self.start.clone()), - PrimitiveCell::Point2(self.end.clone()), - ] - } + fn references(&self) -> Vec { + vec![ + PrimitiveCell::Point2(self.start.clone()), + PrimitiveCell::Point2(self.end.clone()), + ] + } - fn created_references(&self) -> Vec { - vec![ - PrimitiveCell::Point2(self.new_points[0].clone()), - PrimitiveCell::Point2(self.new_points[1].clone()), - PrimitiveCell::Line(self.new_lines[0].clone()), - PrimitiveCell::Line(self.new_lines[1].clone()), - PrimitiveCell::Line(self.new_lines[2].clone()), - PrimitiveCell::Line(self.new_lines[3].clone()), - ] - } + fn created_references(&self) -> Vec { + vec![ + PrimitiveCell::Point2(self.new_points[0].clone()), + PrimitiveCell::Point2(self.new_points[1].clone()), + PrimitiveCell::Line(self.new_lines[0].clone()), + PrimitiveCell::Line(self.new_lines[1].clone()), + PrimitiveCell::Line(self.new_lines[2].clone()), + PrimitiveCell::Line(self.new_lines[3].clone()), + ] + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct Add { - pub start: IDType, - pub end: IDType, + pub start: IDType, + pub end: IDType, } impl MessageHandler for Add { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Rc>, - ) -> anyhow::Result> { - let mut isketch = sketch_ref.borrow_mut(); - let mut sketch = isketch.sketch.borrow_mut(); + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Rc>, + ) -> anyhow::Result> { + let mut isketch = sketch_ref.borrow_mut(); + let mut sketch = isketch.sketch.borrow_mut(); - let start_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(self.start).unwrap() { - point - } else { - return Err(anyhow::anyhow!("Start point is not a point")); - }; - let end_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(self.end).unwrap() { - point - } else { - return Err(anyhow::anyhow!("End point is not a point")); - }; + let start_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(self.start).unwrap() { + point + } else { + return Err(anyhow::anyhow!("Start point is not a point")); + }; + let end_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(self.end).unwrap() { + point + } else { + return Err(anyhow::anyhow!("End point is not a point")); + }; - let rectangle = Rectangle::new(start_point.clone(), end_point.clone()); - rectangle.populate_created_references(&mut sketch)?; - drop(sketch); + let rectangle = Rectangle::new(start_point.clone(), end_point.clone()); + rectangle.populate_created_references(&mut sketch)?; + drop(sketch); - let compound = Rc::new(RefCell::new(Compound::Rectangle(rectangle))); - let rectangle_id = isketch.compounds_next_id; - isketch.compounds.insert(rectangle_id, compound.clone()); - isketch.compounds_next_id += 1; + let compound = Rc::new(RefCell::new(Compound::Rectangle(rectangle))); + let rectangle_id = isketch.compounds_next_id; + isketch.compounds.insert(rectangle_id, compound.clone()); + isketch.compounds_next_id += 1; - Ok(Some((rectangle_id, compound.into_result(&isketch)))) - } + Ok(Some((rectangle_id, compound.into_result(&isketch)))) + } } diff --git a/packages/cadmium/src/isketch/face.rs b/packages/cadmium/src/isketch/face.rs index cbb393aa..d9e378ff 100644 --- a/packages/cadmium/src/isketch/face.rs +++ b/packages/cadmium/src/isketch/face.rs @@ -15,77 +15,77 @@ use super::ISketch; pub type Selector = IDSelector; pub trait FaceSelector { - fn get_selected_faces(&self, isketch: &ISketch) -> Vec; - fn from_face_ids(sketch: &ISketch, ids: Vec) -> Self; + fn get_selected_faces(&self, isketch: &ISketch) -> Vec; + fn from_face_ids(sketch: &ISketch, ids: Vec) -> Self; } /// The most simple selector, just select faces by their ID /// If the number or order of faces change for any reason, this selector will break #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IDSelector { - pub ids: Vec, + pub ids: Vec, } impl FaceSelector for IDSelector { - fn get_selected_faces(&self, sketch: &ISketch) -> Vec { - sketch.get_face_ids(self.ids.clone()) - } + fn get_selected_faces(&self, sketch: &ISketch) -> Vec { + sketch.get_face_ids(self.ids.clone()) + } - fn from_face_ids(_sketch: &ISketch, ids: Vec) -> Self { - Self { ids } - } + fn from_face_ids(_sketch: &ISketch, ids: Vec) -> Self { + Self { ids } + } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CentroidSelector { - pub centroids: Vec, + pub centroids: Vec, } impl CentroidSelector { - pub fn get_face_centroid(&self, face: &Face) -> Point2 { - let centroid = face.as_polygon().centroid().unwrap(); - Point2::new(centroid.x(), centroid.y()) - } + pub fn get_face_centroid(&self, face: &Face) -> Point2 { + let centroid = face.as_polygon().centroid().unwrap(); + Point2::new(centroid.x(), centroid.y()) + } } impl FaceSelector for CentroidSelector { - fn get_selected_faces(&self, sketch: &ISketch) -> Vec { - self.centroids - .iter() - .filter_map(|c| { - let point = geo::Point::new(c.x(), c.y()); - let faces = sketch.faces(); - let min = faces.iter().min_by(|a, b| { - let Some(a_centroid) = &a.as_polygon().centroid() else { - return std::cmp::Ordering::Greater; - }; - let Some(b_centroid) = &b.as_polygon().centroid() else { - return std::cmp::Ordering::Greater; - }; - let a_distance = a_centroid.euclidean_distance(&point); - let b_distance = b_centroid.euclidean_distance(&point); - a_distance.partial_cmp(&b_distance).unwrap() - }); + fn get_selected_faces(&self, sketch: &ISketch) -> Vec { + self.centroids + .iter() + .filter_map(|c| { + let point = geo::Point::new(c.x(), c.y()); + let faces = sketch.faces(); + let min = faces.iter().min_by(|a, b| { + let Some(a_centroid) = &a.as_polygon().centroid() else { + return std::cmp::Ordering::Greater; + }; + let Some(b_centroid) = &b.as_polygon().centroid() else { + return std::cmp::Ordering::Greater; + }; + let a_distance = a_centroid.euclidean_distance(&point); + let b_distance = b_centroid.euclidean_distance(&point); + a_distance.partial_cmp(&b_distance).unwrap() + }); - min.cloned() - }) - .collect_vec() - } + min.cloned() + }) + .collect_vec() + } - fn from_face_ids(sketch: &ISketch, ids: Vec) -> Self { - Self { - centroids: sketch - .get_face_ids(ids) - .iter() - .filter_map(|f| { - // We're straight-up skipping faces without a centroid - if let Some(centroid) = f.as_polygon().centroid() { - Some(Point2::new(centroid.x(), centroid.y())) - } else { - None - } - }) - .collect(), - } - } + fn from_face_ids(sketch: &ISketch, ids: Vec) -> Self { + Self { + centroids: sketch + .get_face_ids(ids) + .iter() + .filter_map(|f| { + // We're straight-up skipping faces without a centroid + if let Some(centroid) = f.as_polygon().centroid() { + Some(Point2::new(centroid.x(), centroid.y())) + } else { + None + } + }) + .collect(), + } + } } diff --git a/packages/cadmium/src/isketch/mod.rs b/packages/cadmium/src/isketch/mod.rs index 48bd0b58..5afc7d69 100644 --- a/packages/cadmium/src/isketch/mod.rs +++ b/packages/cadmium/src/isketch/mod.rs @@ -25,150 +25,150 @@ pub mod primitive; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct ISketch { - // TODO: Make it private with a setter - pub plane: Rc>, - - sketch: Rc>, - primitive_map: BTreeMap, - compounds: BTreeMap>>, - compounds_next_id: u64, - points_3d: BTreeMap, + // TODO: Make it private with a setter + pub plane: Rc>, + + sketch: Rc>, + primitive_map: BTreeMap, + compounds: BTreeMap>>, + compounds_next_id: u64, + points_3d: BTreeMap, } impl ISketch { - pub fn new(plane: Rc>) -> Self { - // The key difference between Sketch and RealSketch is that Sketch lives - // in 2D and RealSketch lives in 3D. So we need to convert the points - - let mut real_sketch = Self { - plane: plane.clone(), - points_3d: BTreeMap::new(), - primitive_map: BTreeMap::new(), - compounds: BTreeMap::new(), - compounds_next_id: 0, - sketch: Rc::new(RefCell::new(Sketch::new())), - }; - - for (id, point) in real_sketch.sketch.borrow().get_all_points().iter() { - real_sketch.points_3d.insert( - *id, - Point3::from_plane_point(&plane.borrow().clone(), point), - ); - } - - real_sketch - } - - pub fn try_from_plane_description( - wb: &Workbench, - plane_description: &PlaneDescription, - ) -> anyhow::Result { - let plane = - match plane_description { - PlaneDescription::PlaneId(plane_hash) => { - let plane_id = ID_MAP.with_borrow(|m| m.get(plane_hash).cloned()).ok_or( - anyhow::anyhow!("Failed to find plane with hash {}", plane_hash), - )?; - wb.planes - .get(&plane_id) - .ok_or(anyhow::anyhow!("Failed to find plane with id {}", plane_id))? - } - PlaneDescription::SolidFace { - solid_id: _, - normal: _, - } => todo!("Implement SolidFace"), - } - .clone(); - Ok(Self::new(plane)) - } - - /// Helper function to go from an isotope point2D to a point_3D, as calculated during new - pub fn get_point_3d( - &self, - point: Rc>, - ) -> Result<(u64, Point3), CADmiumError> { - let cell = PrimitiveCell::Point2(point.clone()); - let point_id = self.sketch.borrow().get_primitive_id(&cell).unwrap(); - - if let Some(result) = self.points_3d.get(&point_id) { - Ok((point_id, result.clone())) - } else { - // TODO: While I'd like to calculate and add the point_3d here, we'll pollute everything with mut - // let point_3d = Point3::from_plane_point(&self.plane.borrow(), &point.borrow()); - - // Ok((point_id, - // self.points_3d - // .insert(point_id, point_3d) - // .ok_or(CADmiumError::Point3DCalculationFailed)?)) - Err(CADmiumError::Point3DCalculationFailed) - } - } - - pub fn sketch(&self) -> Rc> { - self.sketch.clone() - } - - pub fn faces(&self) -> Vec { - self.sketch.borrow().get_merged_faces() - } - - pub fn get_face_ids(&self, ids: Vec) -> Vec { - self.sketch - .borrow() - .get_merged_faces() - .iter() - .enumerate() - .filter_map(|(id, f)| { - if ids.contains(&(id as IDType)) { - Some(f.clone()) - } else { - None - } - }) - .collect() - } - - pub fn find_point_ref(&self, x: f64, y: f64) -> Option>> { - self.sketch - .borrow() - .primitives() - .iter() - .find_map(|(_, prim)| { - if let PrimitiveCell::Point2(point_ref) = prim { - let point = point_ref.borrow(); - if (point.x() - x).abs() < 0.0001 && (point.y() - y).abs() < 0.0001 { - Some(point_ref.clone()) - } else { - None - } - } else { - None - } - }) - } + pub fn new(plane: Rc>) -> Self { + // The key difference between Sketch and RealSketch is that Sketch lives + // in 2D and RealSketch lives in 3D. So we need to convert the points + + let mut real_sketch = Self { + plane: plane.clone(), + points_3d: BTreeMap::new(), + primitive_map: BTreeMap::new(), + compounds: BTreeMap::new(), + compounds_next_id: 0, + sketch: Rc::new(RefCell::new(Sketch::new())), + }; + + for (id, point) in real_sketch.sketch.borrow().get_all_points().iter() { + real_sketch.points_3d.insert( + *id, + Point3::from_plane_point(&plane.borrow().clone(), point), + ); + } + + real_sketch + } + + pub fn try_from_plane_description( + wb: &Workbench, + plane_description: &PlaneDescription, + ) -> anyhow::Result { + let plane = + match plane_description { + PlaneDescription::PlaneId(plane_hash) => { + let plane_id = ID_MAP.with_borrow(|m| m.get(plane_hash).cloned()).ok_or( + anyhow::anyhow!("Failed to find plane with hash {}", plane_hash), + )?; + wb.planes + .get(&plane_id) + .ok_or(anyhow::anyhow!("Failed to find plane with id {}", plane_id))? + } + PlaneDescription::SolidFace { + solid_id: _, + normal: _, + } => todo!("Implement SolidFace"), + } + .clone(); + Ok(Self::new(plane)) + } + + /// Helper function to go from an isotope point2D to a point_3D, as calculated during new + pub fn get_point_3d( + &self, + point: Rc>, + ) -> Result<(u64, Point3), CADmiumError> { + let cell = PrimitiveCell::Point2(point.clone()); + let point_id = self.sketch.borrow().get_primitive_id(&cell).unwrap(); + + if let Some(result) = self.points_3d.get(&point_id) { + Ok((point_id, result.clone())) + } else { + // TODO: While I'd like to calculate and add the point_3d here, we'll pollute everything with mut + // let point_3d = Point3::from_plane_point(&self.plane.borrow(), &point.borrow()); + + // Ok((point_id, + // self.points_3d + // .insert(point_id, point_3d) + // .ok_or(CADmiumError::Point3DCalculationFailed)?)) + Err(CADmiumError::Point3DCalculationFailed) + } + } + + pub fn sketch(&self) -> Rc> { + self.sketch.clone() + } + + pub fn faces(&self) -> Vec { + self.sketch.borrow().get_merged_faces() + } + + pub fn get_face_ids(&self, ids: Vec) -> Vec { + self.sketch + .borrow() + .get_merged_faces() + .iter() + .enumerate() + .filter_map(|(id, f)| { + if ids.contains(&(id as IDType)) { + Some(f.clone()) + } else { + None + } + }) + .collect() + } + + pub fn find_point_ref(&self, x: f64, y: f64) -> Option>> { + self.sketch + .borrow() + .primitives() + .iter() + .find_map(|(_, prim)| { + if let PrimitiveCell::Point2(point_ref) = prim { + let point = point_ref.borrow(); + if (point.x() - x).abs() < 0.0001 && (point.y() - y).abs() < 0.0001 { + Some(point_ref.clone()) + } else { + None + } + } else { + None + } + }) + } } impl Identifiable for Rc> { - type Parent = Rc>; - const ID_NAME: &'static str = "sketch_id"; - - fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { - let step = parent - .borrow() - .get_step_by_hash(hash) - .ok_or(anyhow::anyhow!( - "No step with hash {} exists in the current workbench", - hash - ))? - .clone(); - - let StepResult::Sketch(sketch) = step.borrow().result.clone() else { - return Err(anyhow::anyhow!( - "The step with hash {} is not a sketch", - hash - )); - }; - - Ok(sketch) - } + type Parent = Rc>; + const ID_NAME: &'static str = "sketch_id"; + + fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { + let step = parent + .borrow() + .get_step_by_hash(hash) + .ok_or(anyhow::anyhow!( + "No step with hash {} exists in the current workbench", + hash + ))? + .clone(); + + let StepResult::Sketch(sketch) = step.borrow().result().clone() else { + return Err(anyhow::anyhow!( + "The step with hash {} is not a sketch", + hash + )); + }; + + Ok(sketch) + } } diff --git a/packages/cadmium/src/isketch/primitive.rs b/packages/cadmium/src/isketch/primitive.rs index 8ef4f2c5..213ed1cd 100644 --- a/packages/cadmium/src/isketch/primitive.rs +++ b/packages/cadmium/src/isketch/primitive.rs @@ -18,195 +18,195 @@ use super::ISketch; #[message(ISketch, rename_parent = "Sketch")] pub fn add_point(&mut self, x: f64, y: f64) -> anyhow::Result> { - let point = archetypes::Point2 { - x, - y, - hidden: false, - }; - let point_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Point2( - point.clone(), - ))); - // TODO: link - - let iso_point = ISOPoint2::new(x, y); - let iso_point_cell = PrimitiveCell::Point2(Rc::new(RefCell::new(iso_point.clone()))); - - // TODO: On plane change the 3D points have to be recalculated - let plane = self.plane.borrow().clone(); - let point_id = self.sketch().borrow_mut().add_primitive(iso_point_cell)?; - self.points_3d - .insert(point_id, Point3::from_plane_point(&plane, &iso_point)); - - Ok(Some((point_id, point_wrapped.into_result(self)))) + let point = archetypes::Point2 { + x, + y, + hidden: false, + }; + let point_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Point2( + point.clone(), + ))); + // TODO: link + + let iso_point = ISOPoint2::new(x, y); + let iso_point_cell = PrimitiveCell::Point2(Rc::new(RefCell::new(iso_point.clone()))); + + // TODO: On plane change the 3D points have to be recalculated + let plane = self.plane.borrow().clone(); + let point_id = self.sketch().borrow_mut().add_primitive(iso_point_cell)?; + self.points_3d + .insert(point_id, Point3::from_plane_point(&plane, &iso_point)); + + Ok(Some((point_id, point_wrapped.into_result(self)))) } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddArc { - pub center: StepHash, - pub radius: f64, - pub clockwise: bool, - pub start_angle: f64, - pub end_angle: f64, + pub center: StepHash, + pub radius: f64, + pub clockwise: bool, + pub start_angle: f64, + pub end_angle: f64, } impl MessageHandler for AddArc { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Self::Parent, - ) -> anyhow::Result> { - let isketch = sketch_ref.borrow(); - // let center_id = isketch.primitive_map.get(&self.center).unwrap(); - let center_id = crate::ID_MAP - .with_borrow(|m| m.get(&self.center).cloned()) - .ok_or(anyhow::anyhow!("center point not found"))?; - let mut sketch = isketch.sketch.borrow_mut(); - - let center_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(center_id).unwrap() { - point - } else { - return Err(anyhow::anyhow!("Center point is not a point")); - }; - - let isoarc = PrimitiveCell::Arc(Rc::new(RefCell::new(isotope::primitives::arc::Arc::new( - center_point.clone(), - self.radius, - self.clockwise, - self.start_angle, - self.end_angle, - )))); - let arc = archetypes::Arc2 { - center: self.center, - radius: self.radius, - clockwise: self.clockwise, - start_angle: self.start_angle, - end_angle: self.end_angle, - }; - let arc_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Arc2(arc))); - // TODO: link - - let arc_id = sketch.add_primitive(isoarc)?; - drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch - Ok(Some((arc_id, arc_wrapped.into_result(&isketch)))) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Self::Parent, + ) -> anyhow::Result> { + let isketch = sketch_ref.borrow(); + // let center_id = isketch.primitive_map.get(&self.center).unwrap(); + let center_id = crate::ID_MAP + .with_borrow(|m| m.get(&self.center).cloned()) + .ok_or(anyhow::anyhow!("center point not found"))?; + let mut sketch = isketch.sketch.borrow_mut(); + + let center_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(center_id).unwrap() { + point + } else { + return Err(anyhow::anyhow!("Center point is not a point")); + }; + + let isoarc = PrimitiveCell::Arc(Rc::new(RefCell::new(isotope::primitives::arc::Arc::new( + center_point.clone(), + self.radius, + self.clockwise, + self.start_angle, + self.end_angle, + )))); + let arc = archetypes::Arc2 { + center: self.center, + radius: self.radius, + clockwise: self.clockwise, + start_angle: self.start_angle, + end_angle: self.end_angle, + }; + let arc_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Arc2(arc))); + // TODO: link + + let arc_id = sketch.add_primitive(isoarc)?; + drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch + Ok(Some((arc_id, arc_wrapped.into_result(&isketch)))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddCircle { - pub center: StepHash, - pub radius: f64, + pub center: StepHash, + pub radius: f64, } impl MessageHandler for AddCircle { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Self::Parent, - ) -> anyhow::Result> { - let isketch = sketch_ref.borrow(); - // let center_id = isketch.primitive_map.get(&self.center).unwrap(); - let center_id = crate::ID_MAP - .with_borrow(|m| m.get(&self.center).cloned()) - .ok_or(anyhow::anyhow!("center point not found"))?; - let mut sketch = isketch.sketch.borrow_mut(); - - let center_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(center_id).unwrap() { - point - } else { - return Err(anyhow::anyhow!("Center point is not a point")); - }; - - let iso_circle = PrimitiveCell::Circle(Rc::new(RefCell::new( - isotope::primitives::circle::Circle::new(center_point.clone(), self.radius), - ))); - let circle = archetypes::Circle2 { - center: self.center, - radius: self.radius, - }; - let circle_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Circle2(circle))); - // TODO: link - - let circle_id = sketch.add_primitive(iso_circle)?; - drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch - Ok(Some((circle_id, circle_wrapped.into_result(&isketch)))) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Self::Parent, + ) -> anyhow::Result> { + let isketch = sketch_ref.borrow(); + // let center_id = isketch.primitive_map.get(&self.center).unwrap(); + let center_id = crate::ID_MAP + .with_borrow(|m| m.get(&self.center).cloned()) + .ok_or(anyhow::anyhow!("center point not found"))?; + let mut sketch = isketch.sketch.borrow_mut(); + + let center_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(center_id).unwrap() { + point + } else { + return Err(anyhow::anyhow!("Center point is not a point")); + }; + + let iso_circle = PrimitiveCell::Circle(Rc::new(RefCell::new( + isotope::primitives::circle::Circle::new(center_point.clone(), self.radius), + ))); + let circle = archetypes::Circle2 { + center: self.center, + radius: self.radius, + }; + let circle_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Circle2(circle))); + // TODO: link + + let circle_id = sketch.add_primitive(iso_circle)?; + drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch + Ok(Some((circle_id, circle_wrapped.into_result(&isketch)))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddLine { - pub start: StepHash, - pub end: StepHash, + pub start: StepHash, + pub end: StepHash, } impl MessageHandler for AddLine { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Self::Parent, - ) -> anyhow::Result> { - let isketch = sketch_ref.borrow(); - // let start_id = isketch.primitive_map.get(&self.start).unwrap(); - // let end_id = isketch.primitive_map.get(&self.end).unwrap(); - let start_id = crate::ID_MAP - .with_borrow(|m| m.get(&self.start).cloned()) - .ok_or(anyhow::anyhow!("start point not found"))?; - let end_id = crate::ID_MAP - .with_borrow(|m| m.get(&self.end).cloned()) - .ok_or(anyhow::anyhow!("end point not found"))?; - let mut sketch = isketch.sketch.borrow_mut(); - - let start_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(start_id).unwrap() { - point - } else { - return Err(anyhow::anyhow!("Start point is not a point")); - }; - let end_point = - if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(end_id).unwrap() { - point - } else { - return Err(anyhow::anyhow!("End point is not a point")); - }; - - let iso_line = PrimitiveCell::Line(Rc::new(RefCell::new(Line::new( - start_point.clone(), - end_point.clone(), - )))); - - let line = archetypes::Line2 { - start: self.start, - end: self.end, - }; - let line_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Line2(line))); - // TODO: link - - let line_id = sketch.add_primitive(iso_line)?; - drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch - Ok(Some((line_id, line_wrapped.into_result(&isketch)))) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Self::Parent, + ) -> anyhow::Result> { + let isketch = sketch_ref.borrow(); + // let start_id = isketch.primitive_map.get(&self.start).unwrap(); + // let end_id = isketch.primitive_map.get(&self.end).unwrap(); + let start_id = crate::ID_MAP + .with_borrow(|m| m.get(&self.start).cloned()) + .ok_or(anyhow::anyhow!("start point not found"))?; + let end_id = crate::ID_MAP + .with_borrow(|m| m.get(&self.end).cloned()) + .ok_or(anyhow::anyhow!("end point not found"))?; + let mut sketch = isketch.sketch.borrow_mut(); + + let start_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(start_id).unwrap() { + point + } else { + return Err(anyhow::anyhow!("Start point is not a point")); + }; + let end_point = + if let PrimitiveCell::Point2(point) = sketch.get_primitive_by_id(end_id).unwrap() { + point + } else { + return Err(anyhow::anyhow!("End point is not a point")); + }; + + let iso_line = PrimitiveCell::Line(Rc::new(RefCell::new(Line::new( + start_point.clone(), + end_point.clone(), + )))); + + let line = archetypes::Line2 { + start: self.start, + end: self.end, + }; + let line_wrapped = Rc::new(RefCell::new(archetypes::WrappedPrimitive::Line2(line))); + // TODO: link + + let line_id = sketch.add_primitive(iso_line)?; + drop(sketch); // We need to drop the mutable borrow because into_result borrows the sketch + Ok(Some((line_id, line_wrapped.into_result(&isketch)))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct DeletePrimitive { - id: IDType, + id: IDType, } impl MessageHandler for DeletePrimitive { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Rc>, - ) -> anyhow::Result> { - let isketch = sketch_ref.borrow(); - let mut sketch = isketch.sketch.borrow_mut(); - - sketch.delete_primitive(self.id)?; - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Rc>, + ) -> anyhow::Result> { + let isketch = sketch_ref.borrow(); + let mut sketch = isketch.sketch.borrow_mut(); + + sketch.delete_primitive(self.id)?; + Ok(None) + } } diff --git a/packages/cadmium/src/lib.rs b/packages/cadmium/src/lib.rs index 86a7e077..b606e237 100644 --- a/packages/cadmium/src/lib.rs +++ b/packages/cadmium/src/lib.rs @@ -96,20 +96,20 @@ pub mod workbench; pub type IDType = u64; thread_local! { - /// This is a global map to keep track of hashes against local IDs - /// The hash is the unique identifier for a step - /// The ID could be any kind of ID, e.g. a isotope sketch primitive - /// - ///
- /// - /// Using this map in reverse (from local ID to hash) requires manual check logic - /// (that the resulting hash is a step of the correct type, points to the correct parent, etc.) - /// - ///
- static ID_MAP: RefCell> = RefCell::new(BTreeMap::new()); + /// This is a global map to keep track of hashes against local IDs + /// The hash is the unique identifier for a step + /// The ID could be any kind of ID, e.g. a isotope sketch primitive + /// + ///
+ /// + /// Using this map in reverse (from local ID to hash) requires manual check logic + /// (that the resulting hash is a step of the correct type, points to the correct parent, etc.) + /// + ///
+ static ID_MAP: RefCell> = RefCell::new(BTreeMap::new()); - /// Global project list - this is the preferred way to store & access projects - static PROJECTS: RefCell> = RefCell::new(Vec::new()); + /// Global project list - this is the preferred way to store & access projects + static PROJECTS: RefCell> = RefCell::new(Vec::new()); } /// Creates a new [`Project`](project::Project) and returns the index of the project in the global project list @@ -126,12 +126,12 @@ thread_local! { /// ``` #[wasm_bindgen] pub fn create_project(name: &str) -> usize { - let p = project::Project::new(name); - PROJECTS.with(|projects_ref| { - let mut projects = projects_ref.borrow_mut(); - projects.push(p); - projects.len() - 1 - }) + let p = project::Project::new(name); + PROJECTS.with(|projects_ref| { + let mut projects = projects_ref.borrow_mut(); + projects.push(p); + projects.len() - 1 + }) } /// Returns a concrete [`Project`](project::Project) from the global project list. @@ -139,13 +139,13 @@ pub fn create_project(name: &str) -> usize { /// A new project can be created with [`create_project`] function. #[wasm_bindgen] pub fn get_project(project_index: usize) -> Result { - PROJECTS.with(|projects_ref| { - let projects = projects_ref.borrow(); - Ok(projects - .get(project_index) - .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())? - .clone()) - }) + PROJECTS.with(|projects_ref| { + let projects = projects_ref.borrow(); + Ok(projects + .get(project_index) + .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())? + .clone()) + }) } /// Sends a message to a [`Project`](project::Project) and returns the result @@ -170,98 +170,98 @@ pub fn get_project(project_index: usize) -> Result { /// ``` #[wasm_bindgen] pub fn send_message(project_index: usize, message: &Message) -> MessageResult { - PROJECTS.with(|projects_ref| { - let mut projects = projects_ref.borrow_mut(); - let Some(mut p) = projects.get_mut(project_index as usize) else { - return CADmiumError::ProjectIDNotFound(project_index).into(); - }; + PROJECTS.with(|projects_ref| { + let mut projects = projects_ref.borrow_mut(); + let Some(mut p) = projects.get_mut(project_index as usize) else { + return CADmiumError::ProjectIDNotFound(project_index).into(); + }; - message.handle(&mut p).into() - }) + message.handle(&mut p).into() + }) } /// Returns a concrete [`Workbench`](workbench::Workbench) from a [`Project`](project::Project). #[wasm_bindgen] pub fn get_workbench( - project_index: usize, - workbench_index: usize, + project_index: usize, + workbench_index: usize, ) -> Result { - PROJECTS.with(|projects_ref| { - let projects = projects_ref.borrow(); - let p = projects - .get(project_index) - .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?; - let wb = p - .workbenches - .get(workbench_index) - .ok_or(CADmiumError::WorkbenchIDNotFound(workbench_index as u64).to_string())? - .borrow(); - Ok(wb.clone()) - }) + PROJECTS.with(|projects_ref| { + let projects = projects_ref.borrow(); + let p = projects + .get(project_index) + .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?; + let wb = p + .workbenches + .get(workbench_index) + .ok_or(CADmiumError::WorkbenchIDNotFound(workbench_index as u64).to_string())? + .borrow(); + Ok(wb.clone()) + }) } #[derive(Debug, Clone)] #[wasm_bindgen] pub struct Project { - native: project::Project, + native: project::Project, } #[wasm_bindgen] impl Project { - #[wasm_bindgen(constructor)] - pub fn new(name: &str) -> Project { - console_error_panic_hook::set_once(); - wasm_logger::init(wasm_logger::Config::default()); + #[wasm_bindgen(constructor)] + pub fn new(name: &str) -> Project { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::default()); - Project { - native: project::Project::new(name), - } - } + Project { + native: project::Project::new(name), + } + } - #[wasm_bindgen(getter)] - pub fn name(&self) -> String { - self.native.name.clone() - } + #[wasm_bindgen(getter)] + pub fn name(&self) -> String { + self.native.name.clone() + } - #[wasm_bindgen(setter)] - pub fn set_name(&mut self, name: String) { - self.native.name = name; - } + #[wasm_bindgen(setter)] + pub fn set_name(&mut self, name: String) { + self.native.name = name; + } - #[wasm_bindgen(getter)] - pub fn json(&self) -> String { - self.native.json() - } + #[wasm_bindgen(getter)] + pub fn json(&self) -> String { + self.native.json() + } - #[wasm_bindgen] - pub fn to_json(&self) -> String { - self.native.json() - } + #[wasm_bindgen] + pub fn to_json(&self) -> String { + self.native.json() + } - #[wasm_bindgen] - pub fn from_json(json: String) -> Project { - let p = project::Project::from_json(&json); - Project { native: p } - } + #[wasm_bindgen] + pub fn from_json(json: String) -> Project { + let p = project::Project::from_json(&json); + Project { native: p } + } - #[wasm_bindgen] - pub fn compute_constraint_errors(&mut self) { - // self.native.compute_constraint_errors(); - } + #[wasm_bindgen] + pub fn compute_constraint_errors(&mut self) { + // self.native.compute_constraint_errors(); + } - #[wasm_bindgen] - pub fn get_workbench(&self, workbench_index: u32) -> workbench::Workbench { - // TODO: Use get() and return a Result - self.native - .workbenches - .get(workbench_index as usize) - .unwrap() - .borrow() - .clone() // This single call pollutes Clone derives for all MessageHandlers - } + #[wasm_bindgen] + pub fn get_workbench(&self, workbench_index: u32) -> workbench::Workbench { + // TODO: Use get() and return a Result + self.native + .workbenches + .get(workbench_index as usize) + .unwrap() + .borrow() + .clone() // This single call pollutes Clone derives for all MessageHandlers + } - #[wasm_bindgen] - pub fn send_message(&mut self, message: &Message) -> MessageResult { - message.handle(&mut self.native).into() - } + #[wasm_bindgen] + pub fn send_message(&mut self, message: &Message) -> MessageResult { + message.handle(&mut self.native).into() + } } diff --git a/packages/cadmium/src/main.rs b/packages/cadmium/src/main.rs index 15b4ab14..a1b9d441 100644 --- a/packages/cadmium/src/main.rs +++ b/packages/cadmium/src/main.rs @@ -12,60 +12,60 @@ use truck_shapeops::{and, or, ShapeOpsCurve, ShapeOpsSurface}; use truck_topology::{Shell, Solid}; fn main() { - cadmium::Project::gen_typescript_defs(); + cadmium::Project::gen_typescript_defs(); - let point_a = vertex(Point3::new(0.0, 0.0, 0.0)); - let line_a = tsweep(&point_a, Vector3::new(1.0, 0.0, 0.0)); - let square_a = tsweep(&line_a, Vector3::new(0.0, 1.0, 0.0)); - let cube_a = tsweep(&square_a, Vector3::new(0.0, 0.0, 1.0)); + let point_a = vertex(Point3::new(0.0, 0.0, 0.0)); + let line_a = tsweep(&point_a, Vector3::new(1.0, 0.0, 0.0)); + let square_a = tsweep(&line_a, Vector3::new(0.0, 1.0, 0.0)); + let cube_a = tsweep(&square_a, Vector3::new(0.0, 0.0, 1.0)); - // simplest case! - // let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); - // let line_b = tsweep(&point_b, Vector3::new(0.2, 0.0, 0.0)); - // let square_b = tsweep(&line_b, Vector3::new(0.0, 0.2, 0.0)); - // let cube_b: Solid< - // truck_meshalgo::prelude::cgmath::Point3, - // truck_modeling::Curve, - // truck_modeling::Surface, - // > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); + // simplest case! + // let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); + // let line_b = tsweep(&point_b, Vector3::new(0.2, 0.0, 0.0)); + // let square_b = tsweep(&line_b, Vector3::new(0.0, 0.2, 0.0)); + // let cube_b: Solid< + // truck_meshalgo::prelude::cgmath::Point3, + // truck_modeling::Curve, + // truck_modeling::Surface, + // > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); - // one flush side! - let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); - let line_b = tsweep(&point_b, Vector3::new(0.6, 0.0, 0.0)); - let square_b = tsweep(&line_b, Vector3::new(0.0, 0.2, 0.0)); - let cube_b: Solid< - truck_meshalgo::prelude::cgmath::Point3, - truck_modeling::Curve, - truck_modeling::Surface, - > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); + // one flush side! + let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); + let line_b = tsweep(&point_b, Vector3::new(0.6, 0.0, 0.0)); + let square_b = tsweep(&line_b, Vector3::new(0.0, 0.2, 0.0)); + let cube_b: Solid< + truck_meshalgo::prelude::cgmath::Point3, + truck_modeling::Curve, + truck_modeling::Surface, + > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); - // two flush sides! - // let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); - // let line_b = tsweep(&point_b, Vector3::new(0.6, 0.0, 0.0)); - // let square_b = tsweep(&line_b, Vector3::new(0.0, 0.6, 0.0)); - // let cube_b: Solid< - // truck_meshalgo::prelude::cgmath::Point3, - // truck_modeling::Curve, - // truck_modeling::Surface, - // > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); + // two flush sides! + // let point_b = vertex(Point3::new(0.4, 0.4, 1.0)); + // let line_b = tsweep(&point_b, Vector3::new(0.6, 0.0, 0.0)); + // let square_b = tsweep(&line_b, Vector3::new(0.0, 0.6, 0.0)); + // let cube_b: Solid< + // truck_meshalgo::prelude::cgmath::Point3, + // truck_modeling::Curve, + // truck_modeling::Surface, + // > = tsweep(&square_b, Vector3::new(0.0, 0.0, 0.2)); - // extend the cube to be just 0.01 longer than it needs to be - // let cube_b = tsweep(&square_b, Vector3::new(0.0, 0.0, 1.01)); - // let bad_volume = tsweep(&square_b, Vector3::new(0.0, 0.0, -0.01)); - // then translate it down - // let cube_b = translated(&cube_b, Vector3::new(0.0, 0.0, -0.01)); - // let combined_big = or(&cube_a, &cube_b, 0.01).unwrap(); + // extend the cube to be just 0.01 longer than it needs to be + // let cube_b = tsweep(&square_b, Vector3::new(0.0, 0.0, 1.01)); + // let bad_volume = tsweep(&square_b, Vector3::new(0.0, 0.0, -0.01)); + // then translate it down + // let cube_b = translated(&cube_b, Vector3::new(0.0, 0.0, -0.01)); + // let combined_big = or(&cube_a, &cube_b, 0.01).unwrap(); - // let combined = or(&cube_a, &cube_b, 0.01).unwrap(); - let combined = fuse(&cube_a, &cube_b).unwrap(); + // let combined = or(&cube_a, &cube_b, 0.01).unwrap(); + let combined = fuse(&cube_a, &cube_b).unwrap(); - println!( - "combined_cube_or has {:?} shell boundaries", - combined.boundaries().len() - ); + println!( + "combined_cube_or has {:?} shell boundaries", + combined.boundaries().len() + ); - let mut mesh = combined.triangulation(0.01).to_polygon(); - mesh.put_together_same_attrs(0.1); - let file = std::fs::File::create("combined_cube.obj").unwrap(); - obj::write(&mesh, file).unwrap(); + let mut mesh = combined.triangulation(0.01).to_polygon(); + mesh.put_together_same_attrs(0.1); + let file = std::fs::File::create("combined_cube.obj").unwrap(); + obj::write(&mesh, file).unwrap(); } diff --git a/packages/cadmium/src/message/idwrap/de.rs b/packages/cadmium/src/message/idwrap/de.rs index f8d826cb..406a48f6 100644 --- a/packages/cadmium/src/message/idwrap/de.rs +++ b/packages/cadmium/src/message/idwrap/de.rs @@ -9,75 +9,75 @@ use super::IDWrap; impl<'de, T, C> Deserialize<'de> for IDWrap where - T: MessageHandler - + Clone - + Serialize - + for<'dh> Deserialize<'dh> - + wasm_bindgen::convert::RefFromWasmAbi, - C: Identifiable, + T: MessageHandler + + Clone + + Serialize + + for<'dh> Deserialize<'dh> + + wasm_bindgen::convert::RefFromWasmAbi, + C: Identifiable, { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct IDWrapVisitor { - marker: std::marker::PhantomData T>, - } + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IDWrapVisitor { + marker: std::marker::PhantomData T>, + } - impl IDWrapVisitor { - fn new() -> Self { - IDWrapVisitor { - marker: std::marker::PhantomData, - } - } - } + impl IDWrapVisitor { + fn new() -> Self { + IDWrapVisitor { + marker: std::marker::PhantomData, + } + } + } - // Implementation of Visitor trait for IDWrapVisitor - impl<'de, T, C> Visitor<'de> for IDWrapVisitor> - where - T: MessageHandler - + Clone - + Serialize - + for<'dh> Deserialize<'dh> - + wasm_bindgen::convert::RefFromWasmAbi, - C: Identifiable, - { - type Value = IDWrap; + // Implementation of Visitor trait for IDWrapVisitor + impl<'de, T, C> Visitor<'de> for IDWrapVisitor> + where + T: MessageHandler + + Clone + + Serialize + + for<'dh> Deserialize<'dh> + + wasm_bindgen::convert::RefFromWasmAbi, + C: Identifiable, + { + type Value = IDWrap; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(format!("a map with {}", C::ID_NAME).as_str()) - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(format!("a map with {}", C::ID_NAME).as_str()) + } - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut parent_id = None; - let mut inner_map = serde_json::Map::new(); + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut parent_id = None; + let mut inner_map = serde_json::Map::new(); - // Loop through the map and extract fields - while let Some(key) = map.next_key::()? { - if key == C::ID_NAME { - if parent_id.is_some() { - return Err(de::Error::duplicate_field("parent_id")); - } - parent_id = Some(map.next_value()?); - } else { - // Collect the rest of the map entries for inner deserialization - let value: serde_json::Value = map.next_value()?; - inner_map.insert(key, value); - } - } + // Loop through the map and extract fields + while let Some(key) = map.next_key::()? { + if key == C::ID_NAME { + if parent_id.is_some() { + return Err(de::Error::duplicate_field("parent_id")); + } + parent_id = Some(map.next_value()?); + } else { + // Collect the rest of the map entries for inner deserialization + let value: serde_json::Value = map.next_value()?; + inner_map.insert(key, value); + } + } - let id = parent_id.ok_or_else(|| de::Error::missing_field(C::ID_NAME))?; - let inner_value = serde_json::Value::Object(inner_map); - let inner = T::deserialize(inner_value).map_err(de::Error::custom)?; + let id = parent_id.ok_or_else(|| de::Error::missing_field(C::ID_NAME))?; + let inner_value = serde_json::Value::Object(inner_map); + let inner = T::deserialize(inner_value).map_err(de::Error::custom)?; - // Construct the nested IDWrap structure - Ok(IDWrap { id, inner }) - } - } + // Construct the nested IDWrap structure + Ok(IDWrap { id, inner }) + } + } - deserializer.deserialize_map(IDWrapVisitor::new()) - } + deserializer.deserialize_map(IDWrapVisitor::new()) + } } diff --git a/packages/cadmium/src/message/idwrap/mod.rs b/packages/cadmium/src/message/idwrap/mod.rs index 6a15db50..abed9447 100644 --- a/packages/cadmium/src/message/idwrap/mod.rs +++ b/packages/cadmium/src/message/idwrap/mod.rs @@ -55,75 +55,75 @@ use super::{Identifiable, MessageHandler, ProjectMessageHandler}; #[derive(Tsify, Debug, Clone)] #[tsify(from_wasm_abi)] pub struct IDWrap { - pub id: StepHash, - pub inner: T, + pub id: StepHash, + pub inner: T, } impl IDWrap { - pub fn new(id: StepHash, h: T) -> Self { - Self { id, inner: h } - } + pub fn new(id: StepHash, h: T) -> Self { + Self { id, inner: h } + } - pub fn id(&self) -> StepHash { - self.id - } + pub fn id(&self) -> StepHash { + self.id + } - pub fn inner(&self) -> &T { - &self.inner - } + pub fn inner(&self) -> &T { + &self.inner + } } // First level message handler impl<'a, T> ProjectMessageHandler for IDWrap where - T: MessageHandler>> - + Clone - + Serialize - + DeserializeOwned - + RefFromWasmAbi, - crate::message::message::Message: From, + T: MessageHandler>> + + Clone + + Serialize + + DeserializeOwned + + RefFromWasmAbi, + crate::message::message::Message: From, { - fn handle_project_message( - &self, - project: &mut crate::project::Project, - ) -> anyhow::Result> { - let wb_cell = T::Parent::from_parent_id(project, self.id)?; - let result = self.inner.handle_message(wb_cell.clone())?; - let (id, node) = if let Some((id, node)) = result { - (Some(id), node) - } else { - (None, StepResult::Empty) - }; - - let mut wb = wb_cell.borrow_mut(); - wb.add_message_step(&self.clone().into(), node); - let hash = wb.history.last().map(|step| step.borrow().hash()).clone(); - - if let Some(id) = id { - if let Some(hash) = hash { - crate::ID_MAP.with_borrow_mut(|m| m.insert(hash, id)); - } else { - warn!("IDWrap::handle_project_message: IDWrap returned an ID, but no hash was found in the workbench history"); - } - } - - // Return the step ID (hash) instead of the message handler returned ID - Ok(hash) - } + fn handle_project_message( + &self, + project: &mut crate::project::Project, + ) -> anyhow::Result> { + let wb_cell = T::Parent::from_parent_id(project, self.id)?; + let result = self.inner.handle_message(wb_cell.clone())?; + let (id, node) = if let Some((id, node)) = result { + (Some(id), node) + } else { + (None, StepResult::Empty) + }; + + let mut wb = wb_cell.borrow_mut(); + wb.add_message_step(&self.clone().into(), node); + let hash = wb.history.last().map(|step| step.borrow().hash()).clone(); + + if let Some(id) = id { + if let Some(hash) = hash { + crate::ID_MAP.with_borrow_mut(|m| m.insert(hash, id)); + } else { + warn!("IDWrap::handle_project_message: IDWrap returned an ID, but no hash was found in the workbench history"); + } + } + + // Return the step ID (hash) instead of the message handler returned ID + Ok(hash) + } } // Second level message handler impl MessageHandler for IDWrap where - T: MessageHandler + Clone + Serialize + DeserializeOwned + RefFromWasmAbi, - C: Identifiable, - P: Identifiable, + T: MessageHandler + Clone + Serialize + DeserializeOwned + RefFromWasmAbi, + C: Identifiable, + P: Identifiable, { - type Parent = C::Parent; - fn handle_message(&self, parent: Self::Parent) -> anyhow::Result> { - let msg_parent = C::from_parent_id(&parent, self.id)?; - self.inner.handle_message(msg_parent) - } + type Parent = C::Parent; + fn handle_message(&self, parent: Self::Parent) -> anyhow::Result> { + let msg_parent = C::from_parent_id(&parent, self.id)?; + self.inner.handle_message(msg_parent) + } } /// A helper trait that allows any type that implements [`MessageHandler`] to be wrapped in an [`IDWrap`]. @@ -151,15 +151,15 @@ where /// println!("The new point has the hash: {}", result); /// ``` pub trait IDWrapable { - fn id_wrap(self, id: StepHash) -> IDWrap; + fn id_wrap(self, id: StepHash) -> IDWrap; } impl IDWrapable for T where - T: MessageHandler + Clone + Serialize + DeserializeOwned + RefFromWasmAbi, - C: Identifiable, + T: MessageHandler + Clone + Serialize + DeserializeOwned + RefFromWasmAbi, + C: Identifiable, { - fn id_wrap(self, id: StepHash) -> IDWrap { - IDWrap { id, inner: self } - } + fn id_wrap(self, id: StepHash) -> IDWrap { + IDWrap { id, inner: self } + } } diff --git a/packages/cadmium/src/message/idwrap/ser.rs b/packages/cadmium/src/message/idwrap/ser.rs index 70477d0f..bd5648dd 100644 --- a/packages/cadmium/src/message/idwrap/ser.rs +++ b/packages/cadmium/src/message/idwrap/ser.rs @@ -7,31 +7,31 @@ use super::IDWrap; impl Serialize for IDWrap where - T: MessageHandler - + Clone - + Serialize - + for<'de> Deserialize<'de> - + wasm_bindgen::convert::RefFromWasmAbi, - C: Identifiable, + T: MessageHandler + + Clone + + Serialize + + for<'de> Deserialize<'de> + + wasm_bindgen::convert::RefFromWasmAbi, + C: Identifiable, { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - // Create a serializer map with the appropriate capacity - let mut map = serializer.serialize_map(Some(1))?; - // Add the id field using the name from the Identifiable trait - map.serialize_entry(C::ID_NAME, &self.id)?; - // Add the inner object fields - serde_json::to_value(&self.inner) - .map_err(serde::ser::Error::custom)? - .as_object() - .ok_or_else(|| serde::ser::Error::custom("Expected object"))? - .iter() - .try_for_each(|(k, v)| { - map.serialize_entry(k, v)?; - Ok(()) - })?; - map.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Create a serializer map with the appropriate capacity + let mut map = serializer.serialize_map(Some(1))?; + // Add the id field using the name from the Identifiable trait + map.serialize_entry(C::ID_NAME, &self.id)?; + // Add the inner object fields + serde_json::to_value(&self.inner) + .map_err(serde::ser::Error::custom)? + .as_object() + .ok_or_else(|| serde::ser::Error::custom("Expected object"))? + .iter() + .try_for_each(|(k, v)| { + map.serialize_entry(k, v)?; + Ok(()) + })?; + map.end() + } } diff --git a/packages/cadmium/src/message/message.rs b/packages/cadmium/src/message/message.rs index de3fdab1..24f4affe 100644 --- a/packages/cadmium/src/message/message.rs +++ b/packages/cadmium/src/message/message.rs @@ -16,64 +16,64 @@ use super::ProjectMessageHandler; #[tsify(from_wasm_abi, into_wasm_abi)] #[serde(tag = "type")] pub enum Message { - ProjectRename(crate::project::ProjectRename), - WorkbenchRename(IDWrap), - WorkbenchPointAdd(IDWrap), - WorkbenchPlaneAdd(IDWrap), - WorkbenchSketchAdd(IDWrap), - WorkbenchSketchSetPlane(IDWrap), - WorkbenchPointUpdate(IDWrap>), + ProjectRename(crate::project::ProjectRename), + WorkbenchRename(IDWrap), + WorkbenchPointAdd(IDWrap), + WorkbenchPlaneAdd(IDWrap), + WorkbenchSketchAdd(IDWrap), + WorkbenchSketchSetPlane(IDWrap), + WorkbenchPointUpdate(IDWrap>), - SketchAddPoint(IDWrap>), - SketchAddArc(IDWrap>), - SketchAddCircle(IDWrap>), - SketchAddLine(IDWrap>), - SketchAddRectangle(IDWrap>), - SketchDeletePrimitive(IDWrap>), + SketchAddPoint(IDWrap>), + SketchAddArc(IDWrap>), + SketchAddCircle(IDWrap>), + SketchAddLine(IDWrap>), + SketchAddRectangle(IDWrap>), + SketchDeletePrimitive(IDWrap>), - FeatureExtrusionAdd(IDWrap), + FeatureExtrusionAdd(IDWrap), - StepRename(IDWrap>), - StepDelete(IDWrap), + StepRename(IDWrap>), + StepDelete(IDWrap), } /// The result of a message handling operation. #[derive(Tsify, Debug, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct MessageResult { - // TODO: Add more data fields and not a blanket string - /// Whether the operation was successful or not. - pub success: bool, - /// The data returned by the operation. - /// - /// Could be "null", contain an error message (in case of `success == false`) or valid JSON data. - pub data: String, + // TODO: Add more data fields and not a blanket string + /// Whether the operation was successful or not. + pub success: bool, + /// The data returned by the operation. + /// + /// Could be "null", contain an error message (in case of `success == false`) or valid JSON data. + pub data: String, } impl From>> for MessageResult { - fn from(result: anyhow::Result>) -> Self { - match result { - Ok(msg) => Self { - success: true, - data: if let Some(id) = msg { - id.to_string() - } else { - "null".to_string() - }, - }, - Err(e) => Self { - success: false, - data: e.to_string() + "\n\n" + e.backtrace().to_string().as_str(), - }, - } - } + fn from(result: anyhow::Result>) -> Self { + match result { + Ok(msg) => Self { + success: true, + data: if let Some(id) = msg { + id.to_string() + } else { + "null".to_string() + }, + }, + Err(e) => Self { + success: false, + data: e.to_string() + "\n\n" + e.backtrace().to_string().as_str(), + }, + } + } } impl From for MessageResult { - fn from(e: crate::error::CADmiumError) -> Self { - Self { - success: false, - data: e.to_string(), - } - } + fn from(e: crate::error::CADmiumError) -> Self { + Self { + success: false, + data: e.to_string(), + } + } } diff --git a/packages/cadmium/src/message/mod.rs b/packages/cadmium/src/message/mod.rs index edd1b86c..7ec5cd21 100644 --- a/packages/cadmium/src/message/mod.rs +++ b/packages/cadmium/src/message/mod.rs @@ -14,9 +14,9 @@ use wasm_bindgen::convert::RefFromWasmAbi; /// [`Workbench`](crate::workbench::Workbench) as a parent and is identified by /// the hash of an [`AddSketch`](crate::workbench::AddSketch) message. pub trait Identifiable: Sized { - type Parent; - const ID_NAME: &'static str; - fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> Result; + type Parent; + const ID_NAME: &'static str; + fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> Result; } /// A trait for types that can handle messages without the identity-dereference mechanism. @@ -24,10 +24,10 @@ pub trait Identifiable: Sized { /// For example a [`ProjectRename`](crate::project::ProjectRename) message can be handled /// by the [`Project`](crate::project::Project) without needing to dereference the parent. pub trait ProjectMessageHandler: RefFromWasmAbi { - fn handle_project_message( - &self, - project: &mut crate::project::Project, - ) -> anyhow::Result>; + fn handle_project_message( + &self, + project: &mut crate::project::Project, + ) -> anyhow::Result>; } /// A trait for types that can handle messages and need a parent instance to do so. @@ -35,6 +35,6 @@ pub trait ProjectMessageHandler: RefFromWasmAbi { /// For example an [`AddPoint`](crate::workbench::AddPoint) message needs a /// [`Workbench`](crate::workbench::Workbench) to add the point to. pub trait MessageHandler: Serialize + for<'de> Deserialize<'de> + RefFromWasmAbi { - type Parent: Identifiable; - fn handle_message(&self, item: Self::Parent) -> anyhow::Result>; + type Parent: Identifiable; + fn handle_message(&self, item: Self::Parent) -> anyhow::Result>; } diff --git a/packages/cadmium/src/project.rs b/packages/cadmium/src/project.rs index c9bf7eec..cb31ef91 100644 --- a/packages/cadmium/src/project.rs +++ b/packages/cadmium/src/project.rs @@ -16,7 +16,7 @@ use crate::workbench::{AddPlane, AddPoint, Workbench}; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Assembly { - name: String, + name: String, } /// The main data structure of CADmium. A project contains a list of [`Assembly`]s and [`Workbench`]es. @@ -27,458 +27,458 @@ pub struct Assembly { #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Project { - /// The project name - mainly used for display purposes. - pub name: String, - pub assemblies: Vec, - /// The list of workbenches in the project. - pub workbenches: Vec>>, + /// The project name - mainly used for display purposes. + pub name: String, + pub assemblies: Vec, + /// The list of workbenches in the project. + pub workbenches: Vec>>, } impl Project { - /// Creates a new project with a given name. - /// - /// A new [`Workbench`] is added to the project with the name "Workbench 1". - /// Check the [`add_workbench`](Project::add_workbench) method for more details on the new workbench. - pub fn new(name: &str) -> Self { - let mut p = Project { - name: name.to_owned(), - assemblies: vec![], - workbenches: vec![], - }; - - p.add_workbench("Workbench 1"); - - p - } - - /// Adds a new [`Workbench`] to the project. - /// - /// The workbench is created with the given name, - /// an origin point (history index 0) and three planes: top, front, and right - /// (history index 1-3). - pub fn add_workbench(&mut self, name: &str) { - let wb = Workbench::new(name); - let wb_cell = Rc::new(RefCell::new(wb)); - self.workbenches.push(wb_cell.clone()); - let wb_id = self.workbenches.len() as u64 - 1; - let wb_hash = StepHash::from_int(wb_id); - - IDWrap { - id: wb_hash.clone(), - inner: AddPoint { - x: 0.0, - y: 0.0, - z: 0.0, - }, - } - .handle_project_message(self) - .unwrap() - .unwrap(); - - for plane_inst in [Plane::top(), Plane::front(), Plane::right()].iter() { - IDWrap { - id: wb_hash.clone(), - inner: AddPlane { - plane: plane_inst.clone(), - width: 100.0, - height: 100.0, - }, - } - .handle_project_message(self) - .unwrap() - .unwrap(); - } - - let wb_ref = wb_cell.borrow(); - wb_ref.history.get(0).unwrap().borrow_mut().name = "Origin".to_string(); - wb_ref.history.get(1).unwrap().borrow_mut().name = "Top Plane".to_string(); - wb_ref.history.get(2).unwrap().borrow_mut().name = "Front Plane".to_string(); - wb_ref.history.get(3).unwrap().borrow_mut().name = "Right Plane".to_string(); - assert_eq!(wb_ref.history.len(), 4); - assert_eq!(wb_ref.points.len(), 1); - assert_eq!(wb_ref.planes.len(), 3); - assert_eq!(wb_ref.points_next_id, 1); - assert_eq!(wb_ref.planes_next_id, 3); - } - - pub fn json(&self) -> String { - let result = serde_json::to_string(self); - match result { - Ok(json) => json, - Err(e) => format!("Error: {}", e), - } - } - - pub fn from_json(json: &str) -> Self { - let result = serde_json::from_str(json); - match result { - Ok(p) => p, - Err(e) => { - error!("Error: {}", e); - Project::new("Error") - } - } - } - - pub fn get_workbench_by_id(&self, id: u64) -> Result>, CADmiumError> { - self.workbenches - .get(id as usize) - .cloned() - .ok_or(CADmiumError::WorkbenchIDNotFound(id)) - } + /// Creates a new project with a given name. + /// + /// A new [`Workbench`] is added to the project with the name "Workbench 1". + /// Check the [`add_workbench`](Project::add_workbench) method for more details on the new workbench. + pub fn new(name: &str) -> Self { + let mut p = Project { + name: name.to_owned(), + assemblies: vec![], + workbenches: vec![], + }; + + p.add_workbench("Workbench 1"); + + p + } + + /// Adds a new [`Workbench`] to the project. + /// + /// The workbench is created with the given name, + /// an origin point (history index 0) and three planes: top, front, and right + /// (history index 1-3). + pub fn add_workbench(&mut self, name: &str) { + let wb = Workbench::new(name); + let wb_cell = Rc::new(RefCell::new(wb)); + self.workbenches.push(wb_cell.clone()); + let wb_id = self.workbenches.len() as u64 - 1; + let wb_hash = StepHash::from_int(wb_id); + + IDWrap { + id: wb_hash.clone(), + inner: AddPoint { + x: 0.0, + y: 0.0, + z: 0.0, + }, + } + .handle_project_message(self) + .unwrap() + .unwrap(); + + for plane_inst in [Plane::top(), Plane::front(), Plane::right()].iter() { + IDWrap { + id: wb_hash.clone(), + inner: AddPlane { + plane: plane_inst.clone(), + width: 100.0, + height: 100.0, + }, + } + .handle_project_message(self) + .unwrap() + .unwrap(); + } + + let wb_ref = wb_cell.borrow(); + wb_ref.history.get(0).unwrap().borrow_mut().name = "Origin".to_string(); + wb_ref.history.get(1).unwrap().borrow_mut().name = "Top Plane".to_string(); + wb_ref.history.get(2).unwrap().borrow_mut().name = "Front Plane".to_string(); + wb_ref.history.get(3).unwrap().borrow_mut().name = "Right Plane".to_string(); + assert_eq!(wb_ref.history.len(), 4); + assert_eq!(wb_ref.points.len(), 1); + assert_eq!(wb_ref.planes.len(), 3); + assert_eq!(wb_ref.points_next_id, 1); + assert_eq!(wb_ref.planes_next_id, 3); + } + + pub fn json(&self) -> String { + let result = serde_json::to_string(self); + match result { + Ok(json) => json, + Err(e) => format!("Error: {}", e), + } + } + + pub fn from_json(json: &str) -> Self { + let result = serde_json::from_str(json); + match result { + Ok(p) => p, + Err(e) => { + error!("Error: {}", e); + Project::new("Error") + } + } + } + + pub fn get_workbench_by_id(&self, id: u64) -> Result>, CADmiumError> { + self.workbenches + .get(id as usize) + .cloned() + .ok_or(CADmiumError::WorkbenchIDNotFound(id)) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct ProjectRename { - pub new_name: String, + pub new_name: String, } impl ProjectMessageHandler for ProjectRename { - fn handle_project_message( - &self, - project: &mut crate::project::Project, - ) -> anyhow::Result> { - project.name = self.new_name.clone(); - Ok(None) - } + fn handle_project_message( + &self, + project: &mut crate::project::Project, + ) -> anyhow::Result> { + project.name = self.new_name.clone(); + Ok(None) + } } #[cfg(test)] pub mod tests { - use crate::archetypes::PlaneDescription; - - use crate::feature::extrusion; - use crate::feature::extrusion::{Direction, Mode}; - use crate::isketch::primitive::{AddLine, SketchAddPointMessage}; - use crate::message::idwrap::IDWrap; - use crate::message::MessageHandler; - use crate::step; - use crate::workbench::{AddSketch, SetSketchPlane}; - - use super::*; - - pub const WORKBENCH_HASH: StepHash = StepHash::from_int(0); - - pub fn create_test_project() -> Project { - let mut p = Project::new("Test Project"); - let top_hash = p - .workbenches - .get(0) - .unwrap() - .borrow() - .history - .get(1) - .unwrap() - .borrow() - .hash(); - let plane_description = PlaneDescription::PlaneId(top_hash); - let sketch_id = IDWrap { - id: WORKBENCH_HASH, - inner: AddSketch { plane_description }, - } - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - - add_test_rectangle(&mut p, sketch_id, 0.0, 0.0, 40.0, 40.0); - - IDWrap { - id: WORKBENCH_HASH, - inner: extrusion::Add { - sketch_id, - faces: vec![0], - length: 25.0, - offset: 0.0, - direction: Direction::Normal, - mode: Mode::New, - }, - } - .handle_project_message(&mut p) - .unwrap() - .unwrap(); - - p - } - - pub fn add_test_rectangle( - p: &mut Project, - sketch_id: StepHash, - x_start: f64, - y_start: f64, - x_end: f64, - y_end: f64, - ) { - let ll = IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: SketchAddPointMessage { - x: x_start, - y: y_start, - }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - let lr = IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: SketchAddPointMessage { - x: x_end, - y: y_start, - }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - let ul = IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: SketchAddPointMessage { - x: x_start, - y: y_end, - }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - let ur = IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: SketchAddPointMessage { x: x_end, y: y_end }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - - IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: AddLine { start: ll, end: lr }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: AddLine { start: lr, end: ur }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: AddLine { start: ur, end: ul }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - IDWrap { - id: WORKBENCH_HASH, - inner: IDWrap { - id: sketch_id, - inner: AddLine { start: ul, end: ll }, - }, - } - .handle_project_message(p) - .unwrap() - .unwrap(); - } - - #[test] - fn one_extrusion() { - let p = create_test_project(); - - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let workbench = workbench_ref.borrow(); - let solids = &workbench.features; - println!("solids: {:?}", solids); - - assert_eq!(solids.len(), 1); - } - - #[test] - fn move_sketch() { - let p = create_test_project(); - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let front_hash = p - .workbenches - .get(0) - .unwrap() - .borrow() - .history - .get(2) - .unwrap() - .borrow() - .hash(); - - SetSketchPlane { - sketch_id: 0, - plane_description: PlaneDescription::PlaneId(front_hash), - } - .handle_message(workbench_ref.clone()) - .unwrap(); - } - - #[test] - fn rename_step() { - let p = create_test_project(); - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let workbench = workbench_ref.borrow(); - let new_name = "New Extrusion Name".to_string(); - let target = workbench.history.last().unwrap(); - - step::actions::Rename { - new_name: new_name.clone(), - } - .handle_message(target.clone()) - .unwrap(); - - assert_eq!(target.borrow().name, new_name); - } - - #[test] - #[ignore = "uses old filetype"] - fn circle_crashing() { - let file_contents = - std::fs::read_to_string("src/test_inputs/circle_crashing_2.cadmium").unwrap(); - - let p = Project::from_json(&file_contents); - - let workbench_ref = p.get_workbench_by_id(0).unwrap(); - let workbench = workbench_ref.borrow(); - println!("{:?}", workbench); - } - - // #[test] - // fn bruno() { - // let mut p = create_test_project(); - // let wb = p.workbenches.get_mut(0).unwrap(); - - // let s2_id = wb.add_sketch_to_solid_face("Sketch-2", "Ext1:0", Vector3::new(0.0, 0.0, 1.0)); - // let s2 = wb.get_sketch_mut("Sketch-2").unwrap(); - - // // smaller - // let ll = s2.add_point(12.0, 12.0); - // let lr = s2.add_point(32.0, 12.0); - // let ul = s2.add_point(12.0, 32.0); - // let ur = s2.add_point(32.0, 32.0); - // // bigger! - // // let ll = s2.add_point(-10.0, -10.0); - // // let lr = s2.add_point(50.0, -10.0); - // // let ul = s2.add_point(-10.0, 50.0); - // // let ur = s2.add_point(50.0, 50.0); - // s2.add_segment(ll, lr); - // s2.add_segment(lr, ur); - // s2.add_segment(ur, ul); - // s2.add_segment(ul, ll); - - // // println!("S2: {:?}", s2); - - // let extrusion2 = Extrusion::new( - // s2_id.to_owned(), - // vec![0], - // 25.0, - // 0.0, - // Direction::Normal, - // ExtrusionMode::Add(vec!["Ext1:0".to_string()]), - // ); - // wb.add_extrusion("Ext2", extrusion2); - - // wb.add_sketch_to_plane("Sketch 3", "Plane-1"); - // let s3 = wb.get_sketch_mut("Sketch 3").unwrap(); - // let center = s3.add_point(20.0, 15.0); - // s3.add_circle(center, 5.0); - - // let extrusion3 = Extrusion::new( - // "Sketch-2".to_owned(), - // vec![0], - // 50.0, - // 0.0, - // Direction::NegativeNormal, - // ExtrusionMode::Remove(vec!["Ext1:0".to_string()]), - // ); - // wb.add_extrusion("Ext3", extrusion3); - - // let realization = p.get_realization(0, 1000); - // let solids = realization.solids; - - // let num_solids = solids.len(); - // println!("Num Solids: {:?}", num_solids); - // assert!(num_solids == 1); - - // let final_solid = &solids["Ext1:0"]; - // println!("Final solid: {:?}", final_solid.truck_solid); - // let mut mesh = final_solid.truck_solid.triangulation(0.02).to_polygon(); - // mesh.put_together_same_attrs(); - // let file = std::fs::File::create("pkg/bruno.obj").unwrap(); - // obj::write(&mesh, file).unwrap(); - - // let file = std::fs::File::create("pkg/bruno.json").unwrap(); - // serde_json::to_writer(file, &p).unwrap(); - // } - - // #[test] - // fn secondary_extrusion_with_merge() { - // let mut p = create_test_project(); - // let wb = p.workbenches.get_mut(0).unwrap(); - - // let s2_id = wb.add_sketch_to_solid_face("Sketch-2", "Ext1:0", Vector3::new(0.0, 0.0, 1.0)); - // let s2 = wb.get_sketch_mut("Sketch-2").unwrap(); - - // // smaller - // let ll = s2.add_point(12.0, 0.0); - // let lr = s2.add_point(32.0, 0.0); - // let ul = s2.add_point(12.0, 32.0); - // let ur = s2.add_point(32.0, 32.0); - // s2.add_segment(ll, lr); - // s2.add_segment(lr, ur); - // s2.add_segment(ur, ul); - // s2.add_segment(ul, ll); - - // // println!("S2: {:?}", s2); - - // let extrusion2 = Extrusion::new( - // s2_id.to_owned(), - // vec![0], - // 25.0, - // 0.0, - // Direction::Normal, - // ExtrusionMode::Add(vec!["Ext1:0".to_string()]), - // ); - // wb.add_extrusion("Ext2", extrusion2); - - // let realization = p.get_realization(0, 1000); - // let solids = realization.solids; - - // let num_solids = solids.len(); - // println!("Num Solids: {:?}", num_solids); - // assert!(num_solids == 1); - - // let final_solid = &solids["Ext1:0"]; - // let mut mesh = final_solid.truck_solid.triangulation(0.02).to_polygon(); - // mesh.put_together_same_attrs(); - // let file = std::fs::File::create("secondary_extrusion.obj").unwrap(); - // obj::write(&mesh, file).unwrap(); - - // let file = std::fs::File::create("secondary_extrusion.json").unwrap(); - // serde_json::to_writer(file, &p).unwrap(); - // } + use crate::archetypes::PlaneDescription; + + use crate::feature::extrusion; + use crate::feature::extrusion::{Direction, Mode}; + use crate::isketch::primitive::{AddLine, SketchAddPointMessage}; + use crate::message::idwrap::IDWrap; + use crate::message::MessageHandler; + use crate::step; + use crate::workbench::{AddSketch, SetSketchPlane}; + + use super::*; + + pub const WORKBENCH_HASH: StepHash = StepHash::from_int(0); + + pub fn create_test_project() -> Project { + let mut p = Project::new("Test Project"); + let top_hash = p + .workbenches + .get(0) + .unwrap() + .borrow() + .history + .get(1) + .unwrap() + .borrow() + .hash(); + let plane_description = PlaneDescription::PlaneId(top_hash); + let sketch_id = IDWrap { + id: WORKBENCH_HASH, + inner: AddSketch { plane_description }, + } + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + + add_test_rectangle(&mut p, sketch_id, 0.0, 0.0, 40.0, 40.0); + + IDWrap { + id: WORKBENCH_HASH, + inner: extrusion::Add { + sketch_id, + faces: vec![0], + length: 25.0, + offset: 0.0, + direction: Direction::Normal, + mode: Mode::New, + }, + } + .handle_project_message(&mut p) + .unwrap() + .unwrap(); + + p + } + + pub fn add_test_rectangle( + p: &mut Project, + sketch_id: StepHash, + x_start: f64, + y_start: f64, + x_end: f64, + y_end: f64, + ) { + let ll = IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: SketchAddPointMessage { + x: x_start, + y: y_start, + }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + let lr = IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: SketchAddPointMessage { + x: x_end, + y: y_start, + }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + let ul = IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: SketchAddPointMessage { + x: x_start, + y: y_end, + }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + let ur = IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: SketchAddPointMessage { x: x_end, y: y_end }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + + IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: AddLine { start: ll, end: lr }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: AddLine { start: lr, end: ur }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: AddLine { start: ur, end: ul }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + IDWrap { + id: WORKBENCH_HASH, + inner: IDWrap { + id: sketch_id, + inner: AddLine { start: ul, end: ll }, + }, + } + .handle_project_message(p) + .unwrap() + .unwrap(); + } + + #[test] + fn one_extrusion() { + let p = create_test_project(); + + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let workbench = workbench_ref.borrow(); + let solids = &workbench.features; + println!("solids: {:?}", solids); + + assert_eq!(solids.len(), 1); + } + + #[test] + fn move_sketch() { + let p = create_test_project(); + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let front_hash = p + .workbenches + .get(0) + .unwrap() + .borrow() + .history + .get(2) + .unwrap() + .borrow() + .hash(); + + SetSketchPlane { + sketch_id: 0, + plane_description: PlaneDescription::PlaneId(front_hash), + } + .handle_message(workbench_ref.clone()) + .unwrap(); + } + + #[test] + fn rename_step() { + let p = create_test_project(); + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let workbench = workbench_ref.borrow(); + let new_name = "New Extrusion Name".to_string(); + let target = workbench.history.last().unwrap(); + + step::actions::Rename { + new_name: new_name.clone(), + } + .handle_message(target.clone()) + .unwrap(); + + assert_eq!(target.borrow().name, new_name); + } + + #[test] + #[ignore = "uses old filetype"] + fn circle_crashing() { + let file_contents = + std::fs::read_to_string("src/test_inputs/circle_crashing_2.cadmium").unwrap(); + + let p = Project::from_json(&file_contents); + + let workbench_ref = p.get_workbench_by_id(0).unwrap(); + let workbench = workbench_ref.borrow(); + println!("{:?}", workbench); + } + + // #[test] + // fn bruno() { + // let mut p = create_test_project(); + // let wb = p.workbenches.get_mut(0).unwrap(); + + // let s2_id = wb.add_sketch_to_solid_face("Sketch-2", "Ext1:0", Vector3::new(0.0, 0.0, 1.0)); + // let s2 = wb.get_sketch_mut("Sketch-2").unwrap(); + + // // smaller + // let ll = s2.add_point(12.0, 12.0); + // let lr = s2.add_point(32.0, 12.0); + // let ul = s2.add_point(12.0, 32.0); + // let ur = s2.add_point(32.0, 32.0); + // // bigger! + // // let ll = s2.add_point(-10.0, -10.0); + // // let lr = s2.add_point(50.0, -10.0); + // // let ul = s2.add_point(-10.0, 50.0); + // // let ur = s2.add_point(50.0, 50.0); + // s2.add_segment(ll, lr); + // s2.add_segment(lr, ur); + // s2.add_segment(ur, ul); + // s2.add_segment(ul, ll); + + // // println!("S2: {:?}", s2); + + // let extrusion2 = Extrusion::new( + // s2_id.to_owned(), + // vec![0], + // 25.0, + // 0.0, + // Direction::Normal, + // ExtrusionMode::Add(vec!["Ext1:0".to_string()]), + // ); + // wb.add_extrusion("Ext2", extrusion2); + + // wb.add_sketch_to_plane("Sketch 3", "Plane-1"); + // let s3 = wb.get_sketch_mut("Sketch 3").unwrap(); + // let center = s3.add_point(20.0, 15.0); + // s3.add_circle(center, 5.0); + + // let extrusion3 = Extrusion::new( + // "Sketch-2".to_owned(), + // vec![0], + // 50.0, + // 0.0, + // Direction::NegativeNormal, + // ExtrusionMode::Remove(vec!["Ext1:0".to_string()]), + // ); + // wb.add_extrusion("Ext3", extrusion3); + + // let realization = p.get_realization(0, 1000); + // let solids = realization.solids; + + // let num_solids = solids.len(); + // println!("Num Solids: {:?}", num_solids); + // assert!(num_solids == 1); + + // let final_solid = &solids["Ext1:0"]; + // println!("Final solid: {:?}", final_solid.truck_solid); + // let mut mesh = final_solid.truck_solid.triangulation(0.02).to_polygon(); + // mesh.put_together_same_attrs(); + // let file = std::fs::File::create("pkg/bruno.obj").unwrap(); + // obj::write(&mesh, file).unwrap(); + + // let file = std::fs::File::create("pkg/bruno.json").unwrap(); + // serde_json::to_writer(file, &p).unwrap(); + // } + + // #[test] + // fn secondary_extrusion_with_merge() { + // let mut p = create_test_project(); + // let wb = p.workbenches.get_mut(0).unwrap(); + + // let s2_id = wb.add_sketch_to_solid_face("Sketch-2", "Ext1:0", Vector3::new(0.0, 0.0, 1.0)); + // let s2 = wb.get_sketch_mut("Sketch-2").unwrap(); + + // // smaller + // let ll = s2.add_point(12.0, 0.0); + // let lr = s2.add_point(32.0, 0.0); + // let ul = s2.add_point(12.0, 32.0); + // let ur = s2.add_point(32.0, 32.0); + // s2.add_segment(ll, lr); + // s2.add_segment(lr, ur); + // s2.add_segment(ur, ul); + // s2.add_segment(ul, ll); + + // // println!("S2: {:?}", s2); + + // let extrusion2 = Extrusion::new( + // s2_id.to_owned(), + // vec![0], + // 25.0, + // 0.0, + // Direction::Normal, + // ExtrusionMode::Add(vec!["Ext1:0".to_string()]), + // ); + // wb.add_extrusion("Ext2", extrusion2); + + // let realization = p.get_realization(0, 1000); + // let solids = realization.solids; + + // let num_solids = solids.len(); + // println!("Num Solids: {:?}", num_solids); + // assert!(num_solids == 1); + + // let final_solid = &solids["Ext1:0"]; + // let mut mesh = final_solid.truck_solid.triangulation(0.02).to_polygon(); + // mesh.put_together_same_attrs(); + // let file = std::fs::File::create("secondary_extrusion.obj").unwrap(); + // obj::write(&mesh, file).unwrap(); + + // let file = std::fs::File::create("secondary_extrusion.json").unwrap(); + // serde_json::to_writer(file, &p).unwrap(); + // } } diff --git a/packages/cadmium/src/step/actions.rs b/packages/cadmium/src/step/actions.rs index d4966e5e..e636c977 100644 --- a/packages/cadmium/src/step/actions.rs +++ b/packages/cadmium/src/step/actions.rs @@ -13,35 +13,35 @@ use super::{Step, StepResult}; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct Rename { - pub new_name: String, + pub new_name: String, } impl MessageHandler for Rename { - type Parent = Rc>; - fn handle_message( - &self, - step_ref: Self::Parent, - ) -> anyhow::Result> { - let mut step = step_ref.borrow_mut(); - step.name = self.new_name.clone(); - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + step_ref: Self::Parent, + ) -> anyhow::Result> { + let mut step = step_ref.borrow_mut(); + step.name = self.new_name.clone(); + Ok(None) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct Delete { - pub step_id: IDType, + pub step_id: IDType, } impl MessageHandler for Delete { - type Parent = Rc>; - fn handle_message( - &self, - workbench_ref: Self::Parent, - ) -> anyhow::Result> { - let mut workbench = workbench_ref.borrow_mut(); - workbench.history.remove(self.step_id as usize); - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + workbench_ref: Self::Parent, + ) -> anyhow::Result> { + let mut workbench = workbench_ref.borrow_mut(); + workbench.history.remove(self.step_id as usize); + Ok(None) + } } diff --git a/packages/cadmium/src/step/evtree.rs b/packages/cadmium/src/step/evtree.rs new file mode 100644 index 00000000..351a534a --- /dev/null +++ b/packages/cadmium/src/step/evtree.rs @@ -0,0 +1,26 @@ +pub mod loro_serde { + //! Serialization and deserialization implementation for `LoroDoc`. + //! + //! To be used with `#[serde(with = "loro_serde")]` attribute. + + use loro::LoroDoc; + use serde::Deserialize; + + pub fn serialize( + value: &LoroDoc, + serializer: S, + ) -> Result { + serializer.serialize_bytes(&value.export_snapshot()) + } + + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result { + let bytes = Vec::::deserialize(deserializer)?; + let doc = LoroDoc::new(); + + doc.import(&bytes).map_err(serde::de::Error::custom)?; + + Ok(doc) + } +} diff --git a/packages/cadmium/src/step/hash.rs b/packages/cadmium/src/step/hash.rs index 26b05645..740ad8e4 100644 --- a/packages/cadmium/src/step/hash.rs +++ b/packages/cadmium/src/step/hash.rs @@ -19,40 +19,40 @@ use xxhash_rust::xxh3::xxh3_64; pub struct StepHash(#[tsify(type = "string")] u64); impl StepHash { - pub fn into_int(&self) -> u64 { - self.0 - } + pub fn into_int(&self) -> u64 { + self.0 + } - pub const fn from_int(val: u64) -> Self { - Self(val) - } + pub const fn from_int(val: u64) -> Self { + Self(val) + } } impl From<&Message> for StepHash { - fn from(msg: &Message) -> Self { - // Maybe encode to binary instead of json? - let hash = xxh3_64(serde_json::to_string(msg).unwrap().as_bytes()); - Self(hash) - } + fn from(msg: &Message) -> Self { + // Maybe encode to binary instead of json? + let hash = xxh3_64(serde_json::to_string(msg).unwrap().as_bytes()); + Self(hash) + } } impl Display for StepHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } } // Serialize as string impl Serialize for StepHash { - fn serialize(&self, serializer: S) -> Result { - self.0.to_string().serialize(serializer) - } + fn serialize(&self, serializer: S) -> Result { + self.0.to_string().serialize(serializer) + } } // Deserialize from string impl<'de> Deserialize<'de> for StepHash { - fn deserialize>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - Ok(Self(s.parse().unwrap())) - } + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + Ok(Self(s.parse().unwrap())) + } } diff --git a/packages/cadmium/src/step/mod.rs b/packages/cadmium/src/step/mod.rs index b00e7915..de276162 100644 --- a/packages/cadmium/src/step/mod.rs +++ b/packages/cadmium/src/step/mod.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::fmt::Display; use std::rc::Rc; +use std::time::SystemTime; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; @@ -9,6 +10,7 @@ use crate::message::{Identifiable, Message}; use crate::workbench::Workbench; pub mod actions; +pub mod evtree; mod hash; mod result; pub mod sketch_action; @@ -28,48 +30,68 @@ pub use result::StepResult; #[derive(Tsify, Clone, Debug, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct Step { - pub hash: StepHash, - pub name: String, - pub suppressed: bool, - pub data: Message, - pub result: StepResult, + hash: StepHash, + pub name: String, + suppressed: bool, + data: Message, + result: StepResult, + timestamp: SystemTime, + author: String, } impl Step { - pub fn new(data: Message, result: StepResult) -> Self { - let hash = (&data).into(); - Self { - hash: hash, - name: format!("{}-{}", data, hash), - suppressed: false, - data, - result: result, - } - } + pub fn new(data: Message, result: StepResult) -> Self { + let hash = (&data).into(); + Self { + hash, + name: format!("{}-{}", data, hash), + suppressed: false, + data, + result, + timestamp: SystemTime::now(), + author: "Anonymous".to_string(), + } + } - pub fn hash(&self) -> StepHash { - self.hash - } + pub fn hash(&self) -> StepHash { + self.hash + } + + pub fn result(&self) -> &StepResult { + &self.result + } + + pub fn suppress(&mut self) { + self.suppressed = true; + } + + pub fn unsuppress(&mut self) { + self.suppressed = false; + } + + pub fn suppressed(&self) -> bool { + self.suppressed + } } impl Display for Step { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}-{}", self.data, self.hash) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}-{}", self.data, self.hash) + } } impl Identifiable for Rc> { - type Parent = Rc>; - const ID_NAME: &'static str = "step_id"; + type Parent = Rc>; + const ID_NAME: &'static str = "step_id"; - fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { - Ok(parent - .borrow() - .get_step_by_hash(hash) - .ok_or(anyhow::anyhow!( - "No step with hash {} exists in the current workbench", - hash - ))? - .clone()) - } + fn from_parent_id(parent: &Self::Parent, hash: StepHash) -> anyhow::Result { + Ok(parent + .borrow() + .get_step_by_hash(hash) + .ok_or(anyhow::anyhow!( + "No step with hash {} exists in the current workbench", + hash + ))? + .clone()) + } } diff --git a/packages/cadmium/src/step/result.rs b/packages/cadmium/src/step/result.rs index 8ac66c85..4c3c5a03 100644 --- a/packages/cadmium/src/step/result.rs +++ b/packages/cadmium/src/step/result.rs @@ -16,21 +16,21 @@ use super::sketch_action::SketchActionResult; #[tsify(into_wasm_abi, from_wasm_abi)] #[serde(tag = "type")] pub enum StepResult { - Empty, - // TODO: Add a variable/expression - Point(Rc>), - Plane(Rc>), - Sketch(Rc>), - // Primitive(Rc>), - // Constraint(Rc>), - // Compound(Rc>), - SketchAction { - action: SketchActionResult, - faces: Vec, - }, - // We need the solids to be a named field so that we can serialize it with tag = "type" - // otherwise it would result in { type: "Solid", Solid[] } which isn't valid - Solid { - solids: Vec, - }, + Empty, + // TODO: Add a variable/expression + Point(Rc>), + Plane(Rc>), + Sketch(Rc>), + // Primitive(Rc>), + // Constraint(Rc>), + // Compound(Rc>), + SketchAction { + action: SketchActionResult, + faces: Vec, + }, + // We need the solids to be a named field so that we can serialize it with tag = "type" + // otherwise it would result in { type: "Solid", Solid[] } which isn't valid + Solid { + solids: Vec, + }, } diff --git a/packages/cadmium/src/step/sketch_action.rs b/packages/cadmium/src/step/sketch_action.rs index 4678f42d..78a8d809 100644 --- a/packages/cadmium/src/step/sketch_action.rs +++ b/packages/cadmium/src/step/sketch_action.rs @@ -13,39 +13,39 @@ use super::StepResult; #[tsify(into_wasm_abi, from_wasm_abi)] #[serde(tag = "action_type")] pub enum SketchActionResult { - Primitive(Rc>), - Constraint(Rc>), - Compound(Rc>), + Primitive(Rc>), + Constraint(Rc>), + Compound(Rc>), } impl From>> for SketchActionResult { - fn from(primitive: Rc>) -> Self { - SketchActionResult::Primitive(primitive) - } + fn from(primitive: Rc>) -> Self { + SketchActionResult::Primitive(primitive) + } } impl From>> for SketchActionResult { - fn from(constraint: Rc>) -> Self { - SketchActionResult::Constraint(constraint) - } + fn from(constraint: Rc>) -> Self { + SketchActionResult::Constraint(constraint) + } } impl From>> for SketchActionResult { - fn from(compound: Rc>) -> Self { - SketchActionResult::Compound(compound) - } + fn from(compound: Rc>) -> Self { + SketchActionResult::Compound(compound) + } } pub trait IntoSketchActionResult { - fn into_result(self, sketch: &ISketch) -> StepResult; + fn into_result(self, sketch: &ISketch) -> StepResult; } impl> IntoSketchActionResult for T { - fn into_result(self, sketch: &ISketch) -> StepResult { - let faces = sketch.faces(); - StepResult::SketchAction { - action: self.into(), - faces, - } - } + fn into_result(self, sketch: &ISketch) -> StepResult { + let faces = sketch.faces(); + StepResult::SketchAction { + action: self.into(), + faces, + } + } } diff --git a/packages/cadmium/src/workbench.rs b/packages/cadmium/src/workbench.rs index 4a26983b..2e59b4a9 100644 --- a/packages/cadmium/src/workbench.rs +++ b/packages/cadmium/src/workbench.rs @@ -1,4 +1,5 @@ use log::{debug, info}; +use loro::LoroDoc; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; use wasm_bindgen::prelude::*; @@ -19,234 +20,245 @@ use std::rc::Rc; /// A workbench is the main collection of set of objects that are being worked on. /// /// CADmium is mostly designed around it and acts in objects that are descendants of the workbench. -#[derive(Tsify, Debug, Clone, Serialize, Deserialize)] +#[derive(Tsify, Debug, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] #[non_exhaustive] pub struct Workbench { - /// The workbench name - mainly used for display purposes. - pub name: String, - /// A list of steps that have been taken in the workbench - it's append only and fork-able - pub history: Vec>>, - - /// Free-standing points in 3D space - not part of sketches - pub points: BTreeMap>>, - /// The next ID to use for a point - pub points_next_id: IDType, - - /// Planes that can be used for sketches - pub planes: BTreeMap>>, - /// The next ID to use for a plane - pub planes_next_id: IDType, - - /// Sketches that are part of the workbench - pub sketches: BTreeMap>>, - /// The next ID to use for a sketch - pub sketches_next_id: IDType, - /// Features that are part of the workbench (e.g. [`Extrusion`]) - /// - /// [`Extrusion`]: crate::feature::extrusion::Extrusion - pub features: BTreeMap>>, - /// The next ID to use for a feature - pub features_next_id: IDType, + /// The workbench name - mainly used for display purposes. + pub name: String, + /// A list of steps that have been taken in the workbench - it's append only and fork-able + pub history: Vec>>, + #[serde(with = "crate::step::evtree::loro_serde")] + pub evtree: LoroDoc, + + /// Free-standing points in 3D space - not part of sketches + #[serde(skip)] + pub points: BTreeMap>>, + /// The next ID to use for a point + #[serde(skip)] + pub points_next_id: IDType, + + /// Planes that can be used for sketches + #[serde(skip)] + pub planes: BTreeMap>>, + /// The next ID to use for a plane + #[serde(skip)] + pub planes_next_id: IDType, + + /// Sketches that are part of the workbench + #[serde(skip)] + pub sketches: BTreeMap>>, + /// The next ID to use for a sketch + #[serde(skip)] + pub sketches_next_id: IDType, + /// Features that are part of the workbench (e.g. [`Extrusion`]) + /// + /// [`Extrusion`]: crate::feature::extrusion::Extrusion + #[serde(skip)] + pub features: BTreeMap>>, + /// The next ID to use for a feature + #[serde(skip)] + pub features_next_id: IDType, } impl Workbench { - /// Create a new workbench with a given name - pub(crate) fn new(name: &str) -> Self { - info!("Creating new workbench: {}", name); - Workbench { - name: name.to_owned(), - history: vec![], - - points: BTreeMap::new(), - points_next_id: 0, - planes: BTreeMap::new(), - planes_next_id: 0, - - sketches: BTreeMap::new(), - sketches_next_id: 0, - features: BTreeMap::new(), - features_next_id: 0, - } - } - - /// Records the given message as a [`Step`] in the workbench history - /// - ///
- /// - /// Does NOT call the message handler itself, only appends it to the history - /// - ///
- pub fn add_message_step(&mut self, message: &Message, node: StepResult) { - self.history - .push(Rc::new(RefCell::new(Step::new(message.clone(), node)))); - } - - /// Returns a [`Step`] by its [`StepHash`] - /// - ///
- /// - /// Does NOT check for hash collision (i.e. two steps with the same hash) - /// - ///
- pub fn get_step_by_hash(&self, hash: StepHash) -> Option>> { - debug!( - "Looking for step with hash {} in hashes {:?}", - hash, - self.history - .iter() - .map(|step| step.borrow().hash()) - .collect::>() - ); - self.history - .iter() - .find(|step| step.borrow().hash() == hash) - .cloned() - } + /// Create a new workbench with a given name + pub(crate) fn new(name: &str) -> Self { + info!("Creating new workbench: {}", name); + Workbench { + name: name.to_owned(), + history: vec![], + evtree: LoroDoc::new(), + + points: BTreeMap::new(), + points_next_id: 0, + planes: BTreeMap::new(), + planes_next_id: 0, + + sketches: BTreeMap::new(), + sketches_next_id: 0, + features: BTreeMap::new(), + features_next_id: 0, + } + } + + /// Records the given message as a [`Step`] in the workbench history + /// + ///
+ /// + /// Does NOT call the message handler itself, only appends it to the history + /// + ///
+ pub fn add_message_step(&mut self, message: &Message, node: StepResult) { + self.history + .push(Rc::new(RefCell::new(Step::new(message.clone(), node)))); + } + + /// Returns a [`Step`] by its [`StepHash`] + /// + ///
+ /// + /// Does NOT check for hash collision (i.e. two steps with the same hash) + /// + ///
+ pub fn get_step_by_hash(&self, hash: StepHash) -> Option>> { + debug!( + "Looking for step with hash {} in hashes {:?}", + hash, + self.history + .iter() + .map(|step| step.borrow().hash()) + .collect::>() + ); + self.history + .iter() + .find(|step| step.borrow().hash() == hash) + .cloned() + } } impl Identifiable for Rc> { - type Parent = crate::project::Project; - const ID_NAME: &'static str = "workbench_id"; - - fn from_parent_id(parent: &crate::project::Project, hash: StepHash) -> anyhow::Result { - // For now at least there's no good way to differentiate between a workbench - // ID and a step hash. The workbench can't be hash-indexed as it's always - // changing - let id = hash.into_int(); - Ok(parent.get_workbench_by_id(id)?) - } + type Parent = crate::project::Project; + const ID_NAME: &'static str = "workbench_id"; + + fn from_parent_id(parent: &crate::project::Project, hash: StepHash) -> anyhow::Result { + // For now at least there's no good way to differentiate between a workbench + // ID and a step hash. The workbench can't be hash-indexed as it's always + // changing + let id = hash.into_int(); + Ok(parent.get_workbench_by_id(id)?) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddPoint { - pub x: f64, - pub y: f64, - pub z: f64, + pub x: f64, + pub y: f64, + pub z: f64, } impl MessageHandler for AddPoint { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Self::Parent, - ) -> anyhow::Result> { - let mut wb = sketch_ref.borrow_mut(); - - let new_id = wb.points_next_id; - let point = Rc::new(RefCell::new(Point3::new(self.x, self.y, self.z))); - wb.points.insert(new_id, point.clone()); - wb.points_next_id += 1; - Ok(Some((new_id, StepResult::Point(point)))) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Self::Parent, + ) -> anyhow::Result> { + let mut wb = sketch_ref.borrow_mut(); + + let new_id = wb.points_next_id; + let point = Rc::new(RefCell::new(Point3::new(self.x, self.y, self.z))); + wb.points.insert(new_id, point.clone()); + wb.points_next_id += 1; + Ok(Some((new_id, StepResult::Point(point)))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddPlane { - pub plane: Plane, - pub width: f64, - pub height: f64, + pub plane: Plane, + pub width: f64, + pub height: f64, } impl MessageHandler for AddPlane { - type Parent = Rc>; - fn handle_message( - &self, - sketch_ref: Self::Parent, - ) -> anyhow::Result> { - let mut wb = sketch_ref.borrow_mut(); - - let new_id = wb.planes_next_id; - let plane = Rc::new(RefCell::new(self.plane.clone())); - wb.planes.insert(new_id, plane.clone()); - wb.planes_next_id += 1; - Ok(Some((new_id, StepResult::Plane(plane)))) - } + type Parent = Rc>; + fn handle_message( + &self, + sketch_ref: Self::Parent, + ) -> anyhow::Result> { + let mut wb = sketch_ref.borrow_mut(); + + let new_id = wb.planes_next_id; + let plane = Rc::new(RefCell::new(self.plane.clone())); + wb.planes.insert(new_id, plane.clone()); + wb.planes_next_id += 1; + Ok(Some((new_id, StepResult::Plane(plane)))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct AddSketch { - pub plane_description: PlaneDescription, + pub plane_description: PlaneDescription, } impl MessageHandler for AddSketch { - type Parent = Rc>; - fn handle_message( - &self, - workbench_ref: Self::Parent, - ) -> anyhow::Result> { - let mut wb = workbench_ref.borrow_mut(); - let sketch = ISketch::try_from_plane_description(&wb, &self.plane_description)?; - - let new_id = wb.sketches_next_id; - let sketch_cell = Rc::new(RefCell::new(sketch)); - wb.sketches.insert(new_id, sketch_cell.clone()); - wb.sketches_next_id += 1; - Ok(Some((new_id, StepResult::Sketch(sketch_cell.clone())))) - } + type Parent = Rc>; + fn handle_message( + &self, + workbench_ref: Self::Parent, + ) -> anyhow::Result> { + let mut wb = workbench_ref.borrow_mut(); + let sketch = ISketch::try_from_plane_description(&wb, &self.plane_description)?; + + let new_id = wb.sketches_next_id; + let sketch_cell = Rc::new(RefCell::new(sketch)); + wb.sketches.insert(new_id, sketch_cell.clone()); + wb.sketches_next_id += 1; + Ok(Some((new_id, StepResult::Sketch(sketch_cell.clone())))) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub struct WorkbenchRename { - pub new_name: String, + pub new_name: String, } impl MessageHandler for WorkbenchRename { - type Parent = Rc>; - fn handle_message( - &self, - workbench_ref: Self::Parent, - ) -> anyhow::Result> { - let mut workbench = workbench_ref.borrow_mut(); - workbench.name = self.new_name.clone(); - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + workbench_ref: Self::Parent, + ) -> anyhow::Result> { + let mut workbench = workbench_ref.borrow_mut(); + workbench.name = self.new_name.clone(); + Ok(None) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(from_wasm_abi, into_wasm_abi)] pub struct SetSketchPlane { - pub sketch_id: IDType, - pub plane_description: PlaneDescription, + pub sketch_id: IDType, + pub plane_description: PlaneDescription, } impl MessageHandler for SetSketchPlane { - type Parent = Rc>; - fn handle_message( - &self, - workbench_ref: Self::Parent, - ) -> anyhow::Result> { - let wb = workbench_ref.borrow(); - - let plane = match self.plane_description { - PlaneDescription::PlaneId(plane_hash) => { - let plane_id = crate::ID_MAP - .with_borrow(|m| m.get(&plane_hash).cloned()) - .ok_or(anyhow::anyhow!( - "Failed to find plane with hash {}", - plane_hash - ))?; - wb.planes - .get(&plane_id) - .ok_or(anyhow::anyhow!("Failed to find plane with id {}", plane_id))? - } - PlaneDescription::SolidFace { - solid_id: _, - normal: _, - } => todo!("Implement SolidFace"), - } - .clone(); - - let sketch = wb.sketches.get(&self.sketch_id).ok_or(anyhow::anyhow!( - "Failed to find sketch with id {}", - self.sketch_id - ))?; - sketch.borrow_mut().plane = plane; - - Ok(None) - } + type Parent = Rc>; + fn handle_message( + &self, + workbench_ref: Self::Parent, + ) -> anyhow::Result> { + let wb = workbench_ref.borrow(); + + let plane = match self.plane_description { + PlaneDescription::PlaneId(plane_hash) => { + let plane_id = crate::ID_MAP + .with_borrow(|m| m.get(&plane_hash).cloned()) + .ok_or(anyhow::anyhow!( + "Failed to find plane with hash {}", + plane_hash + ))?; + wb.planes + .get(&plane_id) + .ok_or(anyhow::anyhow!("Failed to find plane with id {}", plane_id))? + } + PlaneDescription::SolidFace { + solid_id: _, + normal: _, + } => todo!("Implement SolidFace"), + } + .clone(); + + let sketch = wb.sketches.get(&self.sketch_id).ok_or(anyhow::anyhow!( + "Failed to find sketch with id {}", + self.sketch_id + ))?; + sketch.borrow_mut().plane = plane; + + Ok(None) + } } From 5cd7625bd3bad9754b63cada5ad98aa5618affbc Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Tue, 25 Jun 2024 23:37:33 +0300 Subject: [PATCH 2/6] Introduction of the EvTree as discussed with zxch3n Signed-off-by: Dimitris Zervas --- .vscode/settings.json | 2 + .../examples/project_simple_extrusion.rs | 2 +- packages/cadmium/src/error.rs | 17 ++++ packages/cadmium/src/lib.rs | 83 ++++++++++++++----- packages/cadmium/src/step/evtree.rs | 51 ++++++++++++ packages/cadmium/src/step/hash.rs | 19 +++++ packages/cadmium/src/step/mod.rs | 4 + 7 files changed, 157 insertions(+), 21 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0c05d521..0e017374 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,9 @@ "vite.showTerminal": true, "cSpell.words": [ "bindgen", + "evtree", "isketch", + "Loro", "Threlte", "tsify", "wireframe" diff --git a/packages/cadmium/examples/project_simple_extrusion.rs b/packages/cadmium/examples/project_simple_extrusion.rs index acc14f6e..314b4b74 100644 --- a/packages/cadmium/examples/project_simple_extrusion.rs +++ b/packages/cadmium/examples/project_simple_extrusion.rs @@ -31,7 +31,7 @@ fn main() { let wb_ref = p.workbenches.first().unwrap().clone(); let step = wb_ref.borrow().get_step_by_hash(sketch_id).unwrap(); - let StepResult::Sketch(sketch) = step.borrow().result.clone() else { + let StepResult::Sketch(sketch) = step.borrow().result().clone() else { panic!("Expected a sketch"); }; diff --git a/packages/cadmium/src/error.rs b/packages/cadmium/src/error.rs index 061f972a..dbb4f557 100644 --- a/packages/cadmium/src/error.rs +++ b/packages/cadmium/src/error.rs @@ -16,6 +16,23 @@ pub enum CADmiumError { #[error("The sketch ID {0} was not found")] SketchIDNotFound(u64), + #[error( + "Node {0} in evtree of project {1} workbench {2} is not a concrete LoroValue but a Handler" + )] + EvTreeNodeNotValue(usize, usize, usize), + #[error("Node {0} in evtree of project {1} workbench {2} is not a map")] + EvTreeNodeNotMap(usize, usize, usize), + #[error("The `prev` map key in node {0} in evtree of project {1} workbench {2} was not found")] + EvTreeNodePrevNotFound(usize, usize, usize), + #[error("The `this` map key in node {0} in evtree of project {1} workbench {2} was not found")] + EvTreeNodeThisNotFound(usize, usize, usize), + #[error("The `hash` meta key in node {0} was not found")] + EvTreeHashNotFound(i32), + #[error("The `hash` meta key in node {0} is a container and not a value")] + EvTreeHashIsContainer(i32), + #[error("The `hash` meta key is not an I64")] + EvTreeHashNotI64, + // RealSketch errors #[error("The primitive could not be found inside the sketch")] PrimitiveNotInSketch, diff --git a/packages/cadmium/src/lib.rs b/packages/cadmium/src/lib.rs index b606e237..735a29ec 100644 --- a/packages/cadmium/src/lib.rs +++ b/packages/cadmium/src/lib.rs @@ -75,7 +75,7 @@ use std::collections::BTreeMap; use error::CADmiumError; use message::{Message, MessageResult}; -use step::StepHash; +use step::{History, StepHash}; use tsify_next::declare; use wasm_bindgen::prelude::*; extern crate console_error_panic_hook; @@ -180,23 +180,66 @@ pub fn send_message(project_index: usize, message: &Message) -> MessageResult { }) } -/// Returns a concrete [`Workbench`](workbench::Workbench) from a [`Project`](project::Project). +/// Returns the history of a [`Workbench`](workbench::Workbench) as a [`History`] object #[wasm_bindgen] -pub fn get_workbench( +pub fn get_workbench_oplog( project_index: usize, workbench_index: usize, -) -> Result { +) -> Result { PROJECTS.with(|projects_ref| { let projects = projects_ref.borrow(); let p = projects .get(project_index) .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?; - let wb = p - .workbenches - .get(workbench_index) - .ok_or(CADmiumError::WorkbenchIDNotFound(workbench_index as u64).to_string())? - .borrow(); - Ok(wb.clone()) + let wb_cell = p + .get_workbench_by_id(workbench_index as u64) + .map_err(|e| e.to_string())?; + let wb = wb_cell.borrow(); + + Ok(History(wb.history.clone())) + }) +} + +/// Returns the event tree of a [`Workbench`](workbench::Workbench) as a serialized [`LoroDoc`](loro::LoroDoc) object +// TODO: Add ability to retrieve partial event trees +#[wasm_bindgen] +pub fn get_workbench_evtree( + project_index: usize, + workbench_index: usize, +) -> Result, String> { + PROJECTS.with(|projects_ref| { + let projects = projects_ref.borrow(); + let p = projects + .get(project_index) + .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?; + let wb_cell = p + .get_workbench_by_id(workbench_index as u64) + .map_err(|e| e.to_string())?; + let wb = wb_cell.borrow(); + + Ok(wb.evtree.export_snapshot()) + }) +} + +// TODO: Add ability to retrieve partial event trees +#[wasm_bindgen] +pub fn set_workbench_evtree( + project_index: usize, + workbench_index: usize, + evtree: Vec, +) -> Result<(), String> { + PROJECTS.with(|projects_ref| { + let projects = projects_ref.borrow(); + let p = projects + .get(project_index) + .ok_or(CADmiumError::ProjectIDNotFound(project_index).to_string())?; + let wb_cell = p + .get_workbench_by_id(workbench_index as u64) + .map_err(|e| e.to_string())?; + let wb = wb_cell.borrow(); + wb.evtree.import(&evtree).map_err(|e| e.to_string())?; + + Ok(()) }) } @@ -249,16 +292,16 @@ impl Project { // self.native.compute_constraint_errors(); } - #[wasm_bindgen] - pub fn get_workbench(&self, workbench_index: u32) -> workbench::Workbench { - // TODO: Use get() and return a Result - self.native - .workbenches - .get(workbench_index as usize) - .unwrap() - .borrow() - .clone() // This single call pollutes Clone derives for all MessageHandlers - } + // #[wasm_bindgen] + // pub fn get_workbench(&self, workbench_index: u32) -> workbench::Workbench { + // // TODO: Use get() and return a Result + // self.native + // .workbenches + // .get(workbench_index as usize) + // .unwrap() + // .borrow() + // .clone() // This single call pollutes Clone derives for all MessageHandlers + // } #[wasm_bindgen] pub fn send_message(&mut self, message: &Message) -> MessageResult { diff --git a/packages/cadmium/src/step/evtree.rs b/packages/cadmium/src/step/evtree.rs index 351a534a..b49b9c9e 100644 --- a/packages/cadmium/src/step/evtree.rs +++ b/packages/cadmium/src/step/evtree.rs @@ -1,3 +1,54 @@ +use loro::{LoroDoc, TreeID}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +use crate::error::CADmiumError; + +use super::StepHash; + +// TODO: Use a single global document and use the project + workbench ID as the key +pub const EVTREE_ID: &str = "evtree"; +pub const EVTREE_HASH_META: &str = "hash"; +// pub const EVTREE_PREV_ID: &str = "prev"; +// pub const EVTREE_THIS_ID: &str = "this"; + +#[derive(Debug, Default, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct EvTree { + #[serde(with = "loro_serde")] + doc: LoroDoc, +} + +impl EvTree { + /// Traverses the evolution tree from the current node until the root node + /// and returns the referenced hashes. + pub fn to_step_hashes(&self, peer: u64, counter: i32) -> anyhow::Result> { + let mut step_hashes = Vec::::new(); + let tree = self.doc.get_tree(EVTREE_ID); + let mut this = TreeID::new(peer, counter); + + loop { + let node_meta = tree.get_meta(this)?; + let value = node_meta + .get(EVTREE_HASH_META) + .ok_or(CADmiumError::EvTreeHashNotFound(this.counter))? + .left() + .ok_or(CADmiumError::EvTreeHashIsContainer(this.counter))?; + let hash: StepHash = (&value).try_into()?; + step_hashes.push(hash); + + match tree.parent(&this) { + Some(Some(parent)) => this = parent, + _ => break, + } + } + + // The traversal was done from the leaf to the root, so we need to reverse the list + step_hashes.reverse(); + Ok(step_hashes) + } +} + pub mod loro_serde { //! Serialization and deserialization implementation for `LoroDoc`. //! diff --git a/packages/cadmium/src/step/hash.rs b/packages/cadmium/src/step/hash.rs index 740ad8e4..4fd38679 100644 --- a/packages/cadmium/src/step/hash.rs +++ b/packages/cadmium/src/step/hash.rs @@ -1,6 +1,8 @@ use std::fmt::Display; +use crate::error::CADmiumError; use crate::message::Message; +use loro::LoroValue; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; use xxhash_rust::xxh3::xxh3_64; @@ -56,3 +58,20 @@ impl<'de> Deserialize<'de> for StepHash { Ok(Self(s.parse().unwrap())) } } + +impl From for LoroValue { + fn from(hash: StepHash) -> Self { + LoroValue::I64(i64::from_ne_bytes(hash.0.to_ne_bytes())) + } +} + +impl TryFrom<&LoroValue> for StepHash { + type Error = CADmiumError; + + fn try_from(value: &LoroValue) -> Result { + match value { + LoroValue::I64(val) => Ok(Self(u64::from_ne_bytes(val.to_ne_bytes()))), + _ => Err(CADmiumError::EvTreeHashNotI64), + } + } +} diff --git a/packages/cadmium/src/step/mod.rs b/packages/cadmium/src/step/mod.rs index de276162..d39e6df9 100644 --- a/packages/cadmium/src/step/mod.rs +++ b/packages/cadmium/src/step/mod.rs @@ -18,6 +18,10 @@ pub mod sketch_action; pub use hash::StepHash; pub use result::StepResult; +#[derive(Tsify, Clone, Debug, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct History(pub Vec>>); + /// A step describes a single operation that takes place in a [`Workbench`]. /// /// An operation is often a transformation of the part in the workbench, From 70267c52ba102a0b86c1fd7b43ffc329fa884535 Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Wed, 26 Jun 2024 00:27:31 +0300 Subject: [PATCH 3/6] Add the append method Signed-off-by: Dimitris Zervas --- packages/cadmium/src/step/evtree.rs | 63 +++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/packages/cadmium/src/step/evtree.rs b/packages/cadmium/src/step/evtree.rs index b49b9c9e..01038e99 100644 --- a/packages/cadmium/src/step/evtree.rs +++ b/packages/cadmium/src/step/evtree.rs @@ -1,4 +1,4 @@ -use loro::{LoroDoc, TreeID}; +use loro::{LoroDoc, LoroValue, TreeID}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; @@ -8,11 +8,14 @@ use super::StepHash; // TODO: Use a single global document and use the project + workbench ID as the key pub const EVTREE_ID: &str = "evtree"; +pub const EVTREE_META_ID: &str = "evtree_meta"; +pub const EVTREE_CURRENT_PEER: &str = "current_peer"; +pub const EVTREE_CURRENT_COUNTER: &str = "current_counter"; pub const EVTREE_HASH_META: &str = "hash"; // pub const EVTREE_PREV_ID: &str = "prev"; // pub const EVTREE_THIS_ID: &str = "this"; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[wasm_bindgen] pub struct EvTree { #[serde(with = "loro_serde")] @@ -22,10 +25,10 @@ pub struct EvTree { impl EvTree { /// Traverses the evolution tree from the current node until the root node /// and returns the referenced hashes. - pub fn to_step_hashes(&self, peer: u64, counter: i32) -> anyhow::Result> { + pub fn to_step_hashes(&self) -> anyhow::Result> { let mut step_hashes = Vec::::new(); let tree = self.doc.get_tree(EVTREE_ID); - let mut this = TreeID::new(peer, counter); + let mut this = self.get_current_treeid(); loop { let node_meta = tree.get_meta(this)?; @@ -47,6 +50,58 @@ impl EvTree { step_hashes.reverse(); Ok(step_hashes) } + + pub fn get_current_treeid(&self) -> TreeID { + let meta_map = self.doc.get_map(EVTREE_META_ID); + let LoroValue::I64(peer_i64) = meta_map.get(EVTREE_CURRENT_PEER).unwrap().left().unwrap() + else { + panic!("Peer ID is not an i64"); + }; + let peer = u64::from_ne_bytes(peer_i64.to_ne_bytes()); + let LoroValue::I64(counter) = meta_map + .get(EVTREE_CURRENT_COUNTER) + .unwrap() + .left() + .unwrap() + else { + panic!("Counter is not an i64"); + }; + TreeID::new(peer, counter as i32) + } + + fn set_current_treeid(&self, treeid: TreeID) { + let meta_map = self.doc.get_map(EVTREE_META_ID); + meta_map + .insert( + EVTREE_CURRENT_PEER, + i64::from_ne_bytes(treeid.peer.to_ne_bytes()), + ) + .unwrap(); + meta_map + .insert(EVTREE_CURRENT_COUNTER, treeid.counter) + .unwrap(); + } + + pub fn append(&mut self, hash: StepHash) { + let tree = self.doc.get_tree(EVTREE_ID); + let new_this = tree.create(Some(self.get_current_treeid())).unwrap(); + let meta = tree.get_meta(new_this).unwrap(); + meta.insert(EVTREE_HASH_META, hash).unwrap(); + self.set_current_treeid(new_this); + } +} + +impl Default for EvTree { + fn default() -> Self { + let doc = LoroDoc::new(); + let tree = doc.get_tree(EVTREE_ID); + let root = tree.create(None).unwrap(); + + let evtree = Self { doc }; + evtree.set_current_treeid(root); + + evtree + } } pub mod loro_serde { From dae0eb47abdebf3f388c9ce5a0257e6b6c0990fc Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Wed, 26 Jun 2024 02:50:52 +0300 Subject: [PATCH 4/6] Change the hashing to include author + timestamp and list evtree Signed-off-by: Dimitris Zervas --- packages/cadmium/src/lib.rs | 2 +- packages/cadmium/src/step/evtree.rs | 89 +++++++---------------------- packages/cadmium/src/step/hash.rs | 10 ---- packages/cadmium/src/step/mod.rs | 24 +++++++- packages/cadmium/src/workbench.rs | 14 ++--- 5 files changed, 50 insertions(+), 89 deletions(-) diff --git a/packages/cadmium/src/lib.rs b/packages/cadmium/src/lib.rs index 735a29ec..7b4ea297 100644 --- a/packages/cadmium/src/lib.rs +++ b/packages/cadmium/src/lib.rs @@ -217,7 +217,7 @@ pub fn get_workbench_evtree( .map_err(|e| e.to_string())?; let wb = wb_cell.borrow(); - Ok(wb.evtree.export_snapshot()) + Ok(wb.evtree.export()) }) } diff --git a/packages/cadmium/src/step/evtree.rs b/packages/cadmium/src/step/evtree.rs index 01038e99..77eb4ce0 100644 --- a/packages/cadmium/src/step/evtree.rs +++ b/packages/cadmium/src/step/evtree.rs @@ -1,9 +1,7 @@ -use loro::{LoroDoc, LoroValue, TreeID}; +use loro::LoroDoc; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -use crate::error::CADmiumError; - use super::StepHash; // TODO: Use a single global document and use the project + workbench ID as the key @@ -15,7 +13,13 @@ pub const EVTREE_HASH_META: &str = "hash"; // pub const EVTREE_PREV_ID: &str = "prev"; // pub const EVTREE_THIS_ID: &str = "this"; -#[derive(Debug, Serialize, Deserialize)] +/// A thin wrapper around a `LoroDoc` that represents the evolution tree. +/// +/// This is a list of the currently active steps in the project, as well as the +/// all the other possible versions - much like git. +/// +/// It doesn't hold the actual steps, but rather the hashes of the steps. +#[derive(Debug, Default, Serialize, Deserialize)] #[wasm_bindgen] pub struct EvTree { #[serde(with = "loro_serde")] @@ -27,80 +31,29 @@ impl EvTree { /// and returns the referenced hashes. pub fn to_step_hashes(&self) -> anyhow::Result> { let mut step_hashes = Vec::::new(); - let tree = self.doc.get_tree(EVTREE_ID); - let mut this = self.get_current_treeid(); - loop { - let node_meta = tree.get_meta(this)?; - let value = node_meta - .get(EVTREE_HASH_META) - .ok_or(CADmiumError::EvTreeHashNotFound(this.counter))? - .left() - .ok_or(CADmiumError::EvTreeHashIsContainer(this.counter))?; - let hash: StepHash = (&value).try_into()?; + self.doc.get_list(EVTREE_ID).for_each(|(_, value_handler)| { + let value = value_handler.as_value().unwrap(); + let hash = StepHash::try_from(value).unwrap(); step_hashes.push(hash); + }); - match tree.parent(&this) { - Some(Some(parent)) => this = parent, - _ => break, - } - } - - // The traversal was done from the leaf to the root, so we need to reverse the list - step_hashes.reverse(); Ok(step_hashes) } - pub fn get_current_treeid(&self) -> TreeID { - let meta_map = self.doc.get_map(EVTREE_META_ID); - let LoroValue::I64(peer_i64) = meta_map.get(EVTREE_CURRENT_PEER).unwrap().left().unwrap() - else { - panic!("Peer ID is not an i64"); - }; - let peer = u64::from_ne_bytes(peer_i64.to_ne_bytes()); - let LoroValue::I64(counter) = meta_map - .get(EVTREE_CURRENT_COUNTER) - .unwrap() - .left() - .unwrap() - else { - panic!("Counter is not an i64"); - }; - TreeID::new(peer, counter as i32) - } - - fn set_current_treeid(&self, treeid: TreeID) { - let meta_map = self.doc.get_map(EVTREE_META_ID); - meta_map - .insert( - EVTREE_CURRENT_PEER, - i64::from_ne_bytes(treeid.peer.to_ne_bytes()), - ) - .unwrap(); - meta_map - .insert(EVTREE_CURRENT_COUNTER, treeid.counter) - .unwrap(); + /// Add a new step hash to the evolution tree. + pub fn push(&mut self, hash: StepHash) { + let list = self.doc.get_list(EVTREE_ID); + list.push(hash).unwrap(); + self.doc.commit(); } - pub fn append(&mut self, hash: StepHash) { - let tree = self.doc.get_tree(EVTREE_ID); - let new_this = tree.create(Some(self.get_current_treeid())).unwrap(); - let meta = tree.get_meta(new_this).unwrap(); - meta.insert(EVTREE_HASH_META, hash).unwrap(); - self.set_current_treeid(new_this); + pub fn export(&self) -> Vec { + self.doc.export_snapshot() } -} - -impl Default for EvTree { - fn default() -> Self { - let doc = LoroDoc::new(); - let tree = doc.get_tree(EVTREE_ID); - let root = tree.create(None).unwrap(); - - let evtree = Self { doc }; - evtree.set_current_treeid(root); - evtree + pub fn import(&self, bytes: &[u8]) -> anyhow::Result<()> { + Ok(self.doc.import(bytes)?) } } diff --git a/packages/cadmium/src/step/hash.rs b/packages/cadmium/src/step/hash.rs index 4fd38679..be05a22d 100644 --- a/packages/cadmium/src/step/hash.rs +++ b/packages/cadmium/src/step/hash.rs @@ -1,11 +1,9 @@ use std::fmt::Display; use crate::error::CADmiumError; -use crate::message::Message; use loro::LoroValue; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; -use xxhash_rust::xxh3::xxh3_64; /// This represents a hash of a [`Message`]. /// @@ -30,14 +28,6 @@ impl StepHash { } } -impl From<&Message> for StepHash { - fn from(msg: &Message) -> Self { - // Maybe encode to binary instead of json? - let hash = xxh3_64(serde_json::to_string(msg).unwrap().as_bytes()); - Self(hash) - } -} - impl Display for StepHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) diff --git a/packages/cadmium/src/step/mod.rs b/packages/cadmium/src/step/mod.rs index d39e6df9..63465c61 100644 --- a/packages/cadmium/src/step/mod.rs +++ b/packages/cadmium/src/step/mod.rs @@ -5,6 +5,7 @@ use std::time::SystemTime; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; +use xxhash_rust::xxh3::xxh3_64; use crate::message::{Identifiable, Message}; use crate::workbench::Workbench; @@ -45,15 +46,28 @@ pub struct Step { impl Step { pub fn new(data: Message, result: StepResult) -> Self { - let hash = (&data).into(); + let timestamp = SystemTime::now(); + let author = "Anonymous".to_string(); + + let message_data = serde_json::to_string(&data).unwrap(); + let timestamp_str = serde_json::to_string(×tamp).unwrap(); + let author = serde_json::to_string(&author).unwrap(); + let hash_data = [ + message_data.as_bytes(), + timestamp_str.as_bytes(), + author.as_bytes(), + ] + .concat(); + let hash = StepHash::from_int(xxh3_64(&hash_data)); + Self { hash, name: format!("{}-{}", data, hash), suppressed: false, data, result, - timestamp: SystemTime::now(), - author: "Anonymous".to_string(), + timestamp, + author, } } @@ -76,6 +90,10 @@ impl Step { pub fn suppressed(&self) -> bool { self.suppressed } + + pub fn timestamp(&self) -> SystemTime { + self.timestamp + } } impl Display for Step { diff --git a/packages/cadmium/src/workbench.rs b/packages/cadmium/src/workbench.rs index 2e59b4a9..2ddc8269 100644 --- a/packages/cadmium/src/workbench.rs +++ b/packages/cadmium/src/workbench.rs @@ -1,5 +1,4 @@ use log::{debug, info}; -use loro::LoroDoc; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; use wasm_bindgen::prelude::*; @@ -8,6 +7,7 @@ use crate::archetypes::{Plane, PlaneDescription}; use crate::feature::point::Point3; use crate::feature::Feature; use crate::isketch::ISketch; +use crate::step::evtree::EvTree; use crate::step::{Step, StepHash, StepResult}; use crate::IDType; @@ -28,8 +28,7 @@ pub struct Workbench { pub name: String, /// A list of steps that have been taken in the workbench - it's append only and fork-able pub history: Vec>>, - #[serde(with = "crate::step::evtree::loro_serde")] - pub evtree: LoroDoc, + pub evtree: EvTree, /// Free-standing points in 3D space - not part of sketches #[serde(skip)] @@ -68,7 +67,7 @@ impl Workbench { Workbench { name: name.to_owned(), history: vec![], - evtree: LoroDoc::new(), + evtree: EvTree::default(), points: BTreeMap::new(), points_next_id: 0, @@ -82,7 +81,7 @@ impl Workbench { } } - /// Records the given message as a [`Step`] in the workbench history + /// Records the given message as a [`Step`] in the workbench history and evolution tree /// ///
/// @@ -90,8 +89,9 @@ impl Workbench { /// ///
pub fn add_message_step(&mut self, message: &Message, node: StepResult) { - self.history - .push(Rc::new(RefCell::new(Step::new(message.clone(), node)))); + let step = Step::new(message.clone(), node); + self.evtree.push(step.hash()); + self.history.push(Rc::new(RefCell::new(step))); } /// Returns a [`Step`] by its [`StepHash`] From 5dfe59fd669cfe261ccf3330cdc1702b8acb3e28 Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Wed, 26 Jun 2024 21:45:14 +0300 Subject: [PATCH 5/6] Implement workbench recalculation based on the evtree Signed-off-by: Dimitris Zervas --- packages/cadmium/src/message/idwrap/mod.rs | 13 +++---- packages/cadmium/src/message/message.rs | 35 ++++++++++++++++-- packages/cadmium/src/project.rs | 33 +++++++++++++++++ packages/cadmium/src/step/mod.rs | 6 +++- packages/cadmium/src/workbench.rs | 42 +++++++++++++++++----- 5 files changed, 109 insertions(+), 20 deletions(-) diff --git a/packages/cadmium/src/message/idwrap/mod.rs b/packages/cadmium/src/message/idwrap/mod.rs index abed9447..717c3751 100644 --- a/packages/cadmium/src/message/idwrap/mod.rs +++ b/packages/cadmium/src/message/idwrap/mod.rs @@ -34,7 +34,6 @@ use std::cell::RefCell; use std::rc::Rc; -use log::warn; use serde::de::DeserializeOwned; use serde::Serialize; use tsify_next::Tsify; @@ -96,19 +95,15 @@ where }; let mut wb = wb_cell.borrow_mut(); - wb.add_message_step(&self.clone().into(), node); - let hash = wb.history.last().map(|step| step.borrow().hash()).clone(); + let hash = wb.add_message_step(&self.clone().into(), node); if let Some(id) = id { - if let Some(hash) = hash { - crate::ID_MAP.with_borrow_mut(|m| m.insert(hash, id)); - } else { - warn!("IDWrap::handle_project_message: IDWrap returned an ID, but no hash was found in the workbench history"); - } + crate::ID_MAP.with_borrow_mut(|m| m.insert(hash, id)); } // Return the step ID (hash) instead of the message handler returned ID - Ok(hash) + // TODO: Why is this an option? + Ok(Some(hash)) } } diff --git a/packages/cadmium/src/message/message.rs b/packages/cadmium/src/message/message.rs index 24f4affe..677891ae 100644 --- a/packages/cadmium/src/message/message.rs +++ b/packages/cadmium/src/message/message.rs @@ -1,11 +1,15 @@ +use std::cell::RefCell; +use std::rc::Rc; + use cadmium_macros::MessageEnum; use serde::{Deserialize, Serialize}; use tsify_next::Tsify; -use crate::step::StepHash; +use crate::step::{StepHash, StepResult}; +use crate::IDType; use super::idwrap::IDWrap; -use super::ProjectMessageHandler; +use super::{MessageHandler, ProjectMessageHandler}; /// All the possible messages that can be sent to the backend. /// @@ -37,6 +41,33 @@ pub enum Message { StepDelete(IDWrap), } +impl Message { + pub fn recalculate( + &self, + workbench: Rc>, + ) -> anyhow::Result> { + match self { + // TODO: Move inside the derive macro + Message::WorkbenchRename(v) => v.inner().handle_message(workbench), + Message::WorkbenchPointAdd(v) => v.inner().handle_message(workbench), + Message::WorkbenchPlaneAdd(v) => v.inner().handle_message(workbench), + Message::WorkbenchSketchAdd(v) => v.inner().handle_message(workbench), + Message::WorkbenchSketchSetPlane(v) => v.inner().handle_message(workbench), + Message::WorkbenchPointUpdate(v) => v.inner().handle_message(workbench), + + Message::SketchAddPoint(v) => v.inner().handle_message(workbench), + Message::SketchAddArc(v) => v.inner().handle_message(workbench), + Message::SketchAddCircle(v) => v.inner().handle_message(workbench), + Message::SketchAddLine(v) => v.inner().handle_message(workbench), + Message::SketchAddRectangle(v) => v.inner().handle_message(workbench), + Message::SketchDeletePrimitive(v) => v.inner().handle_message(workbench), + + Message::FeatureExtrusionAdd(v) => v.inner.handle_message(workbench), + _ => Ok(None), + } + } +} + /// The result of a message handling operation. #[derive(Tsify, Debug, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] diff --git a/packages/cadmium/src/project.rs b/packages/cadmium/src/project.rs index cb31ef91..aa258586 100644 --- a/packages/cadmium/src/project.rs +++ b/packages/cadmium/src/project.rs @@ -12,6 +12,7 @@ use crate::message::idwrap::IDWrap; use crate::message::ProjectMessageHandler; use crate::step::StepHash; use crate::workbench::{AddPlane, AddPoint, Workbench}; +use crate::IDType; #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] @@ -126,6 +127,38 @@ impl Project { .cloned() .ok_or(CADmiumError::WorkbenchIDNotFound(id)) } + + pub fn rebuild_workbench(&self, workbench_index: IDType) -> anyhow::Result<()> { + let wb_cell = self.get_workbench_by_id(workbench_index)?; + let mut wb = wb_cell.borrow_mut(); + wb.clean_state(); + + let mut steps = vec![]; + for hash in wb.evtree.to_step_hashes()?.iter() { + let step = wb + .get_step_by_hash(*hash) + .ok_or(anyhow::anyhow!("Failed to find step with hash {}", hash))?; + + steps.push(step.clone()); + } + // Drop the borrow of the workbench before recalculating + // as each step will borrow the workbench itself + drop(wb); + + for step_cell in steps.iter() { + let mut step = step_cell.borrow_mut(); + step.result = step + .message() + .recalculate(wb_cell.clone())? + .ok_or(anyhow::anyhow!( + "Failed to recalculate step with hash {}", + step.hash() + ))? + .1; + } + + Ok(()) + } } #[derive(Tsify, Debug, Clone, Serialize, Deserialize)] diff --git a/packages/cadmium/src/step/mod.rs b/packages/cadmium/src/step/mod.rs index 63465c61..04ccbaad 100644 --- a/packages/cadmium/src/step/mod.rs +++ b/packages/cadmium/src/step/mod.rs @@ -39,7 +39,7 @@ pub struct Step { pub name: String, suppressed: bool, data: Message, - result: StepResult, + pub result: StepResult, timestamp: SystemTime, author: String, } @@ -75,6 +75,10 @@ impl Step { self.hash } + pub fn message(&self) -> &Message { + &self.data + } + pub fn result(&self) -> &StepResult { &self.result } diff --git a/packages/cadmium/src/workbench.rs b/packages/cadmium/src/workbench.rs index 2ddc8269..b1bc8b3e 100644 --- a/packages/cadmium/src/workbench.rs +++ b/packages/cadmium/src/workbench.rs @@ -7,6 +7,7 @@ use crate::archetypes::{Plane, PlaneDescription}; use crate::feature::point::Point3; use crate::feature::Feature; use crate::isketch::ISketch; +use crate::message::MessageHandler; use crate::step::evtree::EvTree; use crate::step::{Step, StepHash, StepResult}; use crate::IDType; @@ -88,19 +89,16 @@ impl Workbench { /// Does NOT call the message handler itself, only appends it to the history /// /// - pub fn add_message_step(&mut self, message: &Message, node: StepResult) { + pub fn add_message_step(&mut self, message: &Message, node: StepResult) -> StepHash { let step = Step::new(message.clone(), node); - self.evtree.push(step.hash()); + let hash = step.hash(); + self.evtree.push(hash); self.history.push(Rc::new(RefCell::new(step))); + + hash } /// Returns a [`Step`] by its [`StepHash`] - /// - ///
- /// - /// Does NOT check for hash collision (i.e. two steps with the same hash) - /// - ///
pub fn get_step_by_hash(&self, hash: StepHash) -> Option>> { debug!( "Looking for step with hash {} in hashes {:?}", @@ -115,6 +113,34 @@ impl Workbench { .find(|step| step.borrow().hash() == hash) .cloned() } + + /// Clean the internal workbench state + /// + /// Keeps only the origin geometry (origin point & top, front, right planes) + /// + /// Shouldn't be used directly, but rather through [`rebuild_state_from_evtree`] + pub(crate) fn clean_state(&mut self) { + // TODO: Handle None case for origin & planes + let origin = self.points.get(&0).unwrap().clone(); + self.points = BTreeMap::new(); + self.points.insert(0, origin); + + let top = self.planes.get(&0).unwrap().clone(); + let front = self.planes.get(&1).unwrap().clone(); + let right = self.planes.get(&2).unwrap().clone(); + self.planes = BTreeMap::new(); + self.planes.insert(0, top); + self.planes.insert(1, front); + self.planes.insert(2, right); + + self.sketches = BTreeMap::new(); + self.features = BTreeMap::new(); + + self.points_next_id = 1; + self.planes_next_id = 3; + self.sketches_next_id = 0; + self.features_next_id = 0; + } } impl Identifiable for Rc> { From e2c4e82bb6cb633dae62402f992df3aefe1de7a1 Mon Sep 17 00:00:00 2001 From: Dimitris Zervas Date: Thu, 27 Jun 2024 00:07:41 +0300 Subject: [PATCH 6/6] Use the bench namespace functions Signed-off-by: Dimitris Zervas --- Cargo.lock | 1 + applications/web/src/components/AppBar.svelte | 4 +- .../web/src/components/BottomBar.svelte | 4 +- .../web/src/components/ToolBar.svelte | 5 +- .../src/components/features/Extrusion.svelte | 6 +- .../web/src/components/features/Plane.svelte | 4 +- .../web/src/components/features/Point.svelte | 4 +- .../web/src/components/features/Sketch.svelte | 8 +- .../web/src/components/tools/NewLine.svelte | 4 +- .../src/components/tools/NewRectangle.svelte | 8 +- packages/cadmium/Cargo.toml | 1 + packages/cadmium/src/step/mod.rs | 11 +- packages/shared/projectUtils.ts | 115 +----------------- 13 files changed, 32 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19559379..94c71779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,6 +343,7 @@ version = "0.1.0" dependencies = [ "anyhow", "cadmium-macros", + "chrono", "console_error_panic_hook", "convert_case 0.6.0", "crc32fast", diff --git a/applications/web/src/components/AppBar.svelte b/applications/web/src/components/AppBar.svelte index 646c4874..07355652 100644 --- a/applications/web/src/components/AppBar.svelte +++ b/applications/web/src/components/AppBar.svelte @@ -11,7 +11,7 @@ import Sun from "phosphor-svelte/lib/Sun" import type {WithTarget} from "shared/types" import {base} from "../base" - import {renameProject} from "shared/projectUtils" + import {bench} from "shared/projectUtils" const log = (function () { const context = "[AppBar.svelte]"; const color="gray"; return Function.prototype.bind.call(console.log, console, `%c${context}`, `font-weight:bold;color:${color};`)})() // prettier-ignore @@ -62,7 +62,7 @@ on:keydown={e => { if (e.key === "Enter") { log("Renaming project") - renameProject(newProjectName) + bench.projectRename(newProjectName) project.name = newProjectName renaming = false } diff --git a/applications/web/src/components/BottomBar.svelte b/applications/web/src/components/BottomBar.svelte index e95d5702..6cbe0d94 100644 --- a/applications/web/src/components/BottomBar.svelte +++ b/applications/web/src/components/BottomBar.svelte @@ -1,6 +1,6 @@