diff --git a/rust/kcl-lib/src/execution/artifact.rs b/rust/kcl-lib/src/execution/artifact.rs index 7b24a94702a..7dd3dffc494 100644 --- a/rust/kcl-lib/src/execution/artifact.rs +++ b/rust/kcl-lib/src/execution/artifact.rs @@ -12,7 +12,7 @@ use uuid::Uuid; use crate::{ KclError, NodePath, SourceRange, errors::KclErrorDetails, - execution::ArtifactId, + execution::{ArtifactId, id_generator::EngineIdGenerator}, parsing::ast::types::{Node, Program}, }; @@ -594,6 +594,8 @@ pub(super) fn build_artifact_graph( fill_in_node_paths(exec_artifact, ast, item_count); } + let mut id_generator = EngineIdGenerator::new(Uuid::new_v4()); + for artifact_command in artifact_commands { if let ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) = artifact_command.command { current_plane_id = Some(entity_id); @@ -610,6 +612,9 @@ pub(super) fn build_artifact_graph( if let ModelingCmd::SketchModeDisable(_) = artifact_command.command { current_plane_id = None; } + if let ModelingCmd::StartPath(_) = artifact_command.command { + id_generator = EngineIdGenerator::new(artifact_command.cmd_id); + } let flattened_responses = flatten_modeling_command_responses(responses); let artifact_updates = artifacts_to_update( @@ -620,7 +625,13 @@ pub(super) fn build_artifact_graph( ast, item_count, exec_artifacts, + &id_generator, )?; + + if let ModelingCmd::ExtendPath(_) = artifact_command.command { + id_generator.next_edge(); + } + for artifact in artifact_updates { // Merge with existing artifacts. merge_artifact_into_map(&mut map, artifact); @@ -741,6 +752,7 @@ fn artifacts_to_update( ast: &Node, cached_body_items: usize, exec_artifacts: &IndexMap, + id_generator: &EngineIdGenerator, ) -> Result, KclError> { let uuid = artifact_command.cmd_id; let response = responses.get(&uuid); @@ -881,8 +893,9 @@ fn artifacts_to_update( ), }); let mut return_arr = Vec::new(); + let curve_id = id_generator.get_curve_id(); return_arr.push(Artifact::Segment(Segment { - id, + id: curve_id, path_id, surface_id: None, edge_ids: Vec::new(), @@ -893,7 +906,7 @@ fn artifacts_to_update( let path = artifacts.get(&path_id); if let Some(Artifact::Path(path)) = path { let mut new_path = path.clone(); - new_path.seg_ids = vec![id]; + new_path.seg_ids = vec![curve_id]; return_arr.push(Artifact::Path(new_path)); } if let Some(OkModelingCmdResponse::ClosePath(close_path)) = response { @@ -997,14 +1010,15 @@ fn artifacts_to_update( }; let mut return_arr = Vec::new(); let target = ArtifactId::from(target); - return_arr.push(Artifact::Sweep(Sweep { + let mut sweep = Sweep { id, sub_type, path_id: target, surface_ids: Vec::new(), edge_ids: Vec::new(), code_ref, - })); + }; + let path = artifacts.get(&target); if let Some(Artifact::Path(path)) = path { let mut new_path = path.clone(); @@ -1017,7 +1031,111 @@ fn artifacts_to_update( inner_path_artifact.sweep_id = Some(id); return_arr.push(Artifact::Path(inner_path_artifact)) } + + if let ModelingCmd::Extrude(kcmc::Extrude { .. }) = cmd { + // Note: target.0 === path.id + let mut id_generator = EngineIdGenerator::new(target.0); + let sweep_id = id; // command id is the sweep id + + let start_cap_id = id_generator.get_start_cap_id(); + let end_cap_id = id_generator.get_end_cap_id(); + + // Go through segments and add walls, opposite and adj edges + for index in 0..path.seg_ids.len() - 1 { + let face_id = id_generator.get_face_id(); + let next_face_id = id_generator.get_next_face_id(path.seg_ids.len() as u32); + let curve_id = id_generator.get_curve_id(); + let opposite_edge_id = id_generator.get_opposite_edge_id(); + + let adjacent_edge_id = id_generator.get_adjacent_edge_id(); + + let wall_artifact = Artifact::Wall(Wall { + id: face_id, + seg_id: curve_id, + edge_cut_edge_ids: vec![opposite_edge_id, adjacent_edge_id], + sweep_id, + path_ids: Vec::new(), + face_code_ref: find_sketch_on_face_code_ref(exec_artifacts, face_id), + cmd_id: artifact_command.cmd_id, + }); + return_arr.push(wall_artifact); + sweep.surface_ids.push(face_id); + + return_arr.push(Artifact::SweepEdge(SweepEdge { + id: opposite_edge_id, + sub_type: SweepEdgeSubType::Opposite, + seg_id: curve_id, + cmd_id: artifact_command.cmd_id, + index, + sweep_id, + common_surface_ids: vec![face_id, end_cap_id], + })); + + return_arr.push(Artifact::SweepEdge(SweepEdge { + id: adjacent_edge_id, + sub_type: SweepEdgeSubType::Adjacent, + seg_id: curve_id, + cmd_id: artifact_command.cmd_id, + index, + sweep_id, + common_surface_ids: vec![face_id, next_face_id], + })); + + // Add opposite and adjacent edges to segment, sweep and wall. + if let Some(Artifact::Segment(segment)) = artifacts.get(&curve_id) { + let mut new_segment = segment.clone(); + new_segment.edge_ids = vec![opposite_edge_id, adjacent_edge_id]; + new_segment.common_surface_ids = vec![face_id, end_cap_id]; + return_arr.push(Artifact::Segment(new_segment)); + } + + sweep.edge_ids.push(opposite_edge_id); + sweep.edge_ids.push(adjacent_edge_id); + + // TODO is this ever used? + // if let Some(artifact) = artifacts.get(&edge_id) { + // match artifact { + // Artifact::SweepEdge(_sweep_edge) => { + + // // let mut new_sweep_edge = sweep_edge.clone(); + // // new_sweep_edge.common_surface_ids = + // // original_info.faces.iter().map(|face| ArtifactId::new(*face)).collect(); + // // return_arr.push(Artifact::SweepEdge(new_sweep_edge)); + // } + // _ => {} + // }; + // }; + + id_generator.next_edge(); + } + + // Add end caps + + return_arr.push(Artifact::Cap(Cap { + id: start_cap_id, + sub_type: CapSubType::Start, + edge_cut_edge_ids: Vec::new(), + sweep_id, + path_ids: Vec::new(), + face_code_ref: find_sketch_on_face_code_ref(exec_artifacts, start_cap_id), + cmd_id: artifact_command.cmd_id, + })); + sweep.surface_ids.push(start_cap_id); + + return_arr.push(Artifact::Cap(Cap { + id: end_cap_id, + sub_type: CapSubType::End, + edge_cut_edge_ids: Vec::new(), + sweep_id, + path_ids: Vec::new(), + face_code_ref: find_sketch_on_face_code_ref(exec_artifacts, end_cap_id), + cmd_id: artifact_command.cmd_id, + })); + sweep.surface_ids.push(end_cap_id); + } } + return_arr.push(Artifact::Sweep(sweep)); + return Ok(return_arr); } ModelingCmd::Loft(loft_cmd) => { @@ -1465,3 +1583,25 @@ fn artifacts_to_update( Ok(Vec::new()) } + +// Find sketch_on_face_code_ref (code from Solid3dGetExtrusionFaceInfo handling) +fn find_sketch_on_face_code_ref(exec_artifacts: &IndexMap, face_id: ArtifactId) -> CodeRef { + let extra_artifact = exec_artifacts.values().find(|a| { + if let Artifact::StartSketchOnFace(s) = a { + s.face_id == face_id + } else if let Artifact::StartSketchOnPlane(s) = a { + s.plane_id == face_id + } else { + false + } + }); + let sketch_on_face_code_ref = extra_artifact + .and_then(|a| match a { + Artifact::StartSketchOnFace(s) => Some(s.code_ref.clone()), + Artifact::StartSketchOnPlane(s) => Some(s.code_ref.clone()), + _ => None, + }) + // TODO: If we didn't find it, it's probably a bug. + .unwrap_or_default(); + sketch_on_face_code_ref +} diff --git a/rust/kcl-lib/src/execution/id_generator.rs b/rust/kcl-lib/src/execution/id_generator.rs index 4fb1d089008..48baaa4aaa9 100644 --- a/rust/kcl-lib/src/execution/id_generator.rs +++ b/rust/kcl-lib/src/execution/id_generator.rs @@ -1,6 +1,6 @@ //! A generator for ArtifactIds that can be stable across executions. -use crate::execution::ModuleId; +use crate::execution::{ArtifactId, ModuleId}; const NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("8bda3118-75eb-58c7-a866-bef1dcb495e7"); @@ -32,6 +32,71 @@ impl IdGenerator { } } +const ENGINE_NAMESPACE_KCL: uuid::Uuid = uuid::uuid!("22b85cda-1c8d-57c4-88b5-3fd71846f31e"); +pub struct EngineIdGenerator { + base: uuid::Uuid, // this is the sketch id, aka ClosePath's path_id, aka object uuid in engine + path_index: u32, // zero based index of the segment/curve/edge in the path. +} + +impl EngineIdGenerator { + pub fn new(base: uuid::Uuid) -> Self { + Self { base, path_index: 0 } + } + + pub fn next_edge(&mut self) { + self.path_index += 1; + } + + // aka edge/segment id + pub fn get_curve_id(&self) -> ArtifactId { + self.generate_path_id("") // "path_0" + } + + // aka wall_id + pub fn get_face_id(&self) -> ArtifactId { + self.generate_path_id("face") // "path_0_face" + } + + pub fn get_next_face_id(&self, num_segments: u32) -> ArtifactId { + let index = (self.path_index + 1) % num_segments; + let path_modifier = format!("path_{}", index); + let modifier = format!("{}_{}", path_modifier, "face"); + self.generate_id(&modifier) + } + + pub fn get_opposite_edge_id(&self) -> ArtifactId { + self.generate_path_id("opp") // "path_0_opp" + } + + pub fn get_adjacent_edge_id(&self) -> ArtifactId { + self.generate_path_id("adj") // "path_0_adj" + } + + pub fn get_start_cap_id(&self) -> ArtifactId { + self.generate_id("face_bottom") // "path_0_face_bottom" + } + + pub fn get_end_cap_id(&self) -> ArtifactId { + self.generate_id("face_top") // "path_0_face_bottom" + } + + fn generate_path_id(&self, suffix: &str) -> ArtifactId { + let path_modifier = format!("path_{}", self.path_index); + let modifier = if suffix.is_empty() { + path_modifier + } else { + format!("{}_{}", path_modifier, suffix) + }; + self.generate_id(&modifier) + } + + fn generate_id(&self, modifier: &str) -> ArtifactId { + let name = format!("{}_{}", self.base, modifier); + let uuid = uuid::Uuid::new_v5(&ENGINE_NAMESPACE_KCL, name.as_bytes()); + ArtifactId::new(uuid) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/kcl-lib/src/execution/mod.rs b/rust/kcl-lib/src/execution/mod.rs index ded53bf9877..f8b3e7c8f9a 100644 --- a/rust/kcl-lib/src/execution/mod.rs +++ b/rust/kcl-lib/src/execution/mod.rs @@ -11,7 +11,7 @@ pub use cache::{bust_cache, clear_mem_cache}; pub use cad_op::Group; pub use cad_op::Operation; pub use geometry::*; -pub use id_generator::IdGenerator; +pub use id_generator::{EngineIdGenerator, IdGenerator}; pub(crate) use import::PreImportedGeometry; use indexmap::IndexMap; pub use kcl_value::{KclObjectFields, KclValue}; diff --git a/rust/kcl-lib/src/std/clone.rs b/rust/kcl-lib/src/std/clone.rs index 505f0ba4bc0..c802849747c 100644 --- a/rust/kcl-lib/src/std/clone.rs +++ b/rust/kcl-lib/src/std/clone.rs @@ -155,6 +155,7 @@ async fn fix_tags_and_references( exec_state, args, None, + false, ) .await?; diff --git a/rust/kcl-lib/src/std/extrude.rs b/rust/kcl-lib/src/std/extrude.rs index 76aa000dda1..f8cf76b1fd9 100644 --- a/rust/kcl-lib/src/std/extrude.rs +++ b/rust/kcl-lib/src/std/extrude.rs @@ -22,7 +22,8 @@ use super::{DEFAULT_TOLERANCE_MM, args::TyF64, utils::point_to_mm}; use crate::{ errors::{KclError, KclErrorDetails}, execution::{ - ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path, Sketch, SketchSurface, Solid, + ArtifactId, EngineIdGenerator, ExecState, ExtrudeSurface, GeoMeta, KclValue, ModelingCmdMeta, Path, Sketch, + SketchSurface, Solid, types::{PrimitiveType, RuntimeType}, }, parsing::ast::types::TagNode, @@ -313,6 +314,7 @@ async fn inner_extrude( exec_state, &args, None, + true, ) .await?, ); @@ -337,6 +339,7 @@ pub(crate) async fn do_post_extrude<'a>( exec_state: &mut ExecState, args: &Args, edge_id: Option, + generate_predictive_ids: bool, ) -> Result { // Bring the object to the front of the scene. // See: https://github.com/KittyCAD/modeling-app/issues/806 @@ -347,75 +350,99 @@ pub(crate) async fn do_post_extrude<'a>( ) .await?; - let any_edge_id = if let Some(edge_id) = sketch.mirror { - edge_id - } else if let Some(id) = edge_id { - id - } else { - // The "get extrusion face info" API call requires *any* edge on the sketch being extruded. - // So, let's just use the first one. - let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else { - return Err(KclError::new_type(KclErrorDetails::new( - "Expected a non-empty sketch".to_owned(), - vec![args.source_range], - ))); - }; - any_edge_id - }; + let mut face_id_map: HashMap> = Default::default(); + let start_cap_id: Option; + let end_cap_id: Option; let mut sketch = sketch.clone(); sketch.is_closed = true; - // If we were sketching on a face, we need the original face id. - if let SketchSurface::Face(ref face) = sketch.on { - // If we are creating a new body we need to preserve its new id. - if extrude_method != ExtrudeMethod::New { - sketch.id = face.solid.sketch.id; + if generate_predictive_ids { + let mut id_generator = EngineIdGenerator::new(sketch.id); + start_cap_id = Some(id_generator.get_start_cap_id().into()); + end_cap_id = Some(id_generator.get_end_cap_id().into()); + + // TODO Note we're skipping the last one, because 3 segments have 4 paths in sketch. + for _ in 0..sketch.paths.len() - 1 { + face_id_map.insert( + id_generator.get_curve_id().into(), + Some(id_generator.get_face_id().into()), + ); + id_generator.next_edge(); } - } + } else { + let any_edge_id = if let Some(edge_id) = sketch.mirror { + edge_id + } else if let Some(id) = edge_id { + id + } else { + // The "get extrusion face info" API call requires *any* edge on the sketch being extruded. + // So, let's just use the first one. - let solid3d_info = exec_state - .send_modeling_cmd( - args.into(), - ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo { - edge_id: any_edge_id, - object_id: sketch.id, - }), - ) - .await?; + EngineIdGenerator::new(sketch.id).get_curve_id().into() + // let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else { + // return Err(KclError::new_type(KclErrorDetails::new( + // "Expected a non-empty sketch".to_owned(), + // vec![args.source_range], + // ))); + // }; + // any_edge_id + }; - let face_infos = if let OkWebSocketResponseData::Modeling { - modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data), - } = solid3d_info - { - data.faces - } else { - vec![] - }; + // If we were sketching on a face, we need the original face id. + if let SketchSurface::Face(ref face) = sketch.on { + // If we are creating a new body we need to preserve its new id. + if extrude_method != ExtrudeMethod::New { + sketch.id = face.solid.sketch.id; + } + } + + let solid3d_info = exec_state + .send_modeling_cmd( + args.into(), + ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo { + edge_id: any_edge_id, + object_id: sketch.id, + }), + ) + .await?; + + #[cfg(target_arch = "wasm32")] + web_sys::console::warn_1(&format!("*** solid3d_info: {solid3d_info:#?}").into()); - // Only do this if we need the artifact graph. - #[cfg(feature = "artifact-graph")] - { - // Getting the ids of a sectional sweep does not work well and we cannot guarantee that - // any of these call will not just fail. - if !sectional { - exec_state - .batch_modeling_cmd( - args.into(), - ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo { - object_id: sketch.id, - edge_id: any_edge_id, - }), - ) - .await?; + let face_infos = if let OkWebSocketResponseData::Modeling { + modeling_response: OkModelingCmdResponse::Solid3dGetExtrusionFaceInfo(data), + } = solid3d_info + { + data.faces + } else { + vec![] + }; + + // Only do this if we need the artifact graph. + #[cfg(feature = "artifact-graph")] + { + // Getting the ids of a sectional sweep does not work well and we cannot guarantee that + // any of these call will not just fail. + if !sectional { + exec_state + .batch_modeling_cmd( + args.into(), + ModelingCmd::from(mcmd::Solid3dGetAdjacencyInfo { + object_id: sketch.id, + edge_id: any_edge_id, + }), + ) + .await?; + } } + + let faces = analyze_faces(exec_state, args, face_infos).await; + face_id_map = faces.sides; + start_cap_id = faces.start_cap_id; + end_cap_id = faces.end_cap_id; } - let Faces { - sides: face_id_map, - start_cap_id, - end_cap_id, - } = analyze_faces(exec_state, args, face_infos).await; // Iterate over the sketch.value array and add face_id to GeoMeta let no_engine_commands = args.ctx.no_engine_commands().await; let mut new_value: Vec = Vec::with_capacity(sketch.paths.len() + sketch.inner_paths.len() + 2); diff --git a/rust/kcl-lib/src/std/loft.rs b/rust/kcl-lib/src/std/loft.rs index ec830dcbcde..74014356bf1 100644 --- a/rust/kcl-lib/src/std/loft.rs +++ b/rust/kcl-lib/src/std/loft.rs @@ -104,6 +104,7 @@ async fn inner_loft( exec_state, &args, None, + false, ) .await?, )) diff --git a/rust/kcl-lib/src/std/revolve.rs b/rust/kcl-lib/src/std/revolve.rs index dc032f2dbc9..bcd04a28f5c 100644 --- a/rust/kcl-lib/src/std/revolve.rs +++ b/rust/kcl-lib/src/std/revolve.rs @@ -208,6 +208,7 @@ async fn inner_revolve( exec_state, &args, edge_id, + false, ) .await?, ); diff --git a/rust/kcl-lib/src/std/sweep.rs b/rust/kcl-lib/src/std/sweep.rs index 8e0d19fe6b5..8cef87d761e 100644 --- a/rust/kcl-lib/src/std/sweep.rs +++ b/rust/kcl-lib/src/std/sweep.rs @@ -108,6 +108,7 @@ async fn inner_sweep( exec_state, &args, None, + false, ) .await?, );