From 77a46e9adbdca39e10c46c9a5464c907cfc21766 Mon Sep 17 00:00:00 2001 From: Matt Ferraro Date: Tue, 28 May 2024 23:41:53 -0400 Subject: [PATCH] add prototype of OpLog + EvolutionLog --- Cargo.lock | 66 ++ packages/cadmium/Cargo.toml | 1 + packages/cadmium/src/archetypes.rs | 17 +- packages/cadmium/src/extrusion.rs | 5 +- packages/cadmium/src/lib.rs | 3 +- packages/cadmium/src/main.rs | 381 +++++++-- packages/cadmium/src/message.rs | 11 +- packages/cadmium/src/oplog/mod.rs | 1171 ++++++++++++++++++++++++++++ packages/cadmium/src/sketch/mod.rs | 13 +- packages/cadmium/src/step.rs | 17 +- packages/cadmium/src/workbench.rs | 22 + 11 files changed, 1643 insertions(+), 64 deletions(-) create mode 100644 packages/cadmium/src/oplog/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b525bfbc..89d5d2fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "branches" version = "0.1.3" @@ -192,6 +201,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "sha2", "strum", "svg", "thiserror", @@ -268,6 +278,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -316,6 +335,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "darling" version = "0.20.3" @@ -385,6 +414,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "earcutr" version = "0.4.2" @@ -429,6 +468,16 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "geo" version = "0.26.0" @@ -1169,6 +1218,17 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "smallvec" version = "1.11.0" @@ -1489,6 +1549,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.11" diff --git a/packages/cadmium/Cargo.toml b/packages/cadmium/Cargo.toml index e191311c..d7dcbbc7 100644 --- a/packages/cadmium/Cargo.toml +++ b/packages/cadmium/Cargo.toml @@ -26,6 +26,7 @@ svg = "0.13.1" geo = "0.26.0" serde_with = "3.4.0" crc32fast = "1.3.2" +sha2 = "0.10.8" indexmap = "2.1.0" anyhow = { version = "1.0.86", features = ["backtrace"] } thiserror = "1.0.61" diff --git a/packages/cadmium/src/archetypes.rs b/packages/cadmium/src/archetypes.rs index 3440a0c6..fdc7f064 100644 --- a/packages/cadmium/src/archetypes.rs +++ b/packages/cadmium/src/archetypes.rs @@ -1,13 +1,14 @@ -use tsify::Tsify; use serde::{Deserialize, Serialize}; -use truck_modeling::Plane as TruckPlane; use truck_modeling::InnerSpace; +use truck_modeling::Plane as TruckPlane; +use tsify::Tsify; use crate::sketch::Point2; #[derive(Tsify, Debug, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] pub enum PlaneDescription { + None, PlaneId(String), SolidFace { solid_id: String, normal: Vector3 }, } @@ -87,6 +88,18 @@ impl Plane { } } + pub fn from_truck_face(tf: truck_modeling::Face) -> Self { + let os = tf.oriented_surface(); + match os { + truck_modeling::geometry::Surface::Plane(p) => { + return Plane::from_truck(p); + } + _ => { + panic!("I only know how to put sketches on planes"); + } + } + } + pub fn project(&self, point: &Point3) -> Point2 { let minus_origin = point.minus(&self.origin); let x = minus_origin.dot(&self.primary); diff --git a/packages/cadmium/src/extrusion.rs b/packages/cadmium/src/extrusion.rs index 2b9f6d41..d5f29834 100644 --- a/packages/cadmium/src/extrusion.rs +++ b/packages/cadmium/src/extrusion.rs @@ -16,7 +16,7 @@ use crate::archetypes::{Point3, Vector3}; use crate::project::{RealPlane, RealSketch}; use crate::sketch::{arc_to_points, Face, Sketch}; -use truck_modeling::{Point3 as TruckPoint3, Surface, Plane}; +use truck_modeling::{Plane, Point3 as TruckPoint3, Surface}; use truck_topology::Solid as TruckSolid; @@ -384,8 +384,8 @@ fn are_coplanar(p0: Plane, p1: Plane) -> bool { #[cfg(test)] mod tests { - use crate::project::Project; use crate::project::tests::create_test_project; + use crate::project::Project; #[allow(unused_imports)] use super::*; @@ -443,5 +443,4 @@ mod tests { realization.save_solid_as_step_file(keys[0], "pkg/test.step"); realization.save_solid_as_obj_file(keys[0], "pkg/test.obj", 0.001); } - } diff --git a/packages/cadmium/src/lib.rs b/packages/cadmium/src/lib.rs index 38ce23ab..c583e507 100644 --- a/packages/cadmium/src/lib.rs +++ b/packages/cadmium/src/lib.rs @@ -6,10 +6,11 @@ pub mod archetypes; pub mod error; pub mod extrusion; pub mod message; +pub mod oplog; pub mod project; pub mod realization; -pub mod solid; pub mod sketch; +pub mod solid; pub mod step; pub mod workbench; diff --git a/packages/cadmium/src/main.rs b/packages/cadmium/src/main.rs index 1759992a..5646b7db 100644 --- a/packages/cadmium/src/main.rs +++ b/packages/cadmium/src/main.rs @@ -1,69 +1,348 @@ #![allow(dead_code, unused)] use std::ops::{Sub, SubAssign}; +use std::sync::Arc; +use cadmium::archetypes::Plane; use cadmium::extrusion::fuse; +use cadmium::oplog::EvolutionLog; +use cadmium::oplog::Operation; +use cadmium::{oplog, sketch, Realization}; +use truck_meshalgo::analyzers::CalcVolume; use truck_meshalgo::filters::OptimizingFilter; use truck_meshalgo::tessellation::{MeshableShape, MeshedShape}; use truck_modeling::builder::{translated, tsweep, vertex}; -use truck_modeling::{Plane, Point3, Surface, Vector3}; -use truck_polymesh::{obj, InnerSpace, Invertible, ParametricSurface, Tolerance}; +use truck_modeling::{Point3, Surface, Vector3}; +use truck_polymesh::{ + obj, InnerSpace, Invertible, ParametricSurface, ParametricSurface3D, Tolerance, +}; use truck_shapeops::{and, or, ShapeOpsCurve, ShapeOpsSurface}; use truck_topology::{Shell, Solid}; fn main() { + // truck_test(); + stacked_cubes(); +} + +fn truck_test() { 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)); - - // 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)); - - // 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(); - - 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 result = serde_json::to_string(&cube_a); + match result { + Ok(json) => println!("{}", json), + Err(e) => println!("Error: {}", e), + } +} + +fn stacked_cubes() { + let mut el = EvolutionLog::new(); + + let workbench_id = el.append(Operation::CreateWorkbench { + nonce: "Workbench 1".to_string(), + }); + el.append(Operation::SetWorkbenchName { + workbench_id: workbench_id.clone(), + name: "Main Workbench".to_string(), + }); + + // Create the Top Plane + let top_plane_id = el.append(Operation::CreatePlane { + nonce: "the top plane".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetPlaneName { + plane_id: top_plane_id.clone(), + name: "Top".to_string(), + }); + let set_plane = el.append(Operation::SetPlane { + plane_id: top_plane_id.clone(), + plane: Plane::top(), + }); + let top_plane_real = el.realize_plane(&top_plane_id); + + // Create the sketch + let sketch_id = el.append(Operation::CreateSketch { + nonce: "top sketch".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetSketchName { + sketch_id: sketch_id.clone(), + name: "Original Sketch".to_string(), + }); + el.append(Operation::SetSketchPlane { + sketch_id: sketch_id.clone(), + plane_id: top_plane_real.clone(), + }); + + // make a square + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (0.0, 0.0), + end: (0.0, 100.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (0.0, 100.0), + end: (100.0, 100.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (100.0, 100.0), + end: (100.0, 0.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (100.0, 0.0), + end: (0.0, 0.0), + }); + let realized_sketch = el.realize_sketch(&sketch_id); + + // extrude the square + let extrusion_id = el.append(Operation::CreateExtrusion { + workbench_id: workbench_id.clone(), + nonce: "first extrusion".to_string(), + }); + el.append(Operation::SetExtrusionName { + extrusion_id: extrusion_id.clone(), + name: "Extrude1".to_string(), + }); + el.append(Operation::SetExtrusionDepth { + extrusion_id: extrusion_id.clone(), + depth: 100.0, + }); + el.append(Operation::SetExtrusionSketch { + extrusion_id: extrusion_id.clone(), + sketch_id: realized_sketch.clone(), + }); + el.append(Operation::SetExtrusionFaces { + extrusion_id: extrusion_id.clone(), + faces: vec![0], + }); + + el.realize_extrusion(&extrusion_id); + + // Create a plane on the face whose normal points up + let mut upward_face = None; + for (face_sha, face) in el.faces.iter() { + let surface = face.oriented_surface(); + let normal = surface.normal(0.0, 0.0); + if normal.near(&Vector3::new(0.0, 0.0, 1.0)) { + upward_face = Some(face.clone()); + } + } + let second_plane_id = el.append(Operation::CreatePlane { + nonce: "the second plane".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetPlaneName { + plane_id: second_plane_id.clone(), + name: "Second Plane".to_string(), + }); + match upward_face { + Some(face) => { + let set_plane = el.append(Operation::SetPlane { + plane_id: second_plane_id.clone(), + plane: Plane::from_truck_face(face), + }); + } + None => { + println!("No upward face found!"); + unreachable!(); + } + } + let second_plane_real = el.realize_plane(&second_plane_id); + + // Create a second sketch on top of the second plane + let second_sketch_id = el.append(Operation::CreateSketch { + nonce: "second sketch".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetSketchName { + sketch_id: second_sketch_id.clone(), + name: "Second Sketch".to_string(), + }); + el.append(Operation::SetSketchPlane { + sketch_id: second_sketch_id.clone(), + plane_id: second_plane_real.clone(), + }); + + // make a square + el.append(Operation::AddSketchLine { + sketch_id: second_sketch_id.clone(), + start: (20.0, 20.0), + end: (20.0, 80.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: second_sketch_id.clone(), + start: (20.0, 80.0), + end: (80.0, 80.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: second_sketch_id.clone(), + start: (80.0, 80.0), + end: (80.0, 20.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: second_sketch_id.clone(), + start: (80.0, 20.0), + end: (20.0, 20.0), + }); + + let second_realized_sketch = el.realize_sketch(&second_sketch_id); + + // extrude the second square + let second_extrusion_id = el.append(Operation::CreateExtrusion { + workbench_id: workbench_id.clone(), + nonce: "second extrusion".to_string(), + }); + el.append(Operation::SetExtrusionName { + extrusion_id: second_extrusion_id.clone(), + name: "Extrude2".to_string(), + }); + el.append(Operation::SetExtrusionDepth { + extrusion_id: second_extrusion_id.clone(), + depth: 60.0, + }); + el.append(Operation::SetExtrusionSketch { + extrusion_id: second_extrusion_id.clone(), + sketch_id: second_realized_sketch.clone(), + }); + el.append(Operation::SetExtrusionFaces { + extrusion_id: second_extrusion_id.clone(), + faces: vec![0], + }); + el.realize_extrusion(&second_extrusion_id); + + let mut small_solid_id = el.solids.keys().nth(0).unwrap().clone(); + let small_solid_volume = el.solids[&small_solid_id] + .truck_solid + .triangulation(0.1) + .to_polygon() + .volume(); + + let mut big_solid_id = el.solids.keys().nth(1).unwrap().clone(); + let big_solid_volume = el.solids[&big_solid_id] + .truck_solid + .triangulation(0.1) + .to_polygon() + .volume(); + + if big_solid_volume < small_solid_volume { + (small_solid_id, big_solid_id) = (big_solid_id, small_solid_id); + } + + el.append(Operation::FuseSolids { + solid1: big_solid_id, + solid2: small_solid_id, + }); + el.git_log(); + + for (solid_id, solid) in el.solids.iter() { + solid.save_as_obj("fused.obj", 0.1); + // let mut mesh = solid.truck_solid.triangulation(0.1).to_polygon(); + // mesh.put_together_same_attrs(0.1); + // let v = mesh.volume(); + // println!("ID: {solid_id} volume: {v}"); + } +} + +fn simple_cube() { + let mut el = EvolutionLog::new(); + + let workbench_id = el.append(Operation::CreateWorkbench { + nonce: "Workbench 1".to_string(), + }); + el.append(Operation::SetWorkbenchName { + workbench_id: workbench_id.clone(), + name: "Main Workbench".to_string(), + }); + + // Create the Top Plane + let top_plane_id = el.append(Operation::CreatePlane { + nonce: "the top plane".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetPlaneName { + plane_id: top_plane_id.clone(), + name: "Top".to_string(), + }); + let set_plane = el.append(Operation::SetPlane { + plane_id: top_plane_id.clone(), + plane: Plane::top(), + }); + let top_plane_real = el.realize_plane(&top_plane_id); + + // Create the sketch + let sketch_id = el.append(Operation::CreateSketch { + nonce: "top sketch".to_string(), + workbench_id: workbench_id.clone(), + }); + el.append(Operation::SetSketchName { + sketch_id: sketch_id.clone(), + name: "Original Sketch".to_string(), + }); + el.append(Operation::SetSketchPlane { + sketch_id: sketch_id.clone(), + plane_id: top_plane_real.clone(), + }); + + // make a square + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (0.0, 0.0), + end: (0.0, 100.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (0.0, 100.0), + end: (100.0, 100.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (100.0, 100.0), + end: (100.0, 0.0), + }); + el.append(Operation::AddSketchLine { + sketch_id: sketch_id.clone(), + start: (100.0, 0.0), + end: (0.0, 0.0), + }); + let realized_sketch = el.realize_sketch(&sketch_id); + + // extrude the square + let extrusion_id = el.append(Operation::CreateExtrusion { + workbench_id: workbench_id.clone(), + nonce: "first extrusion".to_string(), + }); + el.append(Operation::SetExtrusionName { + extrusion_id: extrusion_id.clone(), + name: "Extrude1".to_string(), + }); + el.append(Operation::SetExtrusionDepth { + extrusion_id: extrusion_id.clone(), + depth: 100.0, + }); + + el.append(Operation::SetExtrusionSketch { + extrusion_id: extrusion_id.clone(), + sketch_id: realized_sketch.clone(), + }); + el.append(Operation::SetExtrusionFaces { + extrusion_id: extrusion_id.clone(), + faces: vec![0], + }); + + el.realize_extrusion(&extrusion_id); + + // print each solid + for (solid_id, solid) in el.solids.iter() { + println!("Solid: {:?}", solid); + solid.save_as_obj("first_solid.obj", 0.01); + } + + el.git_log(); + // el.to_project(); } diff --git a/packages/cadmium/src/message.rs b/packages/cadmium/src/message.rs index 433b6183..22837aec 100644 --- a/packages/cadmium/src/message.rs +++ b/packages/cadmium/src/message.rs @@ -338,7 +338,11 @@ impl Message { } => { let workbench = project.get_workbench_by_id_mut(*workbench_id)?; let step = workbench.get_step_by_id_mut(&sketch_id)?; - let plane_description: &mut PlaneDescription = if let StepData::Sketch { plane_description, .. } = &mut step.data { + let plane_description: &mut PlaneDescription = if let StepData::Sketch { + plane_description, + .. + } = &mut step.data + { plane_description } else { return Err(CADmiumError::IncorrectStepDataType("Sketch".to_owned()).into()); @@ -349,7 +353,7 @@ impl Message { *plane_id = pid.to_owned(); Ok(format!("\"plane_id\": \"{}\"", pid)) } - _ => Err(CADmiumError::NotImplemented.into()) + _ => Err(CADmiumError::NotImplemented.into()), } } Message::DeleteStep { @@ -357,7 +361,8 @@ impl Message { step_name, } => { let workbench = project.get_workbench_by_id_mut(*workbench_id)?; - let index = workbench.history + let index = workbench + .history .iter() .position(|step| step.name == *step_name) .ok_or(CADmiumError::StepNameNotFound(step_name.to_owned()))?; diff --git a/packages/cadmium/src/oplog/mod.rs b/packages/cadmium/src/oplog/mod.rs new file mode 100644 index 00000000..6ceb5703 --- /dev/null +++ b/packages/cadmium/src/oplog/mod.rs @@ -0,0 +1,1171 @@ +use crate::archetypes::{Plane, PlaneDescription}; +use crate::extrusion::fuse; +use crate::project::{Project, RealPlane, RealSketch}; +use crate::solid::Solid; +use crate::step::StepData; +use crate::workbench::Workbench; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use truck_meshalgo::analyzers::CalcVolume; +use truck_meshalgo::tessellation::{MeshableShape, MeshedShape}; +// use std::hash::{Hash, Hasher}; +use std::vec; +// use truck_meshalgo::analyzers::CalcVolume; +// use truck_meshalgo::filters::OptimizingFilter; +// use truck_meshalgo::tessellation::{MeshableShape, MeshedShape}; +// use truck_polymesh::faces; +// use truck_topology::{ +// FaceDisplayFormat, ShellDisplayFormat, SolidDisplayFormat, VertexDisplayFormat, +// WireDisplayFormat, +// }; + +// use crate::extrusion::Solid; +// use crate::project::{ +// Plane, PlaneDescription, Project, RealPlane, RealSketch, StepData, Workbench, +// }; +use crate::sketch::Face; +// use FaceDisplayFormat as FDF; +// use ShellDisplayFormat as ShDF; +// use SolidDisplayFormat as SDF; +// use VertexDisplayFormat as VDF; +// use WireDisplayFormat as WDF; + +pub type Sha = String; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpLog { + commits: Vec, + commits_by_sha: HashMap, +} + +impl OpLog { + pub fn new() -> Self { + Self { + commits: vec![], + commits_by_sha: HashMap::new(), + } + } + + pub fn init(&mut self) { + let creation_commit = Commit::init(); + self.commits_by_sha + .insert(creation_commit.id.clone(), self.commits.len()); + self.commits.push(creation_commit); + } + + pub fn append(&mut self, parent: &Sha, operation: Operation) -> Commit { + let op_hash = operation.hash(); + let parent = parent.clone(); + let new_commit = Commit { + id: id_from_op_and_parent(&operation, &parent, self.commits.len()), + operation, + content_hash: op_hash, + parent, + }; + + self.commits_by_sha + .insert(new_commit.id.clone(), self.commits.len()); + self.commits.push(new_commit.clone()); + + new_commit + } + + pub fn last(&self) -> Option { + match self.commits.last() { + Some(commit) => Some(commit.clone()), + None => None, + } + } + + pub fn get_length(&self) -> usize { + self.commits.len() + } +} + +fn id_from_op_and_parent(operation: &Operation, parent: &Sha, nonce: usize) -> Sha { + let h = operation.hash(); + let mut hasher = Sha256::new(); + hasher.update(format!("{h}-{parent}-{nonce}").as_bytes()); + format!("{:x}", hasher.finalize()) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct EvolutionLog { + pub cursor: Sha, + pub oplog: OpLog, + pub project: Project, + pub workbenches: HashMap, + pub planes: HashMap, + pub real_planes: HashMap, + pub sketches: HashMap, + pub real_sketches: HashMap, + pub extrusions: HashMap, + pub faces: HashMap< + Sha, + truck_topology::Face< + truck_meshalgo::prelude::cgmath::Point3, + truck_modeling::Curve, + truck_modeling::Surface, + >, + >, + pub solids: HashMap, +} + +impl EvolutionLog { + pub fn new() -> Self { + let mut ol = OpLog::new(); + ol.init(); + Self { + cursor: ol.last().unwrap().id.clone(), + oplog: ol, + project: Project::new("Untitled"), + workbenches: HashMap::new(), + planes: HashMap::new(), + real_planes: HashMap::new(), + sketches: HashMap::new(), + real_sketches: HashMap::new(), + extrusions: HashMap::new(), + faces: HashMap::new(), + solids: HashMap::new(), + } + } + + pub fn append(&mut self, operation: Operation) -> Sha { + self.cursor = self.oplog.append(&self.cursor, operation.clone()).id; + + match operation { + Operation::CreateWorkbench { nonce } => { + let w = Workbench::new(&nonce); + self.project.workbenches.push(w); + let index = self.project.workbenches.len() - 1; + self.workbenches.insert(self.cursor.clone(), index); + // self.workbenches_inverse.insert(index, self.cursor.clone()); + } + Operation::SetWorkbenchName { workbench_id, name } => { + let workbench_index = self.workbenches.get(&workbench_id).unwrap(); + self.project.workbenches[*workbench_index].name = name.clone(); + } + Operation::CreatePlane { + nonce, + workbench_id, + } => { + let workbench_index = self.workbenches.get(&workbench_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_index).unwrap(); + let plane_id = wb.add_plane("Untitled-Plane", Plane::front()); + self.planes + .insert(self.cursor.clone(), (*workbench_index, plane_id)); + } + Operation::SetPlaneName { plane_id, name } => { + // the plane_id passed in is a SHA, we need to look up the actual plane_id + let (workbench_idx, step_id) = self.planes.get(&plane_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(step_id).unwrap(); + wb.history.get_mut(step_idx as usize).unwrap().name = name.to_owned(); + } + Operation::SetPlane { plane_id, plane } => { + let (workbench_idx, step_id) = self.planes.get(&plane_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(step_id).unwrap(); + let step = wb.history.get_mut(step_idx as usize).unwrap(); + let new_plane = plane; // this is just to change the name to avoid a collision + if let StepData::Plane { plane, .. } = &mut step.data { + *plane = new_plane.clone(); + } else { + unreachable!() + }; + } + Operation::CreateSketch { + nonce, + workbench_id, + } => { + let workbench_index = self.workbenches.get(&workbench_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_index).unwrap(); + let sketch_id = wb.add_blank_sketch("Untitled-Sketch"); + self.sketches + .insert(self.cursor.clone(), (*workbench_index, sketch_id)); + } + Operation::SetSketchName { sketch_id, name } => { + let (workbench_idx, step_id) = self.sketches.get(&sketch_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(step_id).unwrap(); + wb.history.get_mut(step_idx as usize).unwrap().name = name.to_owned(); + } + Operation::SetSketchPlane { + sketch_id, + plane_id, + } => { + let real_plane_sha = plane_id; + let (workbench_idx_sketch, sketch_id) = self.sketches.get(&sketch_id).unwrap(); + let (workbench_idx_plane, plane_id) = + self.real_planes.get(&real_plane_sha).unwrap(); + assert_eq!(workbench_idx_sketch, workbench_idx_plane); + let mut wb = self + .project + .workbenches + .get_mut(*workbench_idx_plane) + .unwrap(); + let step_idx = wb.step_id_from_unique_id(sketch_id).unwrap(); + let step = wb.history.get_mut(step_idx as usize).unwrap(); + if let StepData::Sketch { + plane_description, .. + } = &mut step.data + { + *plane_description = PlaneDescription::PlaneId(real_plane_sha.clone()); + } else { + unreachable!() + }; + } + Operation::AddSketchLine { + sketch_id, + start, + end, + } => { + let (workbench_idx, sketch_id) = self.sketches.get(&sketch_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(sketch_id).unwrap(); + let step = wb.history.get_mut(step_idx as usize).unwrap(); + if let StepData::Sketch { sketch, .. } = &mut step.data { + sketch.add_line_segment(start.0, start.1, end.0, end.1); + } else { + unreachable!() + }; + } + Operation::CreateExtrusion { + workbench_id, + nonce, + } => { + let workbench_idx = self.workbenches.get(&workbench_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + + let extrusion = crate::extrusion::Extrusion { + sketch_id: "".to_owned(), + face_ids: vec![], + length: 25.0, + offset: 0.0, + direction: crate::extrusion::Direction::Normal, + mode: crate::extrusion::ExtrusionMode::New, + }; + wb.add_extrusion("Untitled Extrusion", extrusion); + let step_id = wb.history.len() - 1; + self.extrusions + .insert(self.cursor.clone(), (*workbench_idx, step_id as usize)); + } + Operation::SetExtrusionName { extrusion_id, name } => { + let (workbench_idx, extrusion_idx) = self.extrusions.get(&extrusion_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + wb.history.get_mut(*extrusion_idx).unwrap().name = name.clone(); + } + Operation::SetExtrusionDepth { + extrusion_id, + depth, + } => { + let (workbench_idx, extrusion_idx) = self.extrusions.get(&extrusion_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + if let StepData::Extrusion { extrusion, .. } = &mut wb.history[*extrusion_idx].data + { + extrusion.length = depth; + } else { + unreachable!() + }; + } + Operation::SetExtrusionFaces { + extrusion_id, + faces, + } => { + let (workbench_idx, extrusion_idx) = self.extrusions.get(&extrusion_id).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + if let StepData::Extrusion { extrusion, .. } = &mut wb.history[*extrusion_idx].data + { + extrusion.face_ids = faces.iter().map(|i| *i as u64).collect(); + // let actual_faces = faces + // .iter() + // .map(|sha| self.faces.get(sha).unwrap().clone()) + // .collect(); + // extrusion.faces = actual_faces; + } else { + unreachable!() + }; + } + Operation::SetExtrusionSketch { + extrusion_id, + sketch_id, + } => { + let (workbench_idx, extrusion_idx) = self.extrusions.get(&extrusion_id).unwrap(); + let real_sketch = self + .real_sketches + .get(&sketch_id) + .expect("No such real sketch"); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + if let StepData::Extrusion { extrusion, .. } = &mut wb.history[*extrusion_idx].data + { + extrusion.sketch_id = sketch_id.clone(); + } else { + unreachable!() + }; + } + Operation::FuseSolids { solid1, solid2 } => { + let solid_a = self.solids.get(&solid1).unwrap(); + let solid_b = self.solids.get(&solid2).unwrap(); + + let fused = fuse(&solid_a.truck_solid, &solid_b.truck_solid); + match fused { + Some(fused) => { + let new_solid = Solid::from_truck_solid("alpha".to_owned(), fused); + let new_op = Operation::CreateSolid { + nonce: "Fused Solid".to_string(), + solid: new_solid.clone(), + }; + self.append(new_op); + self.solids.insert(self.cursor.clone(), new_solid); + + // delete the old solids + self.solids.remove(&solid1); + let delete_op_1 = Operation::DeleteSolid { solid_id: solid1 }; + self.append(delete_op_1); + + self.solids.remove(&solid2); + let delete_op_2 = Operation::DeleteSolid { solid_id: solid2 }; + self.append(delete_op_2); + } + _ => {} + } + } + _ => {} + } + + self.cursor.clone() + } + + // fn find_faces_do_not_use(&mut self, workbench_id: &Sha, sketch_id: &Sha) -> Sha { + // // TODO: delete this whole function. It is unnecessary + // let (workbench_idx, sketch_id) = self.sketches.get(sketch_id).unwrap(); + // // let workbench_sha = self.workbenches_inverse.get(workbench_idx).unwrap(); + // let wb = self.project.workbenches.get(*workbench_idx).unwrap(); + + // let step_idx = wb.step_id_from_unique_id(sketch_id).unwrap(); + // let step = wb.history.get(step_idx as usize).unwrap(); + + // let mut new_face_ops = Vec::new(); + // if let StepData::Sketch { sketch, .. } = &step.data { + // let (faces, _unused_segments) = sketch.find_faces(); + // for face in faces { + // let face_op = Operation::CreateFace { + // workbench_id: workbench_id.clone(), + // sketch_id: sketch_id.clone(), + // face: face.clone(), + // }; + // println!("Face Op: {:?}", face_op); + // new_face_ops.push(face_op); + // } + // } else { + // unreachable!() + // }; + + // for face_op in new_face_ops { + // self.append(face_op.clone()); + // if let Operation::CreateFace { face, .. } = face_op { + // self.faces.insert(self.cursor.clone(), face.clone()); + // } else { + // unreachable!() + // } + // } + + // self.cursor.clone() + // } + + pub fn realize_plane(&mut self, plane_id: &Sha) -> Sha { + let mut new_operations = vec![]; + let plane_sha = plane_id; + let wbidx; + + { + let (workbench_idx, plane_uid) = self.planes.get(plane_sha).unwrap(); + wbidx = workbench_idx.clone(); + let wb = self.project.workbenches.get(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(plane_uid).unwrap(); + let step = wb.history.get(step_idx as usize).unwrap(); + + if let StepData::Plane { plane, .. } = &step.data { + new_operations.push(Operation::CreateRealPlane { + plane_id: plane_sha.clone(), + real_plane: RealPlane { + plane: plane.clone(), + width: 90.0, + height: 60.0, + name: plane_sha.clone(), + }, + }); + } else { + unreachable!() + }; + } + + for new_operation in new_operations { + // frustratingly, we use a for loop because that's the easiest way to appease the borrow + // checker, but we know there's only one operation in the vec + self.append(new_operation.clone()); + if let Operation::CreateRealPlane { real_plane, .. } = new_operation { + self.real_planes + .insert(self.cursor.clone(), (wbidx, real_plane)); + } else { + unreachable!() + } + } + + self.cursor.clone() + } + + pub fn realize_sketch(&mut self, sketch_id: &Sha) -> Sha { + let sketch_sha = sketch_id; + let (workbench_idx, sketch_id) = self.sketches.get(sketch_sha).unwrap(); + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step_idx = wb.step_id_from_unique_id(sketch_id).unwrap(); + let step = wb.history.get_mut(step_idx as usize).unwrap(); + + let new_op; + + if let StepData::Sketch { + sketch, + plane_description, + .. + } = &mut step.data + { + if let PlaneDescription::PlaneId(plane_sha) = plane_description { + let (workbench_idx_plane, real_plane) = self.real_planes.get(plane_sha).unwrap(); + assert_eq!(workbench_idx, workbench_idx_plane); + // let rp = real_plane.clone(); + let real_sketch = + RealSketch::new("test-plane-name", plane_sha, &real_plane, sketch); + + new_op = Operation::CreateRealSketch { + sketch_id: sketch_sha.clone(), + real_sketch, + }; + } else { + unreachable!() + } + } else { + unreachable!() + }; + + self.append(new_op.clone()); + + if let Operation::CreateRealSketch { real_sketch, .. } = new_op { + self.real_sketches + .insert(self.cursor.clone(), real_sketch.clone()); + } else { + unreachable!() + } + + self.cursor.clone() + } + + pub fn realize_extrusion(&mut self, extrusion_id: &Sha) { + let (workbench_idx, extrusion_idx) = self.extrusions.get(extrusion_id).unwrap(); + // iterate through all of self.workbenches to find the one whose index matches workbench_idx + let workbench_sha = self.workbenches.iter().find_map(|(key, &val)| { + if val == *workbench_idx { + Some(key) + } else { + None + } + }); + let workbench_sha = workbench_sha.unwrap().clone(); + + let mut wb = self.project.workbenches.get_mut(*workbench_idx).unwrap(); + let step = wb.history.get_mut(*extrusion_idx).unwrap(); + + if let StepData::Extrusion { extrusion, .. } = &step.data { + let real_sketch = self.real_sketches.get(&extrusion.sketch_id).unwrap(); + let (_, real_plane) = self.real_planes.get(&real_sketch.plane_id).unwrap(); + let solids = + Solid::from_extrusion(step.name.clone(), real_plane, real_sketch, extrusion); + + for (name, solid) in solids { + let new_op = Operation::CreateSolid { + nonce: name, + solid: solid.clone(), + }; + + self.append(new_op); + self.solids.insert(self.cursor.clone(), solid.clone()); + for boundary in solid.truck_solid.boundaries() { + boundary.face_iter().for_each( + |face: &truck_topology::Face< + truck_meshalgo::prelude::cgmath::Point3, + truck_modeling::Curve, + truck_modeling::Surface, + >| { + let o = Operation::CreateTruckFace { + workbench_id: workbench_sha.clone(), + solid_id: self.cursor.clone(), + face: face.clone(), + }; + self.append(o); + self.faces.insert(self.cursor.clone(), face.clone()); + }, + ); + } + } + } else { + unreachable!() + }; + } + + pub fn pretty_print(&self) { + for commit in &self.oplog.commits { + println!("{}", commit.pretty_print()); + } + } + + pub fn to_tree(&self) -> CommitNode { + // Build a tree of commits using CommitNode + let mut commit_node_table: HashMap = HashMap::new(); + for commit in &self.oplog.commits { + commit_node_table.insert( + commit.id.clone(), + CommitNode { + commit: commit.id.clone(), + children: vec![], + }, + ); + } + for commit in &self.oplog.commits { + let parent = commit.parent.clone(); + if parent == "" { + // special treatment for the root node + continue; + } + let mut parent_commit_node = commit_node_table.get_mut(&parent).unwrap(); + parent_commit_node.children.push(commit.id.clone()); + } + + let root_node = commit_node_table.get(&self.oplog.commits[0].id).unwrap(); + root_node.clone() + } + + pub fn git_log(&self) { + // Build a tree of commits using CommitNode + let mut commit_node_table: HashMap = HashMap::new(); + for commit in &self.oplog.commits { + commit_node_table.insert( + commit.id.clone(), + CommitNode { + commit: commit.id.clone(), + children: vec![], + }, + ); + } + for commit in &self.oplog.commits { + let parent = commit.parent.clone(); + if parent == "" { + // special treatment for the root node + continue; + } + let mut parent_commit_node = commit_node_table.get_mut(&parent).unwrap(); + parent_commit_node.children.push(commit.id.clone()); + // println!( + // "Parent now has: {} children", + // parent_commit_node.children.len() + // ) + } + + let root_node = commit_node_table.get(&self.oplog.commits[0].id).unwrap(); + + let commit_table = self + .oplog + .commits + .iter() + .map(|commit| (commit.id.clone(), commit)) + .collect::>(); + + // const OTHER_CHILD: &str = "│ "; // prefix: pipe + // const OTHER_ENTRY: &str = "├── "; // connector: tee + // const FINAL_CHILD: &str = " "; // prefix: no more siblings + // const FINAL_ENTRY: &str = "└── "; // connector: elbow + + println!("Root:"); + visit(&root_node.commit, "", &commit_table, &commit_node_table); + + fn visit( + sha: &Sha, + prefix: &str, + commit_table: &HashMap, + commit_node_table: &HashMap, + ) { + let commit = commit_table.get(sha).unwrap(); + let commit_node = commit_node_table.get(sha).unwrap(); + println!("{}* {}", prefix, commit); + + if commit_node.children.len() == 0 { + return; + } else if commit_node.children.len() == 1 { + visit( + &commit_node.children[0], + &prefix, + commit_table, + commit_node_table, + ); + } else if commit_node.children.len() == 2 { + println!("{}|\\", prefix); + visit( + &commit_node.children[0], + &format!("| {}", prefix), + commit_table, + commit_node_table, + ); + visit( + &commit_node.children[1], + &prefix, + commit_table, + commit_node_table, + ); + } + } + } + + pub fn checkout(&mut self, sha: Sha) -> Result { + // check that the sha exists in the oplog before doing this + for commit in &self.oplog.commits { + if commit.id == sha { + self.cursor = sha; + return Ok(self.cursor.clone()); + } + } + Err(format!("SHA {} not found in oplog", sha)) + } + + pub fn cherry_pick(&mut self, sha: Sha) -> Result { + // check that the sha exists in the oplog before doing this + for commit in &self.oplog.commits { + if commit.id == sha { + let new_operation = commit.operation.clone(); + let mut new_commit_id = self.append(new_operation.clone()); + + // If the original commit created an entity, we'll need to create an alias commit + if new_operation.is_create() { + new_commit_id = self.append(Operation::Alias { + original: sha, + new: new_commit_id.clone(), + }); + } + + return Ok(new_commit_id); + } + } + Err(format!("SHA {} not found in oplog", sha)) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Commit { + pub operation: Operation, + pub content_hash: Sha, + pub parent: Sha, + pub id: Sha, // this is the SHA of "operation + parent" +} + +impl Commit { + pub fn init() -> Self { + let init_op = Operation::Create { + nonce: "Hello World".to_string(), // TODO: replace with actual seeded random string + }; + let parent_sha = "".to_owned(); + Self { + id: id_from_op_and_parent(&init_op, &parent_sha, 0), + content_hash: init_op.hash(), + operation: init_op, + parent: parent_sha, + } + } + + pub fn pretty_print(&self) -> String { + // truncate to just the first 10 chars of self.id + format!("{}: {}", &self.id[..10], self.operation.pretty_print()) + } +} + +impl std::fmt::Display for Commit { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}: {}", &self.id[..6], self.operation) + } +} + +#[derive(Debug, Clone)] +pub struct CommitNode { + pub commit: Sha, + pub children: Vec, +} + +impl std::fmt::Display for CommitNode { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.commit) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Operation { + Create { + nonce: String, + }, + Describe { + description: String, + commit: Sha, + }, + Alias { + original: Sha, + new: Sha, + }, + + CreateProject { + nonce: String, + }, + SetProjectName { + project_id: Sha, + name: String, + }, + + CreateWorkbench { + nonce: String, + }, + SetWorkbenchName { + workbench_id: Sha, + name: String, + }, + + CreatePlane { + nonce: String, + workbench_id: Sha, + }, + SetPlaneName { + plane_id: Sha, + name: String, + }, + SetPlane { + plane_id: Sha, + plane: Plane, + }, + CreateRealPlane { + plane_id: Sha, + real_plane: RealPlane, + }, + + CreateSketch { + nonce: String, + workbench_id: Sha, + }, + SetSketchName { + sketch_id: Sha, + name: String, + }, + SetSketchPlane { + sketch_id: Sha, + plane_id: Sha, + }, + CreateRealSketch { + sketch_id: Sha, + real_sketch: RealSketch, + }, + + AddSketchRectangle { + sketch_id: Sha, + x: f64, + y: f64, + width: f64, + height: f64, + }, + AddSketchCircle { + sketch_id: Sha, + x: f64, + y: f64, + radius: f64, + }, + AddSketchLine { + sketch_id: Sha, + start: (f64, f64), + end: (f64, f64), + }, + AddSketchHandle { + sketch_id: Sha, + position: (f64, f64), + }, + FinalizeSketch { + workbench_id: Sha, + sketch_id: Sha, + }, + + CreateFace { + workbench_id: Sha, + sketch_id: Sha, + face: Face, + }, + CreateTruckFace { + workbench_id: Sha, + solid_id: Sha, + face: truck_topology::Face< + truck_meshalgo::prelude::cgmath::Point3, + truck_modeling::Curve, + truck_modeling::Surface, + >, + }, + + CreateExtrusion { + workbench_id: Sha, + nonce: String, + }, + SetExtrusionName { + extrusion_id: Sha, + name: String, + }, + SetExtrusionSketch { + extrusion_id: Sha, + sketch_id: Sha, + }, + SetExtrusionHandles { + extrusion_id: Sha, + handles: Vec, + }, + SetExtrusionDepth { + extrusion_id: Sha, + depth: f64, + }, + SetExtrusionFaces { + extrusion_id: Sha, + faces: Vec, + }, + + CreateSolid { + nonce: String, + solid: Solid, + }, + + DeleteSolid { + solid_id: Sha, + }, + + FuseSolids { + solid1: Sha, + solid2: Sha, + }, +} + +impl Operation { + pub fn is_create(&self) -> bool { + match self { + Operation::CreatePlane { .. } => true, + Operation::CreateSketch { .. } => true, + Operation::CreateExtrusion { .. } => true, + _ => false, + } + } + + pub fn hash(&self) -> Sha { + let mut hasher = Sha256::new(); + + hasher.update("cadmium".as_bytes()); // mm, salt + + hasher.update(format!("{:?}", self.pretty_print()).as_bytes()); + + format!("{:x}", hasher.finalize()) + } + + pub fn pretty_print(&self) -> String { + let num_chars = 6; + match self { + Operation::Create { nonce } => format!("Create: {}", nonce), + Operation::Describe { + description, + commit, + } => format!( + "Describe: {} '{}'", + commit.to_owned()[..num_chars].to_string(), + description + ), + Operation::Alias { original, new } => { + format!( + "Alias: from {} to {}", + original.to_owned()[..num_chars].to_string(), + new.to_owned()[..num_chars].to_string() + ) + } + Operation::CreateProject { nonce } => format!("CreateProject: {}", nonce), + Operation::SetProjectName { project_id, name } => { + format!( + "SetProjectName: {} '{}'", + project_id.to_owned()[..num_chars].to_string(), + name + ) + } + Operation::CreateWorkbench { nonce } => { + format!("CreateWorkspace: {}", nonce) + } + Operation::SetWorkbenchName { workbench_id, name } => { + format!( + "SetWorkspaceName: {} '{}'", + workbench_id.to_owned()[..num_chars].to_string(), + name + ) + } + Operation::CreatePlane { + nonce, + workbench_id, + } => format!( + "CreatePlane: {} {}", + workbench_id.to_owned()[..num_chars].to_string(), + nonce + ), + Operation::SetPlaneName { plane_id, name } => { + format!( + "SetPlaneName: {} '{}'", + plane_id.to_owned()[..num_chars].to_string(), + name + ) + } + Operation::SetPlane { plane_id, plane } => { + format!( + "SetPlane: {}", + plane_id.to_owned()[..num_chars].to_string(), + // plane + ) + } + Operation::CreateRealPlane { + plane_id, + real_plane, + } => { + format!( + "CreateRealPlane: {} {:?}", + plane_id.to_owned()[..num_chars].to_string(), + real_plane + ) + } + Operation::CreateSketch { + nonce, + workbench_id, + } => format!( + "CreateSketch: {} {}", + workbench_id.to_owned()[..num_chars].to_string(), + nonce + ), + Operation::SetSketchName { sketch_id, name } => { + format!( + "SetSketchName: {} '{}'", + sketch_id.to_owned()[..num_chars].to_string(), + name + ) + } + Operation::SetSketchPlane { + sketch_id, + plane_id, + } => { + format!( + "SetSketchPlane: {} {}", + sketch_id.to_owned()[..num_chars].to_string(), + plane_id.to_owned()[..num_chars].to_string() + ) + } + Operation::AddSketchRectangle { + sketch_id, + x, + y, + width, + height, + } => format!( + "AddSketchRectangle: {} ({}, {}) {}x{}", + sketch_id.to_owned()[..num_chars].to_string(), + x, + y, + width, + height + ), + Operation::AddSketchCircle { + sketch_id, + x, + y, + radius, + } => format!( + "AddSketchCircle: {} ({}, {}) r={}", + sketch_id.to_owned()[..num_chars].to_string(), + x, + y, + radius + ), + Operation::AddSketchLine { + sketch_id, + start, + end, + } => format!( + "AddSketchLine: {} ({}, {}) to ({}, {})", + sketch_id.to_owned()[..num_chars].to_string(), + start.0, + start.1, + end.0, + end.1 + ), + Operation::AddSketchHandle { + sketch_id, + position, + } => format!( + "AddSketchHandle: {} ({}, {})", + sketch_id.to_owned()[..num_chars].to_string(), + position.0, + position.1 + ), + Operation::FinalizeSketch { + sketch_id, + workbench_id, + } => { + format!( + "FinalizeSketch: {} {}", + workbench_id.to_owned()[..num_chars].to_string(), + sketch_id.to_owned()[..num_chars].to_string() + ) + } + Operation::CreateRealSketch { + sketch_id, + real_sketch, + } => { + let points_str: String = real_sketch + .points + .iter() + .sorted_by_key(|p| p.0) + .map(|p| format!("{:?}", p)) + .collect(); + format!( + "CreateRealSketch: {} {:?}", + sketch_id.to_owned()[..num_chars].to_string(), + points_str + ) + } + + Operation::CreateFace { + workbench_id, + sketch_id, + face, + } => { + format!( + "CreateFace: {} {} {:?}", + workbench_id.to_owned()[..num_chars].to_string(), + sketch_id.to_owned()[..num_chars].to_string(), + face + ) + } + Operation::CreateTruckFace { + workbench_id, + solid_id, + face, + } => { + let mut lengths = vec![]; + for boundary in face.boundaries().iter() { + lengths.push(boundary.len()); + } + format!( + "CreateTruckFace: {} {} lengths: {:?}", + workbench_id.to_owned()[..num_chars].to_string(), + solid_id.to_owned()[..num_chars].to_string(), + lengths + ) + } + Operation::CreateExtrusion { + nonce, + workbench_id, + } => format!( + "CreateExtrusion: {} {}", + workbench_id.to_owned()[..num_chars].to_string(), + nonce + ), + Operation::SetExtrusionName { extrusion_id, name } => { + format!( + "SetExtrusionName: {} '{}'", + extrusion_id.to_owned()[..num_chars].to_string(), + name + ) + } + Operation::SetExtrusionSketch { + extrusion_id, + sketch_id, + } => { + format!( + "SetExtrusionSketch: {} {}", + extrusion_id.to_owned()[..num_chars].to_string(), + sketch_id.to_owned()[..num_chars].to_string() + ) + } + Operation::SetExtrusionHandles { + extrusion_id, + handles, + } => { + let mut click_str = String::new(); + for sha in handles { + click_str.push_str(&format!("{} ", sha.to_owned()[..num_chars].to_string())); + } + format!( + "SetExtrusionClicks: {} {}", + extrusion_id.to_owned()[..num_chars].to_string(), + click_str + ) + } + Operation::SetExtrusionDepth { + extrusion_id, + depth, + } => { + format!( + "SetExtrusionDepth: {} {}", + extrusion_id.to_owned()[..num_chars].to_string(), + depth + ) + } + Operation::SetExtrusionFaces { + extrusion_id, + faces, + } => { + let mut face_str = String::new(); + for sha in faces { + face_str.push_str(&format!("{} ", sha)); + } + format!( + "SetExtrusionFaces: {} {}", + extrusion_id.to_owned()[..num_chars].to_string(), + face_str + ) + } + Operation::CreateSolid { nonce, solid } => { + let mut mesh = solid.truck_solid.triangulation(0.1).to_polygon(); + // mesh.put_together_same_attrs(0.1); + format!( + "CreateSolid: {nonce} Volume: {:?}", + mesh.volume(), + // solid.truck_solid.display(SDF::ShellsList { + // shell_format: ShDF::FacesList { + // face_format: FDF::LoopsList { + // wire_format: WDF::VerticesList { + // vertex_format: VDF::AsPoint + // } + // } + // } + // }) + ) + } + Operation::FuseSolids { solid1, solid2 } => { + format!( + "FuseSolids: {} {}", + solid1.to_owned()[..num_chars].to_string(), + solid2.to_owned()[..num_chars].to_string() + ) + } + Operation::DeleteSolid { solid_id } => { + format!( + "DeleteSolid: {}", + solid_id.to_owned()[..num_chars].to_string() + ) + } + } + } +} + +impl std::fmt::Display for Operation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &self.pretty_print()) + } +} diff --git a/packages/cadmium/src/sketch/mod.rs b/packages/cadmium/src/sketch/mod.rs index 84f733e3..2d71484e 100644 --- a/packages/cadmium/src/sketch/mod.rs +++ b/packages/cadmium/src/sketch/mod.rs @@ -346,6 +346,15 @@ impl Sketch { id } + pub fn add_or_get_point(&mut self, x: f64, y: f64) -> u64 { + for (id, point) in self.points.iter() { + if (point.x - x).abs() < 1e-6 && (point.y - y).abs() < 1e-6 { + return *id; + } + } + self.add_point(x, y) + } + pub fn add_hidden_point(&mut self, x: f64, y: f64) -> u64 { let id = self.highest_point_id + 1; self.points.insert(id, Point2::new_hidden(x, y)); @@ -474,8 +483,8 @@ impl Sketch { } pub fn add_line_segment(&mut self, x0: f64, y0: f64, x1: f64, y1: f64) -> u64 { - let id0 = self.add_point(x0, y0); - let id1 = self.add_point(x1, y1); + let id0 = self.add_or_get_point(x0, y0); + let id1 = self.add_or_get_point(x1, y1); let l = Line2 { start: id0, end: id1, diff --git a/packages/cadmium/src/step.rs b/packages/cadmium/src/step.rs index f9802c78..25b5a174 100644 --- a/packages/cadmium/src/step.rs +++ b/packages/cadmium/src/step.rs @@ -1,11 +1,10 @@ - use serde::{Deserialize, Serialize}; use tsify::Tsify; use wasm_bindgen::prelude::*; use crate::archetypes::{Plane, PlaneDescription, Point3, Vector3}; -use crate::sketch::Sketch; use crate::extrusion::Extrusion; +use crate::sketch::Sketch; #[derive(Tsify, Debug, Serialize, Deserialize)] #[serde(tag = "type")] @@ -78,6 +77,20 @@ impl Step { } } + pub fn new_sketch_unbound(name: &str, sketch_id: u64) -> Self { + Step { + name: name.to_owned(), + unique_id: format!("Sketch-{}", sketch_id), + suppressed: false, + data: StepData::Sketch { + plane_description: PlaneDescription::None, + width: 1.25, + height: 0.75, + sketch: Sketch::new(), + }, + } + } + pub fn new_sketch_on_solid_face( name: &str, solid_id: &str, diff --git a/packages/cadmium/src/workbench.rs b/packages/cadmium/src/workbench.rs index 58649e20..e4417681 100644 --- a/packages/cadmium/src/workbench.rs +++ b/packages/cadmium/src/workbench.rs @@ -61,6 +61,25 @@ impl Workbench { None } + pub fn step_id_from_unique_id(&self, unique_id: &str) -> Option { + for (i, step) in self.history.iter().enumerate() { + if step.unique_id == unique_id { + return Some(i as u64); + } + } + None + } + + pub fn add_blank_sketch(&mut self, name: &str) -> String { + let counter = self.step_counters.get_mut("Sketch").unwrap(); + let new_step = Step::new_sketch_unbound(name, *counter); + let new_step_id = new_step.unique_id.clone(); + self.history.push(new_step); + *counter += 1; + + new_step_id + } + pub fn update_step_data(&mut self, step_id: &str, new_step_data: StepData) { let mut index = 0; for step in self.history.iter() { @@ -276,6 +295,9 @@ impl Workbench { plane_description, sketch, } => match plane_description { + PlaneDescription::None => { + println!("Sketch {} has no plane", step.name); + } PlaneDescription::PlaneId(plane_id) => { if plane_id == "" { println!("Sketch {} has no plane", step.name);