2025-01-17 14:34:36 -05:00
use fnv ::FnvHashMap ;
use indexmap ::IndexMap ;
use kittycad_modeling_cmds ::{
self as kcmc ,
ok_response ::OkModelingCmdResponse ,
shared ::ExtrusionFaceCapType ,
websocket ::{ BatchResponse , OkWebSocketResponseData , WebSocketResponse } ,
2025-03-13 21:59:39 -07:00
EnableSketchMode , ModelingCmd ,
2025-01-17 14:34:36 -05:00
} ;
2025-04-29 15:17:41 -04:00
use serde ::{ ser ::SerializeSeq , Serialize } ;
2025-01-08 20:02:30 -05:00
use uuid ::Uuid ;
2025-01-17 14:34:36 -05:00
use crate ::{
2025-02-04 20:34:56 -05:00
errors ::KclErrorDetails ,
2025-05-19 16:50:15 +12:00
execution ::ArtifactId ,
2025-01-17 14:34:36 -05:00
parsing ::ast ::types ::{ Node , Program } ,
2025-05-01 23:55:12 -04:00
KclError , NodePath , SourceRange ,
2025-01-17 14:34:36 -05:00
} ;
#[ cfg(test) ]
mod mermaid_tests ;
2025-01-08 20:02:30 -05:00
2025-05-22 17:48:19 -04:00
macro_rules ! internal_error {
( $range :expr , $( $rest :tt ) * ) = > { {
let message = format! ( $( $rest ) * ) ;
debug_assert! ( false , " {} " , & message ) ;
2025-06-02 13:30:57 -05:00
return Err ( KclError ::new_internal ( KclErrorDetails ::new ( message , vec! [ $range ] ) ) ) ;
2025-05-22 17:48:19 -04:00
} } ;
}
2025-01-08 20:02:30 -05:00
/// A command that may create or update artifacts on the TS side. Because
/// engine commands are batched, we don't have the response yet when these are
/// created.
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
2025-01-08 20:02:30 -05:00
#[ serde(rename_all = " camelCase " ) ]
pub struct ArtifactCommand {
/// Identifier of the command that can be matched with its response.
pub cmd_id : Uuid ,
pub range : SourceRange ,
/// The engine command. Each artifact command is backed by an engine
/// command. In the future, we may need to send information to the TS side
/// without an engine command, in which case, we would make this field
/// optional.
pub command : ModelingCmd ,
}
2025-04-29 06:38:52 -07:00
impl PartialOrd for ArtifactCommand {
fn partial_cmp ( & self , other : & Self ) -> Option < std ::cmp ::Ordering > {
// Order by the source range.
let range = self . range . cmp ( & other . range ) ;
if range ! = std ::cmp ::Ordering ::Equal {
return Some ( range ) ;
}
#[ cfg(test) ]
{
// If the ranges are equal, order by the serde variant.
Some (
crate ::variant_name ::variant_name ( & self . command )
. cmp ( & crate ::variant_name ::variant_name ( & other . command ) ) ,
)
}
#[ cfg(not(test)) ]
self . cmd_id . partial_cmp ( & other . cmd_id )
}
}
2025-01-17 14:34:36 -05:00
pub type DummyPathToNode = Vec < ( ) > ;
fn serialize_dummy_path_to_node < S > ( _path_to_node : & DummyPathToNode , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : serde ::Serializer ,
{
// Always output an empty array, for now.
let seq = serializer . serialize_seq ( Some ( 0 ) ) ? ;
seq . end ( )
}
2025-05-15 19:18:03 -04:00
#[ derive(Debug, Clone, Default, Serialize, PartialEq, Eq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CodeRef {
pub range : SourceRange ,
2025-05-01 23:55:12 -04:00
pub node_path : NodePath ,
2025-01-17 14:34:36 -05:00
// TODO: We should implement this in Rust.
#[ serde(default, serialize_with = " serialize_dummy_path_to_node " ) ]
#[ ts(type = " Array<[string | number, string]> " ) ]
pub path_to_node : DummyPathToNode ,
}
2025-02-28 16:06:26 -05:00
impl CodeRef {
pub fn placeholder ( range : SourceRange ) -> Self {
Self {
range ,
2025-05-01 23:55:12 -04:00
node_path : Default ::default ( ) ,
2025-02-28 16:06:26 -05:00
path_to_node : Vec ::new ( ) ,
}
}
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-04-11 01:16:29 -04:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct CompositeSolid {
pub id : ArtifactId ,
pub sub_type : CompositeSolidSubType ,
/// Constituent solids of the composite solid.
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub solid_ids : Vec < ArtifactId > ,
/// Tool solids used for asymmetric operations like subtract.
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub tool_ids : Vec < ArtifactId > ,
pub code_ref : CodeRef ,
2025-05-07 08:25:12 +10:00
/// This is the ID of the composite solid that this is part of, if any, as a
/// composite solid can be used as input for another composite solid.
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub composite_solid_id : Option < ArtifactId > ,
2025-04-11 01:16:29 -04:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS) ]
2025-04-11 01:16:29 -04:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum CompositeSolidSubType {
Intersect ,
Subtract ,
Union ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Plane {
pub id : ArtifactId ,
pub path_ids : Vec < ArtifactId > ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Path {
pub id : ArtifactId ,
pub plane_id : ArtifactId ,
pub seg_ids : Vec < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub sweep_id : Option < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub solid2d_id : Option < ArtifactId > ,
pub code_ref : CodeRef ,
2025-05-07 08:25:12 +10:00
/// This is the ID of the composite solid that this is part of, if any, as
/// this can be used as input for another composite solid.
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub composite_solid_id : Option < ArtifactId > ,
2025-01-17 14:34:36 -05:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Segment {
pub id : ArtifactId ,
pub path_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub surface_id : Option < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub edge_ids : Vec < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub edge_cut_id : Option < ArtifactId > ,
pub code_ref : CodeRef ,
2025-04-26 14:00:28 +10:00
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub common_surface_ids : Vec < ArtifactId > ,
2025-01-17 14:34:36 -05:00
}
/// A sweep is a more generic term for extrude, revolve, loft, and sweep.
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Sweep {
pub id : ArtifactId ,
pub sub_type : SweepSubType ,
pub path_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub surface_ids : Vec < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub edge_ids : Vec < ArtifactId > ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum SweepSubType {
Extrusion ,
Revolve ,
2025-02-25 23:04:10 -05:00
RevolveAboutEdge ,
2025-01-17 14:34:36 -05:00
Loft ,
Sweep ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Solid2d {
pub id : ArtifactId ,
pub path_id : ArtifactId ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-02-28 16:06:26 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct StartSketchOnFace {
pub id : ArtifactId ,
pub face_id : ArtifactId ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-02-28 16:06:26 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct StartSketchOnPlane {
pub id : ArtifactId ,
pub plane_id : ArtifactId ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Wall {
pub id : ArtifactId ,
pub seg_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub edge_cut_edge_ids : Vec < ArtifactId > ,
pub sweep_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub path_ids : Vec < ArtifactId > ,
2025-02-15 00:57:04 +11:00
/// This is for the sketch-on-face plane, not for the wall itself. Traverse
/// to the extrude and/or segment to get the wall's code_ref.
pub face_code_ref : CodeRef ,
2025-04-30 00:34:35 -04:00
/// The command ID that got the data for this wall. Used for stable sorting.
2025-04-29 06:38:52 -07:00
pub cmd_id : uuid ::Uuid ,
2025-01-17 14:34:36 -05:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Cap {
pub id : ArtifactId ,
pub sub_type : CapSubType ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub edge_cut_edge_ids : Vec < ArtifactId > ,
pub sweep_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub path_ids : Vec < ArtifactId > ,
2025-02-15 00:57:04 +11:00
/// This is for the sketch-on-face plane, not for the cap itself. Traverse
/// to the extrude and/or segment to get the cap's code_ref.
pub face_code_ref : CodeRef ,
2025-04-30 00:34:35 -04:00
/// The command ID that got the data for this cap. Used for stable sorting.
pub cmd_id : uuid ::Uuid ,
2025-01-17 14:34:36 -05:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Copy, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum CapSubType {
Start ,
End ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct SweepEdge {
pub id : ArtifactId ,
pub sub_type : SweepEdgeSubType ,
pub seg_id : ArtifactId ,
2025-04-29 06:38:52 -07:00
pub cmd_id : uuid ::Uuid ,
2025-05-13 21:07:24 -07:00
// This is only used for sorting, not for the actual artifact.
#[ serde(skip) ]
pub index : usize ,
2025-01-17 14:34:36 -05:00
pub sweep_id : ArtifactId ,
2025-04-26 14:00:28 +10:00
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub common_surface_ids : Vec < ArtifactId > ,
2025-01-17 14:34:36 -05:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Copy, Serialize, PartialEq, Ord, PartialOrd, Eq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum SweepEdgeSubType {
Opposite ,
Adjacent ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct EdgeCut {
pub id : ArtifactId ,
pub sub_type : EdgeCutSubType ,
pub consumed_edge_id : ArtifactId ,
#[ serde(default, skip_serializing_if = " Vec::is_empty " ) ]
pub edge_ids : Vec < ArtifactId > ,
#[ serde(default, skip_serializing_if = " Option::is_none " ) ]
pub surface_id : Option < ArtifactId > ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Copy, Serialize, PartialEq, PartialOrd, Ord, Eq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub enum EdgeCutSubType {
Fillet ,
Chamfer ,
}
impl From < kcmc ::shared ::CutType > for EdgeCutSubType {
fn from ( cut_type : kcmc ::shared ::CutType ) -> Self {
match cut_type {
kcmc ::shared ::CutType ::Fillet = > EdgeCutSubType ::Fillet ,
kcmc ::shared ::CutType ::Chamfer = > EdgeCutSubType ::Chamfer ,
}
}
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
2025-01-08 20:02:30 -05:00
#[ serde(rename_all = " camelCase " ) ]
2025-01-17 14:34:36 -05:00
pub struct EdgeCutEdge {
2025-01-08 20:02:30 -05:00
pub id : ArtifactId ,
2025-01-17 14:34:36 -05:00
pub edge_cut_id : ArtifactId ,
pub surface_id : ArtifactId ,
2025-01-08 20:02:30 -05:00
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-02-10 14:24:00 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct Helix {
pub id : ArtifactId ,
/// The axis of the helix. Currently this is always an edge ID, but we may
/// add axes to the graph.
pub axis_id : Option < ArtifactId > ,
pub code_ref : CodeRef ,
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(tag = " type " , rename_all = " camelCase " ) ]
pub enum Artifact {
2025-04-11 01:16:29 -04:00
CompositeSolid ( CompositeSolid ) ,
2025-01-17 14:34:36 -05:00
Plane ( Plane ) ,
Path ( Path ) ,
Segment ( Segment ) ,
Solid2d ( Solid2d ) ,
2025-02-28 16:06:26 -05:00
StartSketchOnFace ( StartSketchOnFace ) ,
StartSketchOnPlane ( StartSketchOnPlane ) ,
2025-01-17 14:34:36 -05:00
Sweep ( Sweep ) ,
Wall ( Wall ) ,
Cap ( Cap ) ,
SweepEdge ( SweepEdge ) ,
EdgeCut ( EdgeCut ) ,
EdgeCutEdge ( EdgeCutEdge ) ,
2025-02-10 14:24:00 -05:00
Helix ( Helix ) ,
2025-01-17 14:34:36 -05:00
}
2025-04-29 06:38:52 -07:00
impl Artifact {
pub ( crate ) fn rank ( & self ) -> u8 {
match self {
Artifact ::Plane ( _ ) = > 0 ,
Artifact ::StartSketchOnPlane ( _ ) = > 1 ,
Artifact ::StartSketchOnFace ( _ ) = > 2 ,
Artifact ::Path ( _ ) = > 3 ,
Artifact ::Segment ( _ ) = > 4 ,
Artifact ::Solid2d ( _ ) = > 5 ,
Artifact ::Sweep ( _ ) = > 6 ,
Artifact ::CompositeSolid ( _ ) = > 7 ,
Artifact ::Wall ( _ ) = > 8 ,
Artifact ::Cap ( Cap { sub_type , .. } ) if * sub_type = = CapSubType ::Start = > 9 ,
Artifact ::Cap ( Cap { sub_type , .. } ) if * sub_type = = CapSubType ::Start = > 10 ,
Artifact ::Cap ( _ ) = > 11 ,
Artifact ::SweepEdge ( SweepEdge { sub_type , .. } ) if * sub_type = = SweepEdgeSubType ::Adjacent = > 12 ,
Artifact ::SweepEdge ( SweepEdge { sub_type , .. } ) if * sub_type = = SweepEdgeSubType ::Opposite = > 13 ,
Artifact ::SweepEdge ( _ ) = > 14 ,
Artifact ::EdgeCut ( _ ) = > 15 ,
Artifact ::EdgeCutEdge ( _ ) = > 16 ,
Artifact ::Helix ( _ ) = > 17 ,
}
}
}
impl PartialOrd for Artifact {
fn partial_cmp ( & self , other : & Self ) -> Option < std ::cmp ::Ordering > {
// The only thing we want to sort is if we have two sweep edges, we want
// to sort them by the sub_type.
match ( self , other ) {
( Artifact ::SweepEdge ( a ) , Artifact ::SweepEdge ( b ) ) = > {
if a . sub_type ! = b . sub_type {
return Some ( a . sub_type . cmp ( & b . sub_type ) ) ;
}
if a . sweep_id ! = b . sweep_id {
return Some ( a . sweep_id . cmp ( & b . sweep_id ) ) ;
}
if a . cmd_id ! = b . cmd_id {
return Some ( a . cmd_id . cmp ( & b . cmd_id ) ) ;
}
2025-05-13 21:07:24 -07:00
if a . index ! = b . index {
return Some ( a . index . cmp ( & b . index ) ) ;
}
2025-04-29 06:38:52 -07:00
Some ( a . id . cmp ( & b . id ) )
}
2025-04-29 09:51:52 -07:00
( Artifact ::EdgeCut ( a ) , Artifact ::EdgeCut ( b ) ) = > {
if a . code_ref . range ! = b . code_ref . range {
return Some ( a . code_ref . range . cmp ( & b . code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
( Artifact ::EdgeCutEdge ( a ) , Artifact ::EdgeCutEdge ( b ) ) = > Some ( a . edge_cut_id . cmp ( & b . edge_cut_id ) ) ,
2025-04-29 06:38:52 -07:00
( Artifact ::Sweep ( a ) , Artifact ::Sweep ( b ) ) = > {
if a . code_ref . range ! = b . code_ref . range {
return Some ( a . code_ref . range . cmp ( & b . code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the planes by their code_ref range.
( Artifact ::Plane ( a ) , Artifact ::Plane ( b ) ) = > {
if a . code_ref . range ! = b . code_ref . range {
return Some ( a . code_ref . range . cmp ( & b . code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the paths by their code_ref range.
( Artifact ::Path ( a ) , Artifact ::Path ( b ) ) = > {
if a . code_ref . range ! = b . code_ref . range {
return Some ( a . code_ref . range . cmp ( & b . code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the segments by their code_ref range.
( Artifact ::Segment ( a ) , Artifact ::Segment ( b ) ) = > {
if a . code_ref . range ! = b . code_ref . range {
return Some ( a . code_ref . range . cmp ( & b . code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the solid2d by their id.
( Artifact ::Solid2d ( a ) , Artifact ::Solid2d ( b ) ) = > {
if a . path_id ! = b . path_id {
return Some ( a . path_id . cmp ( & b . path_id ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the walls by their code_ref range.
( Artifact ::Wall ( a ) , Artifact ::Wall ( b ) ) = > {
if a . sweep_id ! = b . sweep_id {
return Some ( a . sweep_id . cmp ( & b . sweep_id ) ) ;
}
if a . cmd_id ! = b . cmd_id {
return Some ( a . cmd_id . cmp ( & b . cmd_id ) ) ;
}
if a . face_code_ref . range ! = b . face_code_ref . range {
return Some ( a . face_code_ref . range . cmp ( & b . face_code_ref . range ) ) ;
}
if a . seg_id ! = b . seg_id {
return Some ( a . seg_id . cmp ( & b . seg_id ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
// Sort the caps by their code_ref range.
( Artifact ::Cap ( a ) , Artifact ::Cap ( b ) ) = > {
if a . sub_type ! = b . sub_type {
return Some ( a . sub_type . cmp ( & b . sub_type ) ) ;
}
2025-04-30 00:34:35 -04:00
if a . cmd_id ! = b . cmd_id {
return Some ( a . cmd_id . cmp ( & b . cmd_id ) ) ;
}
2025-04-29 06:38:52 -07:00
if a . sweep_id ! = b . sweep_id {
return Some ( a . sweep_id . cmp ( & b . sweep_id ) ) ;
}
if a . face_code_ref . range ! = b . face_code_ref . range {
return Some ( a . face_code_ref . range . cmp ( & b . face_code_ref . range ) ) ;
}
Some ( a . id . cmp ( & b . id ) )
}
( Artifact ::CompositeSolid ( a ) , Artifact ::CompositeSolid ( b ) ) = > Some ( a . id . cmp ( & b . id ) ) ,
( Artifact ::StartSketchOnFace ( a ) , Artifact ::StartSketchOnFace ( b ) ) = > Some ( a . id . cmp ( & b . id ) ) ,
( Artifact ::StartSketchOnPlane ( a ) , Artifact ::StartSketchOnPlane ( b ) ) = > Some ( a . id . cmp ( & b . id ) ) ,
// Planes are first, then paths, then segments, then solids2ds, then sweeps, then
// walls, then caps, then sweep edges, then edge cuts, then edge cut edges, then
// helixes.
_ = > Some ( self . rank ( ) . cmp ( & other . rank ( ) ) ) ,
}
}
}
2025-01-17 14:34:36 -05:00
impl Artifact {
pub ( crate ) fn id ( & self ) -> ArtifactId {
match self {
2025-04-11 01:16:29 -04:00
Artifact ::CompositeSolid ( a ) = > a . id ,
2025-01-17 14:34:36 -05:00
Artifact ::Plane ( a ) = > a . id ,
Artifact ::Path ( a ) = > a . id ,
Artifact ::Segment ( a ) = > a . id ,
Artifact ::Solid2d ( a ) = > a . id ,
2025-02-28 16:06:26 -05:00
Artifact ::StartSketchOnFace ( a ) = > a . id ,
Artifact ::StartSketchOnPlane ( a ) = > a . id ,
2025-01-17 14:34:36 -05:00
Artifact ::Sweep ( a ) = > a . id ,
Artifact ::Wall ( a ) = > a . id ,
Artifact ::Cap ( a ) = > a . id ,
Artifact ::SweepEdge ( a ) = > a . id ,
Artifact ::EdgeCut ( a ) = > a . id ,
Artifact ::EdgeCutEdge ( a ) = > a . id ,
2025-02-10 14:24:00 -05:00
Artifact ::Helix ( a ) = > a . id ,
2025-01-17 14:34:36 -05:00
}
}
2025-05-15 19:18:03 -04:00
/// The [`CodeRef`] for the artifact itself. See also
/// [`Self::face_code_ref`].
pub fn code_ref ( & self ) -> Option < & CodeRef > {
2025-01-17 14:34:36 -05:00
match self {
2025-04-11 01:16:29 -04:00
Artifact ::CompositeSolid ( a ) = > Some ( & a . code_ref ) ,
2025-01-17 14:34:36 -05:00
Artifact ::Plane ( a ) = > Some ( & a . code_ref ) ,
Artifact ::Path ( a ) = > Some ( & a . code_ref ) ,
Artifact ::Segment ( a ) = > Some ( & a . code_ref ) ,
Artifact ::Solid2d ( _ ) = > None ,
2025-02-28 16:06:26 -05:00
Artifact ::StartSketchOnFace ( a ) = > Some ( & a . code_ref ) ,
Artifact ::StartSketchOnPlane ( a ) = > Some ( & a . code_ref ) ,
2025-01-17 14:34:36 -05:00
Artifact ::Sweep ( a ) = > Some ( & a . code_ref ) ,
Artifact ::Wall ( _ ) = > None ,
Artifact ::Cap ( _ ) = > None ,
Artifact ::SweepEdge ( _ ) = > None ,
Artifact ::EdgeCut ( a ) = > Some ( & a . code_ref ) ,
Artifact ::EdgeCutEdge ( _ ) = > None ,
2025-02-10 14:24:00 -05:00
Artifact ::Helix ( a ) = > Some ( & a . code_ref ) ,
2025-01-17 14:34:36 -05:00
}
}
2025-05-15 19:18:03 -04:00
/// The [`CodeRef`] referring to the face artifact that it's on, not the
/// artifact itself.
pub fn face_code_ref ( & self ) -> Option < & CodeRef > {
match self {
Artifact ::CompositeSolid ( _ )
| Artifact ::Plane ( _ )
| Artifact ::Path ( _ )
| Artifact ::Segment ( _ )
| Artifact ::Solid2d ( _ )
| Artifact ::StartSketchOnFace ( _ )
| Artifact ::StartSketchOnPlane ( _ )
| Artifact ::Sweep ( _ ) = > None ,
Artifact ::Wall ( a ) = > Some ( & a . face_code_ref ) ,
Artifact ::Cap ( a ) = > Some ( & a . face_code_ref ) ,
Artifact ::SweepEdge ( _ ) | Artifact ::EdgeCut ( _ ) | Artifact ::EdgeCutEdge ( _ ) | Artifact ::Helix ( _ ) = > None ,
}
}
2025-01-17 14:34:36 -05:00
/// Merge the new artifact into self. If it can't because it's a different
/// type, return the new artifact which should be used as a replacement.
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
match self {
2025-04-11 01:16:29 -04:00
Artifact ::CompositeSolid ( a ) = > a . merge ( new ) ,
2025-01-17 14:34:36 -05:00
Artifact ::Plane ( a ) = > a . merge ( new ) ,
Artifact ::Path ( a ) = > a . merge ( new ) ,
Artifact ::Segment ( a ) = > a . merge ( new ) ,
Artifact ::Solid2d ( _ ) = > Some ( new ) ,
Artifact ::StartSketchOnFace { .. } = > Some ( new ) ,
Artifact ::StartSketchOnPlane { .. } = > Some ( new ) ,
Artifact ::Sweep ( a ) = > a . merge ( new ) ,
Artifact ::Wall ( a ) = > a . merge ( new ) ,
Artifact ::Cap ( a ) = > a . merge ( new ) ,
Artifact ::SweepEdge ( _ ) = > Some ( new ) ,
Artifact ::EdgeCut ( a ) = > a . merge ( new ) ,
Artifact ::EdgeCutEdge ( _ ) = > Some ( new ) ,
2025-02-10 14:24:00 -05:00
Artifact ::Helix ( _ ) = > Some ( new ) ,
2025-01-17 14:34:36 -05:00
}
}
}
2025-04-11 01:16:29 -04:00
impl CompositeSolid {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::CompositeSolid ( new ) = new else {
return Some ( new ) ;
} ;
merge_ids ( & mut self . solid_ids , new . solid_ids ) ;
merge_ids ( & mut self . tool_ids , new . tool_ids ) ;
2025-05-07 08:25:12 +10:00
merge_opt_id ( & mut self . composite_solid_id , new . composite_solid_id ) ;
2025-04-11 01:16:29 -04:00
None
}
}
2025-01-17 14:34:36 -05:00
impl Plane {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Plane ( new ) = new else {
return Some ( new ) ;
} ;
merge_ids ( & mut self . path_ids , new . path_ids ) ;
None
}
}
impl Path {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Path ( new ) = new else {
return Some ( new ) ;
} ;
merge_opt_id ( & mut self . sweep_id , new . sweep_id ) ;
merge_ids ( & mut self . seg_ids , new . seg_ids ) ;
merge_opt_id ( & mut self . solid2d_id , new . solid2d_id ) ;
2025-05-07 08:25:12 +10:00
merge_opt_id ( & mut self . composite_solid_id , new . composite_solid_id ) ;
2025-01-17 14:34:36 -05:00
None
}
}
impl Segment {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Segment ( new ) = new else {
return Some ( new ) ;
} ;
merge_opt_id ( & mut self . surface_id , new . surface_id ) ;
merge_ids ( & mut self . edge_ids , new . edge_ids ) ;
merge_opt_id ( & mut self . edge_cut_id , new . edge_cut_id ) ;
2025-04-26 14:00:28 +10:00
merge_ids ( & mut self . common_surface_ids , new . common_surface_ids ) ;
2025-01-17 14:34:36 -05:00
None
}
}
impl Sweep {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Sweep ( new ) = new else {
return Some ( new ) ;
} ;
merge_ids ( & mut self . surface_ids , new . surface_ids ) ;
merge_ids ( & mut self . edge_ids , new . edge_ids ) ;
None
}
}
impl Wall {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Wall ( new ) = new else {
return Some ( new ) ;
} ;
merge_ids ( & mut self . edge_cut_edge_ids , new . edge_cut_edge_ids ) ;
merge_ids ( & mut self . path_ids , new . path_ids ) ;
None
}
}
impl Cap {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::Cap ( new ) = new else {
return Some ( new ) ;
} ;
merge_ids ( & mut self . edge_cut_edge_ids , new . edge_cut_edge_ids ) ;
merge_ids ( & mut self . path_ids , new . path_ids ) ;
None
}
}
impl EdgeCut {
fn merge ( & mut self , new : Artifact ) -> Option < Artifact > {
let Artifact ::EdgeCut ( new ) = new else {
return Some ( new ) ;
} ;
merge_opt_id ( & mut self . surface_id , new . surface_id ) ;
merge_ids ( & mut self . edge_ids , new . edge_ids ) ;
None
}
}
2025-04-29 15:17:41 -04:00
#[ derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS) ]
2025-01-17 14:34:36 -05:00
#[ ts(export_to = " Artifact.ts " ) ]
#[ serde(rename_all = " camelCase " ) ]
pub struct ArtifactGraph {
map : IndexMap < ArtifactId , Artifact > ,
}
2025-02-05 17:53:49 +13:00
impl ArtifactGraph {
pub fn len ( & self ) -> usize {
self . map . len ( )
}
2025-04-29 06:38:52 -07:00
2025-05-15 19:18:03 -04:00
pub fn is_empty ( & self ) -> bool {
self . map . is_empty ( )
}
pub fn values ( & self ) -> impl Iterator < Item = & Artifact > {
self . map . values ( )
}
/// Consume the artifact graph and return the map of artifacts.
fn into_map ( self ) -> IndexMap < ArtifactId , Artifact > {
self . map
}
2025-04-29 06:38:52 -07:00
/// Used to make the mermaid tests deterministic.
#[ cfg(test) ]
pub ( crate ) fn sort ( & mut self ) {
self . map
. sort_by ( | _ak , av , _bk , bv | av . partial_cmp ( bv ) . unwrap_or ( std ::cmp ::Ordering ::Equal ) ) ;
}
2025-02-05 17:53:49 +13:00
}
2025-05-15 19:18:03 -04:00
/// Build the artifact graph from the artifact commands and the responses. The
/// initial graph is the graph cached from a previous execution. NodePaths of
/// `exec_artifacts` are filled in from the AST.
2025-01-17 14:34:36 -05:00
pub ( super ) fn build_artifact_graph (
artifact_commands : & [ ArtifactCommand ] ,
responses : & IndexMap < Uuid , WebSocketResponse > ,
ast : & Node < Program > ,
2025-05-16 19:22:01 -04:00
cached_body_items : usize ,
2025-05-15 19:18:03 -04:00
exec_artifacts : & mut IndexMap < ArtifactId , Artifact > ,
initial_graph : ArtifactGraph ,
2025-01-17 14:34:36 -05:00
) -> Result < ArtifactGraph , KclError > {
2025-05-15 19:18:03 -04:00
let mut map = initial_graph . into_map ( ) ;
2025-01-17 14:34:36 -05:00
2025-03-13 21:59:39 -07:00
let mut path_to_plane_id_map = FnvHashMap ::default ( ) ;
2025-01-17 14:34:36 -05:00
let mut current_plane_id = None ;
2025-05-15 19:18:03 -04:00
// Fill in NodePaths for artifacts that were added directly to the map
// during execution.
for exec_artifact in exec_artifacts . values_mut ( ) {
// Note: We only have access to the new AST. So if these artifacts
// somehow came from cached AST, this won't fill in anything.
2025-05-16 19:22:01 -04:00
fill_in_node_paths ( exec_artifact , ast , cached_body_items ) ;
2025-05-15 19:18:03 -04:00
}
2025-01-17 14:34:36 -05:00
for artifact_command in artifact_commands {
if let ModelingCmd ::EnableSketchMode ( EnableSketchMode { entity_id , .. } ) = artifact_command . command {
current_plane_id = Some ( entity_id ) ;
}
2025-03-13 21:59:39 -07:00
// If we get a start path command, we need to set the plane ID to the
// current plane ID.
// THIS IS THE ONLY THING WE CAN ASSUME IS ALWAYS SEQUENTIAL SINCE ITS PART OF THE
// SAME ATOMIC COMMANDS BATCHING.
if let ModelingCmd ::StartPath ( _ ) = artifact_command . command {
if let Some ( plane_id ) = current_plane_id {
path_to_plane_id_map . insert ( artifact_command . cmd_id , plane_id ) ;
}
}
if let ModelingCmd ::SketchModeDisable ( _ ) = artifact_command . command {
2025-01-17 14:34:36 -05:00
current_plane_id = None ;
}
let flattened_responses = flatten_modeling_command_responses ( responses ) ;
let artifact_updates = artifacts_to_update (
& map ,
artifact_command ,
& flattened_responses ,
2025-03-13 21:59:39 -07:00
& path_to_plane_id_map ,
2025-01-17 14:34:36 -05:00
ast ,
2025-05-16 19:22:01 -04:00
cached_body_items ,
2025-01-17 14:34:36 -05:00
exec_artifacts ,
) ? ;
for artifact in artifact_updates {
// Merge with existing artifacts.
merge_artifact_into_map ( & mut map , artifact ) ;
}
}
2025-02-28 16:06:26 -05:00
for exec_artifact in exec_artifacts . values ( ) {
merge_artifact_into_map ( & mut map , exec_artifact . clone ( ) ) ;
}
2025-01-17 14:34:36 -05:00
Ok ( ArtifactGraph { map } )
}
2025-05-15 19:18:03 -04:00
/// These may have been created with placeholder `CodeRef`s because we didn't
/// have the entire AST available. Now we fill them in.
2025-05-16 19:22:01 -04:00
fn fill_in_node_paths ( artifact : & mut Artifact , program : & Node < Program > , cached_body_items : usize ) {
2025-05-15 19:18:03 -04:00
match artifact {
Artifact ::StartSketchOnFace ( face ) = > {
if face . code_ref . node_path . is_empty ( ) {
2025-05-16 19:22:01 -04:00
face . code_ref . node_path =
NodePath ::from_range ( program , cached_body_items , face . code_ref . range ) . unwrap_or_default ( ) ;
2025-05-15 19:18:03 -04:00
}
}
Artifact ::StartSketchOnPlane ( plane ) = > {
if plane . code_ref . node_path . is_empty ( ) {
2025-05-16 19:22:01 -04:00
plane . code_ref . node_path =
NodePath ::from_range ( program , cached_body_items , plane . code_ref . range ) . unwrap_or_default ( ) ;
2025-05-15 19:18:03 -04:00
}
}
_ = > { }
}
}
2025-01-17 14:34:36 -05:00
/// Flatten the responses into a map of command IDs to modeling command
/// responses. The raw responses from the engine contain batches.
fn flatten_modeling_command_responses (
responses : & IndexMap < Uuid , WebSocketResponse > ,
) -> FnvHashMap < Uuid , OkModelingCmdResponse > {
let mut map = FnvHashMap ::default ( ) ;
for ( cmd_id , ws_response ) in responses {
let WebSocketResponse ::Success ( response ) = ws_response else {
// Response not successful.
continue ;
} ;
match & response . resp {
OkWebSocketResponseData ::Modeling { modeling_response } = > {
map . insert ( * cmd_id , modeling_response . clone ( ) ) ;
}
OkWebSocketResponseData ::ModelingBatch { responses } = >
{
#[ expect(
clippy ::iter_over_hash_type ,
reason = " Since we're moving entries to another unordered map, it's fine that the order is undefined "
) ]
for ( cmd_id , batch_response ) in responses {
if let BatchResponse ::Success {
response : modeling_response ,
} = batch_response
{
map . insert ( * cmd_id . as_ref ( ) , modeling_response . clone ( ) ) ;
}
}
}
OkWebSocketResponseData ::IceServerInfo { .. }
| OkWebSocketResponseData ::TrickleIce { .. }
| OkWebSocketResponseData ::SdpAnswer { .. }
| OkWebSocketResponseData ::Export { .. }
| OkWebSocketResponseData ::MetricsRequest { .. }
| OkWebSocketResponseData ::ModelingSessionData { .. }
2025-05-01 11:55:52 -04:00
| OkWebSocketResponseData ::Debug { .. }
2025-01-17 14:34:36 -05:00
| OkWebSocketResponseData ::Pong { .. } = > { }
}
}
map
}
fn merge_artifact_into_map ( map : & mut IndexMap < ArtifactId , Artifact > , new_artifact : Artifact ) {
let id = new_artifact . id ( ) ;
let Some ( old_artifact ) = map . get_mut ( & id ) else {
// No old artifact exists. Insert the new one.
map . insert ( id , new_artifact ) ;
return ;
} ;
if let Some ( replacement ) = old_artifact . merge ( new_artifact ) {
* old_artifact = replacement ;
}
}
/// Merge the new IDs into the base vector, avoiding duplicates. This is O(nm)
/// runtime. Rationale is that most of the ID collections in the artifact graph
/// are pretty small, but we may want to change this in the future.
fn merge_ids ( base : & mut Vec < ArtifactId > , new : Vec < ArtifactId > ) {
let original_len = base . len ( ) ;
for id in new {
// Don't bother inspecting new items that we just pushed.
let original_base = & base [ .. original_len ] ;
if ! original_base . contains ( & id ) {
base . push ( id ) ;
}
}
}
fn merge_opt_id ( base : & mut Option < ArtifactId > , new : Option < ArtifactId > ) {
// Always use the new one, even if it clears it.
* base = new ;
}
fn artifacts_to_update (
artifacts : & IndexMap < ArtifactId , Artifact > ,
artifact_command : & ArtifactCommand ,
responses : & FnvHashMap < Uuid , OkModelingCmdResponse > ,
2025-03-13 21:59:39 -07:00
path_to_plane_id_map : & FnvHashMap < Uuid , Uuid > ,
2025-05-01 23:55:12 -04:00
ast : & Node < Program > ,
2025-05-16 19:22:01 -04:00
cached_body_items : usize ,
2025-02-15 00:57:04 +11:00
exec_artifacts : & IndexMap < ArtifactId , Artifact > ,
2025-01-17 14:34:36 -05:00
) -> Result < Vec < Artifact > , KclError > {
2025-05-15 19:18:03 -04:00
let uuid = artifact_command . cmd_id ;
let Some ( response ) = responses . get ( & uuid ) else {
// Response not found or not successful.
return Ok ( Vec ::new ( ) ) ;
} ;
2025-01-17 14:34:36 -05:00
// TODO: Build path-to-node from artifact_command source range. Right now,
// we're serializing an empty array, and the TS wrapper fills it in with the
2025-05-01 23:55:12 -04:00
// correct value based on NodePath.
2025-01-17 14:34:36 -05:00
let path_to_node = Vec ::new ( ) ;
let range = artifact_command . range ;
2025-05-16 19:22:01 -04:00
let node_path = NodePath ::from_range ( ast , cached_body_items , range ) . unwrap_or_default ( ) ;
2025-05-01 23:55:12 -04:00
let code_ref = CodeRef {
range ,
node_path ,
path_to_node ,
} ;
2025-01-17 14:34:36 -05:00
let id = ArtifactId ::new ( uuid ) ;
let cmd = & artifact_command . command ;
match cmd {
ModelingCmd ::MakePlane ( _ ) = > {
if range . is_synthetic ( ) {
return Ok ( Vec ::new ( ) ) ;
}
// If we're calling `make_plane` and the code range doesn't end at
// `0` it's not a default plane, but a custom one from the
// offsetPlane standard library function.
return Ok ( vec! [ Artifact ::Plane ( Plane {
id ,
path_ids : Vec ::new ( ) ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-01-17 14:34:36 -05:00
} ) ] ) ;
}
2025-03-13 21:59:39 -07:00
ModelingCmd ::EnableSketchMode ( EnableSketchMode { entity_id , .. } ) = > {
let existing_plane = artifacts . get ( & ArtifactId ::new ( * entity_id ) ) ;
2025-01-17 14:34:36 -05:00
match existing_plane {
Some ( Artifact ::Wall ( wall ) ) = > {
return Ok ( vec! [ Artifact ::Wall ( Wall {
2025-03-13 21:59:39 -07:00
id : entity_id . into ( ) ,
2025-01-17 14:34:36 -05:00
seg_id : wall . seg_id ,
edge_cut_edge_ids : wall . edge_cut_edge_ids . clone ( ) ,
sweep_id : wall . sweep_id ,
path_ids : wall . path_ids . clone ( ) ,
2025-02-15 00:57:04 +11:00
face_code_ref : wall . face_code_ref . clone ( ) ,
2025-04-29 06:38:52 -07:00
cmd_id : artifact_command . cmd_id ,
2025-02-15 00:57:04 +11:00
} ) ] ) ;
}
Some ( Artifact ::Cap ( cap ) ) = > {
return Ok ( vec! [ Artifact ::Cap ( Cap {
2025-03-13 21:59:39 -07:00
id : entity_id . into ( ) ,
2025-02-15 00:57:04 +11:00
sub_type : cap . sub_type ,
edge_cut_edge_ids : cap . edge_cut_edge_ids . clone ( ) ,
sweep_id : cap . sweep_id ,
path_ids : cap . path_ids . clone ( ) ,
face_code_ref : cap . face_code_ref . clone ( ) ,
2025-04-30 00:34:35 -04:00
cmd_id : artifact_command . cmd_id ,
2025-01-17 14:34:36 -05:00
} ) ] ) ;
}
Some ( _ ) | None = > {
let path_ids = match existing_plane {
Some ( Artifact ::Plane ( Plane { path_ids , .. } ) ) = > path_ids . clone ( ) ,
_ = > Vec ::new ( ) ,
} ;
return Ok ( vec! [ Artifact ::Plane ( Plane {
2025-03-13 21:59:39 -07:00
id : entity_id . into ( ) ,
2025-01-17 14:34:36 -05:00
path_ids ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-01-17 14:34:36 -05:00
} ) ] ) ;
}
}
}
ModelingCmd ::StartPath ( _ ) = > {
let mut return_arr = Vec ::new ( ) ;
2025-03-13 21:59:39 -07:00
let current_plane_id = path_to_plane_id_map . get ( & artifact_command . cmd_id ) . ok_or_else ( | | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Expected a current plane ID when processing StartPath command, but we have none: {id:?} " ) ,
vec! [ range ] ,
) )
2025-01-17 14:34:36 -05:00
} ) ? ;
return_arr . push ( Artifact ::Path ( Path {
id ,
2025-03-13 21:59:39 -07:00
plane_id : ( * current_plane_id ) . into ( ) ,
2025-01-17 14:34:36 -05:00
seg_ids : Vec ::new ( ) ,
sweep_id : None ,
solid2d_id : None ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-05-07 08:25:12 +10:00
composite_solid_id : None ,
2025-01-17 14:34:36 -05:00
} ) ) ;
2025-03-13 21:59:39 -07:00
let plane = artifacts . get ( & ArtifactId ::new ( * current_plane_id ) ) ;
2025-01-17 14:34:36 -05:00
if let Some ( Artifact ::Plane ( plane ) ) = plane {
2025-05-01 23:55:12 -04:00
let plane_code_ref = plane . code_ref . clone ( ) ;
2025-01-17 14:34:36 -05:00
return_arr . push ( Artifact ::Plane ( Plane {
2025-03-13 21:59:39 -07:00
id : ( * current_plane_id ) . into ( ) ,
2025-01-17 14:34:36 -05:00
path_ids : vec ! [ id ] ,
2025-05-01 23:55:12 -04:00
code_ref : plane_code_ref ,
2025-01-17 14:34:36 -05:00
} ) ) ;
}
if let Some ( Artifact ::Wall ( wall ) ) = plane {
return_arr . push ( Artifact ::Wall ( Wall {
2025-03-13 21:59:39 -07:00
id : ( * current_plane_id ) . into ( ) ,
2025-01-17 14:34:36 -05:00
seg_id : wall . seg_id ,
edge_cut_edge_ids : wall . edge_cut_edge_ids . clone ( ) ,
sweep_id : wall . sweep_id ,
path_ids : vec ! [ id ] ,
2025-02-15 00:57:04 +11:00
face_code_ref : wall . face_code_ref . clone ( ) ,
2025-04-29 06:38:52 -07:00
cmd_id : artifact_command . cmd_id ,
2025-02-15 00:57:04 +11:00
} ) ) ;
}
if let Some ( Artifact ::Cap ( cap ) ) = plane {
return_arr . push ( Artifact ::Cap ( Cap {
2025-03-13 21:59:39 -07:00
id : ( * current_plane_id ) . into ( ) ,
2025-02-15 00:57:04 +11:00
sub_type : cap . sub_type ,
edge_cut_edge_ids : cap . edge_cut_edge_ids . clone ( ) ,
sweep_id : cap . sweep_id ,
path_ids : vec ! [ id ] ,
face_code_ref : cap . face_code_ref . clone ( ) ,
2025-04-30 00:34:35 -04:00
cmd_id : artifact_command . cmd_id ,
2025-01-17 14:34:36 -05:00
} ) ) ;
}
return Ok ( return_arr ) ;
}
ModelingCmd ::ClosePath ( _ ) | ModelingCmd ::ExtendPath ( _ ) = > {
let path_id = ArtifactId ::new ( match cmd {
ModelingCmd ::ClosePath ( c ) = > c . path_id ,
ModelingCmd ::ExtendPath ( e ) = > e . path . into ( ) ,
2025-05-22 17:48:19 -04:00
_ = > internal_error! (
range ,
" Close or extend path command variant not handled: id={id:?}, cmd={cmd:?} "
) ,
2025-01-17 14:34:36 -05:00
} ) ;
let mut return_arr = Vec ::new ( ) ;
return_arr . push ( Artifact ::Segment ( Segment {
id ,
path_id ,
surface_id : None ,
edge_ids : Vec ::new ( ) ,
edge_cut_id : None ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-04-26 14:00:28 +10:00
common_surface_ids : Vec ::new ( ) ,
2025-01-17 14:34:36 -05:00
} ) ) ;
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 ] ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
if let OkModelingCmdResponse ::ClosePath ( close_path ) = response {
return_arr . push ( Artifact ::Solid2d ( Solid2d {
id : close_path . face_id . into ( ) ,
path_id ,
} ) ) ;
if let Some ( Artifact ::Path ( path ) ) = path {
let mut new_path = path . clone ( ) ;
new_path . solid2d_id = Some ( close_path . face_id . into ( ) ) ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
}
return Ok ( return_arr ) ;
}
2025-05-23 11:16:36 -04:00
ModelingCmd ::EntityMirror ( kcmc ::EntityMirror {
ids : original_path_ids , ..
} )
| ModelingCmd ::EntityMirrorAcrossEdge ( kcmc ::EntityMirrorAcrossEdge {
ids : original_path_ids , ..
} ) = > {
let face_edge_infos = match response {
OkModelingCmdResponse ::EntityMirror ( resp ) = > & resp . entity_face_edge_ids ,
OkModelingCmdResponse ::EntityMirrorAcrossEdge ( resp ) = > & resp . entity_face_edge_ids ,
_ = > internal_error! (
range ,
" Mirror response variant not handled: id={id:?}, cmd={cmd:?}, response={response:?} "
) ,
} ;
if original_path_ids . len ( ) ! = face_edge_infos . len ( ) {
internal_error! ( range , " EntityMirror or EntityMirrorAcrossEdge response has different number face edge info than original mirrored paths: id={id:?}, cmd={cmd:?}, response={response:?} " ) ;
}
let mut return_arr = Vec ::new ( ) ;
for ( face_edge_info , original_path_id ) in face_edge_infos . iter ( ) . zip ( original_path_ids ) {
let original_path_id = ArtifactId ::new ( * original_path_id ) ;
let path_id = ArtifactId ::new ( face_edge_info . object_id ) ;
// The path may be an existing path that was extended or a new
// path.
let mut path = if let Some ( Artifact ::Path ( path ) ) = artifacts . get ( & path_id ) {
// Existing path.
path . clone ( )
} else {
// It's a new path. We need the original path to get some
// of its info.
let Some ( Artifact ::Path ( original_path ) ) = artifacts . get ( & original_path_id ) else {
// We couldn't find the original path. This is a bug.
internal_error! ( range , " Couldn't find original path for mirror2d: original_path_id={original_path_id:?}, cmd={cmd:?} " ) ;
} ;
Path {
id : path_id ,
plane_id : original_path . plane_id ,
seg_ids : Vec ::new ( ) ,
sweep_id : None ,
solid2d_id : None ,
code_ref : code_ref . clone ( ) ,
composite_solid_id : None ,
}
} ;
face_edge_info . edges . iter ( ) . for_each ( | edge_id | {
let edge_id = ArtifactId ::new ( * edge_id ) ;
return_arr . push ( Artifact ::Segment ( Segment {
id : edge_id ,
path_id : path . id ,
surface_id : None ,
edge_ids : Vec ::new ( ) ,
edge_cut_id : None ,
code_ref : code_ref . clone ( ) ,
common_surface_ids : Vec ::new ( ) ,
} ) ) ;
// Add the edge ID to the path.
path . seg_ids . push ( edge_id ) ;
} ) ;
return_arr . push ( Artifact ::Path ( path ) ) ;
}
return Ok ( return_arr ) ;
}
2025-01-17 14:34:36 -05:00
ModelingCmd ::Extrude ( kcmc ::Extrude { target , .. } )
| ModelingCmd ::Revolve ( kcmc ::Revolve { target , .. } )
2025-02-25 23:04:10 -05:00
| ModelingCmd ::RevolveAboutEdge ( kcmc ::RevolveAboutEdge { target , .. } )
2025-01-17 14:34:36 -05:00
| ModelingCmd ::Sweep ( kcmc ::Sweep { target , .. } ) = > {
let sub_type = match cmd {
ModelingCmd ::Extrude ( _ ) = > SweepSubType ::Extrusion ,
ModelingCmd ::Revolve ( _ ) = > SweepSubType ::Revolve ,
2025-02-25 23:04:10 -05:00
ModelingCmd ::RevolveAboutEdge ( _ ) = > SweepSubType ::RevolveAboutEdge ,
2025-01-17 14:34:36 -05:00
ModelingCmd ::Sweep ( _ ) = > SweepSubType ::Sweep ,
2025-05-22 17:48:19 -04:00
_ = > internal_error! ( range , " Sweep-like command variant not handled: id={id:?}, cmd={cmd:?} " , ) ,
2025-01-17 14:34:36 -05:00
} ;
let mut return_arr = Vec ::new ( ) ;
let target = ArtifactId ::from ( target ) ;
return_arr . push ( Artifact ::Sweep ( Sweep {
id ,
sub_type ,
path_id : target ,
surface_ids : Vec ::new ( ) ,
edge_ids : Vec ::new ( ) ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-01-17 14:34:36 -05:00
} ) ) ;
let path = artifacts . get ( & target ) ;
if let Some ( Artifact ::Path ( path ) ) = path {
let mut new_path = path . clone ( ) ;
new_path . sweep_id = Some ( id ) ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
return Ok ( return_arr ) ;
}
ModelingCmd ::Loft ( loft_cmd ) = > {
let OkModelingCmdResponse ::Loft ( _ ) = response else {
return Ok ( Vec ::new ( ) ) ;
} ;
let mut return_arr = Vec ::new ( ) ;
return_arr . push ( Artifact ::Sweep ( Sweep {
id ,
sub_type : SweepSubType ::Loft ,
// TODO: Using the first one. Make sure to revisit this
// choice, don't think it matters for now.
path_id : ArtifactId ::new ( * loft_cmd . section_ids . first ( ) . ok_or_else ( | | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! ( " Expected at least one section ID in Loft command: {id:?} ; cmd= {cmd:?} " ) ,
vec! [ range ] ,
) )
2025-01-17 14:34:36 -05:00
} ) ? ) ,
surface_ids : Vec ::new ( ) ,
edge_ids : Vec ::new ( ) ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-01-17 14:34:36 -05:00
} ) ) ;
for section_id in & loft_cmd . section_ids {
let path = artifacts . get ( & ArtifactId ::new ( * section_id ) ) ;
if let Some ( Artifact ::Path ( path ) ) = path {
let mut new_path = path . clone ( ) ;
new_path . sweep_id = Some ( id ) ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
}
return Ok ( return_arr ) ;
}
ModelingCmd ::Solid3dGetExtrusionFaceInfo ( _ ) = > {
let OkModelingCmdResponse ::Solid3dGetExtrusionFaceInfo ( face_info ) = response else {
return Ok ( Vec ::new ( ) ) ;
} ;
let mut return_arr = Vec ::new ( ) ;
let mut last_path = None ;
for face in & face_info . faces {
if face . cap ! = ExtrusionFaceCapType ::None {
continue ;
}
let Some ( curve_id ) = face . curve_id . map ( ArtifactId ::new ) else {
continue ;
} ;
let Some ( face_id ) = face . face_id . map ( ArtifactId ::new ) else {
continue ;
} ;
let Some ( Artifact ::Segment ( seg ) ) = artifacts . get ( & curve_id ) else {
continue ;
} ;
let Some ( Artifact ::Path ( path ) ) = artifacts . get ( & seg . path_id ) else {
continue ;
} ;
last_path = Some ( path ) ;
let path_sweep_id = path . sweep_id . ok_or_else ( | | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-02-04 20:34:56 -05:00
" Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?} "
) ,
2025-05-19 14:13:10 -04:00
vec! [ range ] ,
) )
2025-01-17 14:34:36 -05:00
} ) ? ;
2025-02-15 00:57:04 +11:00
let extra_artifact = exec_artifacts . values ( ) . find ( | a | {
2025-02-28 16:06:26 -05:00
if let Artifact ::StartSketchOnFace ( s ) = a {
s . face_id = = face_id
2025-05-15 19:18:03 -04:00
} else if let Artifact ::StartSketchOnPlane ( s ) = a {
s . plane_id = = face_id
2025-02-15 00:57:04 +11:00
} else {
false
}
} ) ;
2025-05-15 19:18:03 -04:00
let sketch_on_face_code_ref = extra_artifact
2025-02-15 00:57:04 +11:00
. and_then ( | a | match a {
2025-05-15 19:18:03 -04:00
Artifact ::StartSketchOnFace ( s ) = > Some ( s . code_ref . clone ( ) ) ,
Artifact ::StartSketchOnPlane ( s ) = > Some ( s . code_ref . clone ( ) ) ,
2025-02-15 00:57:04 +11:00
_ = > None ,
} )
2025-05-15 19:18:03 -04:00
// TODO: If we didn't find it, it's probably a bug.
2025-02-15 00:57:04 +11:00
. unwrap_or_default ( ) ;
2025-01-17 14:34:36 -05:00
return_arr . push ( Artifact ::Wall ( Wall {
id : face_id ,
seg_id : curve_id ,
edge_cut_edge_ids : Vec ::new ( ) ,
sweep_id : path_sweep_id ,
2025-02-15 00:57:04 +11:00
path_ids : Vec ::new ( ) ,
2025-05-15 19:18:03 -04:00
face_code_ref : sketch_on_face_code_ref ,
2025-04-29 06:38:52 -07:00
cmd_id : artifact_command . cmd_id ,
2025-01-17 14:34:36 -05:00
} ) ) ;
let mut new_seg = seg . clone ( ) ;
new_seg . surface_id = Some ( face_id ) ;
return_arr . push ( Artifact ::Segment ( new_seg ) ) ;
if let Some ( Artifact ::Sweep ( sweep ) ) = path . sweep_id . and_then ( | id | artifacts . get ( & id ) ) {
let mut new_sweep = sweep . clone ( ) ;
new_sweep . surface_ids = vec! [ face_id ] ;
return_arr . push ( Artifact ::Sweep ( new_sweep ) ) ;
}
}
if let Some ( path ) = last_path {
for face in & face_info . faces {
let sub_type = match face . cap {
ExtrusionFaceCapType ::Top = > CapSubType ::End ,
ExtrusionFaceCapType ::Bottom = > CapSubType ::Start ,
ExtrusionFaceCapType ::None | ExtrusionFaceCapType ::Both = > continue ,
} ;
let Some ( face_id ) = face . face_id . map ( ArtifactId ::new ) else {
continue ;
} ;
let path_sweep_id = path . sweep_id . ok_or_else ( | | {
2025-06-02 13:30:57 -05:00
KclError ::new_internal ( KclErrorDetails ::new (
2025-05-19 14:13:10 -04:00
format! (
2025-02-25 23:04:10 -05:00
" Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?} "
2025-02-04 20:34:56 -05:00
) ,
2025-05-19 14:13:10 -04:00
vec! [ range ] ,
) )
2025-01-17 14:34:36 -05:00
} ) ? ;
2025-02-15 00:57:04 +11:00
let extra_artifact = exec_artifacts . values ( ) . find ( | a | {
2025-02-28 16:06:26 -05:00
if let Artifact ::StartSketchOnFace ( s ) = a {
s . face_id = = face_id
2025-05-15 19:18:03 -04:00
} else if let Artifact ::StartSketchOnPlane ( s ) = a {
s . plane_id = = face_id
2025-02-15 00:57:04 +11:00
} else {
false
}
} ) ;
2025-05-15 19:18:03 -04:00
let sketch_on_face_code_ref = extra_artifact
2025-02-15 00:57:04 +11:00
. and_then ( | a | match a {
2025-05-15 19:18:03 -04:00
Artifact ::StartSketchOnFace ( s ) = > Some ( s . code_ref . clone ( ) ) ,
Artifact ::StartSketchOnPlane ( s ) = > Some ( s . code_ref . clone ( ) ) ,
2025-02-15 00:57:04 +11:00
_ = > None ,
} )
2025-05-15 19:18:03 -04:00
// TODO: If we didn't find it, it's probably a bug.
2025-02-15 00:57:04 +11:00
. unwrap_or_default ( ) ;
2025-01-17 14:34:36 -05:00
return_arr . push ( Artifact ::Cap ( Cap {
id : face_id ,
sub_type ,
edge_cut_edge_ids : Vec ::new ( ) ,
sweep_id : path_sweep_id ,
path_ids : Vec ::new ( ) ,
2025-05-15 19:18:03 -04:00
face_code_ref : sketch_on_face_code_ref ,
2025-04-30 00:34:35 -04:00
cmd_id : artifact_command . cmd_id ,
2025-01-17 14:34:36 -05:00
} ) ) ;
let Some ( Artifact ::Sweep ( sweep ) ) = artifacts . get ( & path_sweep_id ) else {
continue ;
} ;
let mut new_sweep = sweep . clone ( ) ;
new_sweep . surface_ids = vec! [ face_id ] ;
return_arr . push ( Artifact ::Sweep ( new_sweep ) ) ;
}
}
return Ok ( return_arr ) ;
}
2025-05-13 21:07:24 -07:00
ModelingCmd ::Solid3dGetAdjacencyInfo ( kcmc ::Solid3dGetAdjacencyInfo { .. } ) = > {
let OkModelingCmdResponse ::Solid3dGetAdjacencyInfo ( info ) = response else {
2025-01-17 14:34:36 -05:00
return Ok ( Vec ::new ( ) ) ;
} ;
let mut return_arr = Vec ::new ( ) ;
2025-05-13 21:07:24 -07:00
for ( index , edge ) in info . edges . iter ( ) . enumerate ( ) {
let Some ( original_info ) = & edge . original_info else {
continue ;
} ;
let edge_id = ArtifactId ::new ( original_info . edge_id ) ;
let Some ( artifact ) = artifacts . get ( & edge_id ) else {
continue ;
} ;
match artifact {
Artifact ::Segment ( segment ) = > {
let mut new_segment = segment . clone ( ) ;
new_segment . common_surface_ids =
original_info . faces . iter ( ) . map ( | face | ArtifactId ::new ( * face ) ) . collect ( ) ;
return_arr . push ( Artifact ::Segment ( new_segment ) ) ;
}
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 ) ) ;
}
_ = > { }
} ;
let Some ( Artifact ::Segment ( segment ) ) = artifacts . get ( & edge_id ) else {
continue ;
} ;
let Some ( surface_id ) = segment . surface_id else {
continue ;
} ;
let Some ( Artifact ::Wall ( wall ) ) = artifacts . get ( & surface_id ) else {
continue ;
} ;
let Some ( Artifact ::Sweep ( sweep ) ) = artifacts . get ( & wall . sweep_id ) else {
continue ;
} ;
let Some ( Artifact ::Path ( _ ) ) = artifacts . get ( & sweep . path_id ) else {
continue ;
} ;
if let Some ( opposite_info ) = & edge . opposite_info {
return_arr . push ( Artifact ::SweepEdge ( SweepEdge {
id : opposite_info . edge_id . into ( ) ,
sub_type : SweepEdgeSubType ::Opposite ,
seg_id : edge_id ,
cmd_id : artifact_command . cmd_id ,
index ,
sweep_id : sweep . id ,
common_surface_ids : opposite_info . faces . iter ( ) . map ( | face | ArtifactId ::new ( * face ) ) . collect ( ) ,
} ) ) ;
2025-04-26 14:00:28 +10:00
let mut new_segment = segment . clone ( ) ;
2025-05-13 21:07:24 -07:00
new_segment . edge_ids = vec! [ opposite_info . edge_id . into ( ) ] ;
2025-04-26 14:00:28 +10:00
return_arr . push ( Artifact ::Segment ( new_segment ) ) ;
2025-05-13 21:07:24 -07:00
let mut new_sweep = sweep . clone ( ) ;
new_sweep . edge_ids = vec! [ opposite_info . edge_id . into ( ) ] ;
return_arr . push ( Artifact ::Sweep ( new_sweep ) ) ;
let mut new_wall = wall . clone ( ) ;
new_wall . edge_cut_edge_ids = vec! [ opposite_info . edge_id . into ( ) ] ;
return_arr . push ( Artifact ::Wall ( new_wall ) ) ;
2025-04-26 14:00:28 +10:00
}
2025-05-13 21:07:24 -07:00
if let Some ( adjacent_info ) = & edge . adjacent_info {
return_arr . push ( Artifact ::SweepEdge ( SweepEdge {
id : adjacent_info . edge_id . into ( ) ,
sub_type : SweepEdgeSubType ::Adjacent ,
seg_id : edge_id ,
cmd_id : artifact_command . cmd_id ,
index ,
sweep_id : sweep . id ,
common_surface_ids : adjacent_info . faces . iter ( ) . map ( | face | ArtifactId ::new ( * face ) ) . collect ( ) ,
} ) ) ;
let mut new_segment = segment . clone ( ) ;
new_segment . edge_ids = vec! [ adjacent_info . edge_id . into ( ) ] ;
return_arr . push ( Artifact ::Segment ( new_segment ) ) ;
let mut new_sweep = sweep . clone ( ) ;
new_sweep . edge_ids = vec! [ adjacent_info . edge_id . into ( ) ] ;
return_arr . push ( Artifact ::Sweep ( new_sweep ) ) ;
let mut new_wall = wall . clone ( ) ;
new_wall . edge_cut_edge_ids = vec! [ adjacent_info . edge_id . into ( ) ] ;
return_arr . push ( Artifact ::Wall ( new_wall ) ) ;
2025-04-26 14:00:28 +10:00
}
2025-05-13 21:07:24 -07:00
}
2025-04-26 14:00:28 +10:00
return Ok ( return_arr ) ;
}
2025-01-17 14:34:36 -05:00
ModelingCmd ::Solid3dFilletEdge ( cmd ) = > {
let mut return_arr = Vec ::new ( ) ;
2025-05-07 14:10:18 -07:00
let edge_id = if let Some ( edge_id ) = cmd . edge_id {
ArtifactId ::new ( edge_id )
} else {
2025-05-22 17:48:19 -04:00
let Some ( edge_id ) = cmd . edge_ids . first ( ) else {
internal_error! (
range ,
" Solid3dFilletEdge command has no edge ID: id={id:?}, cmd={cmd:?} "
) ;
} ;
edge_id . into ( )
2025-05-07 14:10:18 -07:00
} ;
2025-01-17 14:34:36 -05:00
return_arr . push ( Artifact ::EdgeCut ( EdgeCut {
id ,
sub_type : cmd . cut_type . into ( ) ,
2025-05-07 14:10:18 -07:00
consumed_edge_id : edge_id ,
2025-01-17 14:34:36 -05:00
edge_ids : Vec ::new ( ) ,
surface_id : None ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-01-17 14:34:36 -05:00
} ) ) ;
2025-05-07 14:10:18 -07:00
let consumed_edge = artifacts . get ( & edge_id ) ;
2025-01-17 14:34:36 -05:00
if let Some ( Artifact ::Segment ( consumed_edge ) ) = consumed_edge {
let mut new_segment = consumed_edge . clone ( ) ;
new_segment . edge_cut_id = Some ( id ) ;
return_arr . push ( Artifact ::Segment ( new_segment ) ) ;
} else {
// TODO: Handle other types like SweepEdge.
}
return Ok ( return_arr ) ;
}
2025-02-10 14:24:00 -05:00
ModelingCmd ::EntityMakeHelixFromParams ( _ ) = > {
let return_arr = vec! [ Artifact ::Helix ( Helix {
id ,
axis_id : None ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-02-10 14:24:00 -05:00
} ) ] ;
return Ok ( return_arr ) ;
}
ModelingCmd ::EntityMakeHelixFromEdge ( helix ) = > {
let edge_id = ArtifactId ::new ( helix . edge_id ) ;
let return_arr = vec! [ Artifact ::Helix ( Helix {
id ,
axis_id : Some ( edge_id ) ,
2025-05-01 23:55:12 -04:00
code_ref ,
2025-02-10 14:24:00 -05:00
} ) ] ;
// We could add the reverse graph edge connecting from the edge to
// the helix here, but it's not useful right now.
return Ok ( return_arr ) ;
}
2025-04-11 01:16:29 -04:00
ModelingCmd ::BooleanIntersection ( _ ) | ModelingCmd ::BooleanSubtract ( _ ) | ModelingCmd ::BooleanUnion ( _ ) = > {
let ( sub_type , solid_ids , tool_ids ) = match cmd {
ModelingCmd ::BooleanIntersection ( intersection ) = > {
let solid_ids = intersection
. solid_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
. collect ::< Vec < _ > > ( ) ;
( CompositeSolidSubType ::Intersect , solid_ids , Vec ::new ( ) )
}
ModelingCmd ::BooleanSubtract ( subtract ) = > {
let solid_ids = subtract
. target_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
. collect ::< Vec < _ > > ( ) ;
let tool_ids = subtract
. tool_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
. collect ::< Vec < _ > > ( ) ;
( CompositeSolidSubType ::Subtract , solid_ids , tool_ids )
}
ModelingCmd ::BooleanUnion ( union ) = > {
let solid_ids = union . solid_ids . iter ( ) . copied ( ) . map ( ArtifactId ::new ) . collect ::< Vec < _ > > ( ) ;
( CompositeSolidSubType ::Union , solid_ids , Vec ::new ( ) )
}
2025-05-22 17:48:19 -04:00
_ = > internal_error! (
range ,
" Boolean or composite command variant not handled: id={id:?}, cmd={cmd:?} "
) ,
2025-04-11 01:16:29 -04:00
} ;
let mut new_solid_ids = vec! [ id ] ;
2025-04-18 18:35:17 -04:00
// Make sure we don't ever create a duplicate ID since merge_ids
// can't handle it.
let not_cmd_id = move | solid_id : & ArtifactId | * solid_id ! = id ;
2025-04-11 01:16:29 -04:00
match response {
OkModelingCmdResponse ::BooleanIntersection ( intersection ) = > intersection
. extra_solid_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
2025-04-18 18:35:17 -04:00
. filter ( not_cmd_id )
2025-04-11 01:16:29 -04:00
. for_each ( | id | new_solid_ids . push ( id ) ) ,
OkModelingCmdResponse ::BooleanSubtract ( subtract ) = > subtract
. extra_solid_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
2025-04-18 18:35:17 -04:00
. filter ( not_cmd_id )
2025-04-11 01:16:29 -04:00
. for_each ( | id | new_solid_ids . push ( id ) ) ,
OkModelingCmdResponse ::BooleanUnion ( union ) = > union
. extra_solid_ids
. iter ( )
. copied ( )
. map ( ArtifactId ::new )
2025-04-18 18:35:17 -04:00
. filter ( not_cmd_id )
2025-04-11 01:16:29 -04:00
. for_each ( | id | new_solid_ids . push ( id ) ) ,
_ = > { }
}
2025-05-07 08:25:12 +10:00
let mut return_arr = Vec ::new ( ) ;
// Create the new composite solids and update their linked artifacts
for solid_id in & new_solid_ids {
// Create the composite solid
return_arr . push ( Artifact ::CompositeSolid ( CompositeSolid {
id : * solid_id ,
sub_type ,
solid_ids : solid_ids . clone ( ) ,
tool_ids : tool_ids . clone ( ) ,
code_ref : code_ref . clone ( ) ,
composite_solid_id : None ,
} ) ) ;
// Update the artifacts that were used as input for this composite solid
for input_id in & solid_ids {
if let Some ( artifact ) = artifacts . get ( input_id ) {
match artifact {
Artifact ::CompositeSolid ( comp ) = > {
let mut new_comp = comp . clone ( ) ;
new_comp . composite_solid_id = Some ( * solid_id ) ;
return_arr . push ( Artifact ::CompositeSolid ( new_comp ) ) ;
}
Artifact ::Path ( path ) = > {
let mut new_path = path . clone ( ) ;
new_path . composite_solid_id = Some ( * solid_id ) ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
_ = > { }
}
}
}
// Update the tool artifacts if this is a subtract operation
for tool_id in & tool_ids {
if let Some ( artifact ) = artifacts . get ( tool_id ) {
match artifact {
Artifact ::CompositeSolid ( comp ) = > {
let mut new_comp = comp . clone ( ) ;
new_comp . composite_solid_id = Some ( * solid_id ) ;
return_arr . push ( Artifact ::CompositeSolid ( new_comp ) ) ;
}
Artifact ::Path ( path ) = > {
let mut new_path = path . clone ( ) ;
new_path . composite_solid_id = Some ( * solid_id ) ;
return_arr . push ( Artifact ::Path ( new_path ) ) ;
}
_ = > { }
}
}
}
}
2025-04-11 01:16:29 -04:00
return Ok ( return_arr ) ;
}
2025-01-17 14:34:36 -05:00
_ = > { }
}
Ok ( Vec ::new ( ) )
2025-01-08 20:02:30 -05:00
}