use std::fmt::Write;
use super::*;
type NodeId = u32;
type Edges = IndexMap<(NodeId, NodeId), EdgeInfo>;
#[derive(Debug, Clone, PartialEq, Eq)]
struct EdgeInfo {
direction: EdgeDirection,
flow: EdgeFlow,
kind: EdgeKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum EdgeDirection {
Forward,
Backward,
Bidirectional,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum EdgeFlow {
SourceToTarget,
TargetToSource,
}
impl EdgeFlow {
#[must_use]
fn reverse(&self) -> EdgeFlow {
match self {
EdgeFlow::SourceToTarget => EdgeFlow::TargetToSource,
EdgeFlow::TargetToSource => EdgeFlow::SourceToTarget,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum EdgeKind {
PathToSweep,
Other,
}
impl EdgeDirection {
#[must_use]
fn merge(&self, other: EdgeDirection) -> EdgeDirection {
match self {
EdgeDirection::Forward => match other {
EdgeDirection::Forward => EdgeDirection::Forward,
EdgeDirection::Backward => EdgeDirection::Bidirectional,
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
},
EdgeDirection::Backward => match other {
EdgeDirection::Forward => EdgeDirection::Bidirectional,
EdgeDirection::Backward => EdgeDirection::Backward,
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
},
EdgeDirection::Bidirectional => EdgeDirection::Bidirectional,
}
}
}
impl Artifact {
pub(crate) fn back_edges(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(a) => {
let mut ids = a.solid_ids.clone();
ids.extend(a.tool_ids.iter());
ids
}
Artifact::Plane(_) => Vec::new(),
Artifact::Path(a) => {
let mut ids = vec![a.plane_id];
if let Some(sketch_block_id) = a.sketch_block_id {
ids.push(sketch_block_id);
}
if let Some(origin_path_id) = a.origin_path_id {
ids.push(origin_path_id);
}
if let Some(inner_path_id) = a.inner_path_id {
ids.push(inner_path_id);
}
if let Some(outer_path_id) = a.outer_path_id {
ids.push(outer_path_id);
}
ids
}
Artifact::Segment(a) => {
let mut ids = vec![a.path_id];
if let Some(original_id) = a.original_seg_id {
ids.push(original_id);
}
ids
}
Artifact::Solid2d(a) => vec![a.path_id],
Artifact::PrimitiveFace(a) => vec![a.solid_id],
Artifact::PrimitiveEdge(a) => vec![a.solid_id],
Artifact::StartSketchOnFace(a) => vec![a.face_id],
Artifact::StartSketchOnPlane(a) => vec![a.plane_id],
Artifact::SketchBlock(a) => a.plane_id.map(|id| vec![id]).unwrap_or_default(),
Artifact::SketchBlockConstraint(_) => Vec::new(),
Artifact::PlaneOfFace(a) => vec![a.face_id],
Artifact::Sweep(a) => {
let mut ids = vec![a.path_id];
if let Some(trajectory_id) = a.trajectory_id {
ids.push(trajectory_id);
}
ids
}
Artifact::Wall(a) => vec![a.seg_id, a.sweep_id],
Artifact::Cap(a) => vec![a.sweep_id],
Artifact::SweepEdge(a) => vec![a.seg_id, a.sweep_id],
Artifact::EdgeCut(a) => vec![a.consumed_edge_id],
Artifact::EdgeCutEdge(a) => vec![a.edge_cut_id],
Artifact::Helix(a) => a.axis_id.map(|id| vec![id]).unwrap_or_default(),
}
}
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(a) => {
let mut ids = Vec::new();
if let Some(composite_solid_id) = a.composite_solid_id {
ids.push(composite_solid_id);
}
ids
}
Artifact::Plane(a) => a.path_ids.clone(),
Artifact::Path(a) => {
let mut ids = a.seg_ids.clone();
if let Some(sweep_id) = a.sweep_id {
ids.push(sweep_id);
}
if let Some(sweep_id_trajectory) = a.trajectory_sweep_id {
ids.push(sweep_id_trajectory);
}
if let Some(solid2d_id) = a.solid2d_id {
ids.push(solid2d_id);
}
if let Some(composite_solid_id) = a.composite_solid_id {
ids.push(composite_solid_id);
}
ids
}
Artifact::Segment(a) => {
let mut ids = Vec::new();
if let Some(surface_id) = a.surface_id {
ids.push(surface_id);
}
ids.extend(&a.edge_ids);
if let Some(edge_cut_id) = a.edge_cut_id {
ids.push(edge_cut_id);
}
ids.extend(&a.common_surface_ids);
ids
}
Artifact::Solid2d(_) => {
Vec::new()
}
Artifact::PrimitiveFace(_) => {
Vec::new()
}
Artifact::PrimitiveEdge(_) => {
Vec::new()
}
Artifact::StartSketchOnFace { .. } => {
Vec::new()
}
Artifact::StartSketchOnPlane { .. } => {
Vec::new()
}
Artifact::SketchBlock(a) => {
let mut ids = Vec::new();
if let Some(path_id) = a.path_id {
ids.push(path_id);
}
ids
}
Artifact::SketchBlockConstraint { .. } => {
Vec::new()
}
Artifact::PlaneOfFace { .. } => {
Vec::new()
}
Artifact::Sweep(a) => {
let mut ids = Vec::new();
ids.extend(&a.surface_ids);
ids.extend(&a.edge_ids);
ids
}
Artifact::Wall(a) => {
let mut ids = Vec::new();
ids.extend(&a.edge_cut_edge_ids);
ids.extend(&a.path_ids);
ids
}
Artifact::Cap(a) => {
let mut ids = Vec::new();
ids.extend(&a.edge_cut_edge_ids);
ids.extend(&a.path_ids);
ids
}
Artifact::SweepEdge(a) => {
let mut ids = Vec::new();
ids.extend(&a.common_surface_ids);
ids
}
Artifact::EdgeCut(a) => {
let mut ids = Vec::new();
ids.extend(&a.edge_ids);
if let Some(surface_id) = a.surface_id {
ids.push(surface_id);
}
ids
}
Artifact::EdgeCutEdge(a) => {
vec![a.surface_id]
}
Artifact::Helix(a) => {
let mut ids = Vec::new();
if let Some(sweep_id) = a.trajectory_sweep_id {
ids.push(sweep_id);
}
ids
}
}
}
}
impl ArtifactGraph {
pub(crate) fn to_mermaid_flowchart(&self) -> Result<String, std::fmt::Error> {
let mut output = String::new();
output.push_str("```mermaid\n");
output.push_str("flowchart LR\n");
let mut next_id = 1_u32;
let mut stable_id_map = FnvHashMap::default();
for id in self.map.keys() {
stable_id_map.insert(*id, next_id);
next_id = next_id.checked_add(1).unwrap();
}
self.flowchart_nodes(&mut output, &stable_id_map, " ")?;
self.flowchart_edges(&mut output, &stable_id_map, " ")?;
output.push_str("```\n");
Ok(output)
}
fn flowchart_nodes<W: Write>(
&self,
output: &mut W,
stable_id_map: &FnvHashMap<ArtifactId, NodeId>,
prefix: &str,
) -> std::fmt::Result {
let mut groups = IndexMap::new();
let mut ungrouped = Vec::new();
for artifact in self.map.values() {
let id = artifact.id();
let grouped = match artifact {
Artifact::CompositeSolid(_) => false,
Artifact::Plane(_) => false,
Artifact::Path(_) => {
groups.entry(id).or_insert_with(Vec::new).push(id);
true
}
Artifact::Segment(segment) => {
let path_id = segment.path_id;
groups.entry(path_id).or_insert_with(Vec::new).push(id);
true
}
Artifact::Solid2d(solid2d) => {
let path_id = solid2d.path_id;
groups.entry(path_id).or_insert_with(Vec::new).push(id);
true
}
Artifact::PrimitiveFace(_) | Artifact::PrimitiveEdge(_) => false,
Artifact::StartSketchOnFace { .. }
| Artifact::StartSketchOnPlane { .. }
| Artifact::SketchBlock { .. }
| Artifact::SketchBlockConstraint { .. }
| Artifact::PlaneOfFace { .. }
| Artifact::Sweep(_)
| Artifact::Wall(_)
| Artifact::Cap(_)
| Artifact::SweepEdge(_)
| Artifact::EdgeCut(_)
| Artifact::EdgeCutEdge(_)
| Artifact::Helix(_) => false,
};
if !grouped {
ungrouped.push(id);
}
}
for (group_id, artifact_ids) in groups {
let group_id = *stable_id_map.get(&group_id).unwrap();
writeln!(output, "{prefix}subgraph path{group_id} [Path]")?;
let indented = format!("{prefix} ");
for artifact_id in artifact_ids {
let artifact = self.map.get(&artifact_id).unwrap();
let id = *stable_id_map.get(&artifact_id).unwrap();
self.flowchart_node(output, artifact, id, &indented)?;
}
writeln!(output, "{prefix}end")?;
}
for artifact_id in ungrouped {
let artifact = self.map.get(&artifact_id).unwrap();
let id = *stable_id_map.get(&artifact_id).unwrap();
self.flowchart_node(output, artifact, id, prefix)?;
}
Ok(())
}
fn flowchart_node<W: Write>(
&self,
output: &mut W,
artifact: &Artifact,
id: NodeId,
prefix: &str,
) -> std::fmt::Result {
fn code_ref_display(code_ref: &CodeRef) -> [usize; 3] {
let range = code_ref.range;
[range.start(), range.end(), range.module_id().as_usize()]
}
fn node_path_display<W: Write>(
output: &mut W,
prefix: &str,
label: Option<&str>,
code_ref: &CodeRef,
) -> std::fmt::Result {
let label = label.unwrap_or("");
if code_ref.node_path.is_empty() {
return writeln!(output, "{prefix} %% {label}Missing NodePath");
}
writeln!(output, "{prefix} %% {label}{:?}", code_ref.node_path.steps)
}
match artifact {
Artifact::CompositeSolid(composite_solid) => {
writeln!(
output,
"{prefix}{id}[\"CompositeSolid {:?}<br>{:?}<br>Consumed: {:?}\"]",
composite_solid.sub_type,
code_ref_display(&composite_solid.code_ref),
composite_solid.consumed
)?;
node_path_display(output, prefix, None, &composite_solid.code_ref)?;
}
Artifact::Plane(plane) => {
writeln!(
output,
"{prefix}{id}[\"Plane<br>{:?}\"]",
code_ref_display(&plane.code_ref)
)?;
node_path_display(output, prefix, None, &plane.code_ref)?;
}
Artifact::Path(path) => {
let path_sub_type = if path.sub_type == PathSubType::Region {
" Region"
} else {
""
};
writeln!(
output,
"{prefix}{id}[\"Path{path_sub_type}<br>{:?}<br>Consumed: {:?}\"]",
code_ref_display(&path.code_ref),
path.consumed
)?;
node_path_display(output, prefix, None, &path.code_ref)?;
}
Artifact::Segment(segment) => {
writeln!(
output,
"{prefix}{id}[\"Segment<br>{:?}\"]",
code_ref_display(&segment.code_ref)
)?;
node_path_display(output, prefix, None, &segment.code_ref)?;
}
Artifact::Solid2d(_solid2d) => {
writeln!(output, "{prefix}{id}[Solid2d]")?;
}
Artifact::PrimitiveFace(face) => {
writeln!(
output,
"{prefix}{id}[\"PrimitiveFace<br>{:?}\"]",
code_ref_display(&face.code_ref)
)?;
node_path_display(output, prefix, None, &face.code_ref)?;
}
Artifact::PrimitiveEdge(edge) => {
writeln!(
output,
"{prefix}{id}[\"PrimitiveEdge<br>{:?}\"]",
code_ref_display(&edge.code_ref)
)?;
node_path_display(output, prefix, None, &edge.code_ref)?;
}
Artifact::StartSketchOnFace(StartSketchOnFace { code_ref, .. }) => {
writeln!(
output,
"{prefix}{id}[\"StartSketchOnFace<br>{:?}\"]",
code_ref_display(code_ref)
)?;
node_path_display(output, prefix, None, code_ref)?;
}
Artifact::StartSketchOnPlane(StartSketchOnPlane { code_ref, .. }) => {
writeln!(
output,
"{prefix}{id}[\"StartSketchOnPlane<br>{:?}\"]",
code_ref_display(code_ref)
)?;
node_path_display(output, prefix, None, code_ref)?;
}
Artifact::SketchBlock(SketchBlock { code_ref, .. }) => {
writeln!(
output,
"{prefix}{id}[\"SketchBlock<br>{:?}\"]",
code_ref_display(code_ref)
)?;
node_path_display(output, prefix, None, code_ref)?;
}
Artifact::SketchBlockConstraint(constraint) => {
writeln!(
output,
"{prefix}{id}[\"SketchBlockConstraint {:?}<br>{:?}\"]",
constraint.constraint_type,
code_ref_display(&constraint.code_ref)
)?;
node_path_display(output, prefix, None, &constraint.code_ref)?;
}
Artifact::PlaneOfFace(PlaneOfFace { code_ref, .. }) => {
writeln!(
output,
"{prefix}{id}[\"PlaneOfFace<br>{:?}\"]",
code_ref_display(code_ref)
)?;
node_path_display(output, prefix, None, code_ref)?;
}
Artifact::Sweep(sweep) => {
writeln!(
output,
"{prefix}{id}[\"Sweep {:?}<br>{:?}<br>Consumed: {:?}\"]",
sweep.sub_type,
code_ref_display(&sweep.code_ref),
sweep.consumed,
)?;
node_path_display(output, prefix, None, &sweep.code_ref)?;
}
Artifact::Wall(wall) => {
writeln!(output, "{prefix}{id}[Wall]")?;
node_path_display(output, prefix, Some("face_code_ref="), &wall.face_code_ref)?;
}
Artifact::Cap(cap) => {
writeln!(output, "{prefix}{id}[\"Cap {:?}\"]", cap.sub_type)?;
node_path_display(output, prefix, Some("face_code_ref="), &cap.face_code_ref)?;
}
Artifact::SweepEdge(sweep_edge) => {
writeln!(output, "{prefix}{id}[\"SweepEdge {:?}\"]", sweep_edge.sub_type)?;
}
Artifact::EdgeCut(edge_cut) => {
writeln!(
output,
"{prefix}{id}[\"EdgeCut {:?}<br>{:?}\"]",
edge_cut.sub_type,
code_ref_display(&edge_cut.code_ref)
)?;
node_path_display(output, prefix, None, &edge_cut.code_ref)?;
}
Artifact::EdgeCutEdge(_edge_cut_edge) => {
writeln!(output, "{prefix}{id}[EdgeCutEdge]")?;
}
Artifact::Helix(helix) => {
writeln!(
output,
"{prefix}{id}[\"Helix<br>{:?}: Consumed: {:?}\"]",
code_ref_display(&helix.code_ref),
helix.consumed
)?;
node_path_display(output, prefix, None, &helix.code_ref)?;
}
}
Ok(())
}
fn flowchart_edges<W: Write>(
&self,
output: &mut W,
stable_id_map: &FnvHashMap<ArtifactId, NodeId>,
prefix: &str,
) -> Result<(), std::fmt::Error> {
fn add_unique_edge(edges: &mut Edges, source_id: NodeId, target_id: NodeId, flow: EdgeFlow, kind: EdgeKind) {
if source_id == target_id {
return;
}
let a = source_id.min(target_id);
let b = source_id.max(target_id);
let new_direction = if a == source_id {
EdgeDirection::Forward
} else {
EdgeDirection::Backward
};
let initial_flow = if a == source_id { flow } else { flow.reverse() };
let edge = edges.entry((a, b)).or_insert(EdgeInfo {
direction: new_direction,
flow: initial_flow,
kind,
});
edge.direction = edge.direction.merge(new_direction);
}
let mut edges = IndexMap::default();
for artifact in self.map.values() {
let source_id = *stable_id_map.get(&artifact.id()).unwrap();
for (target_id, flow) in artifact
.back_edges()
.into_iter()
.zip(std::iter::repeat(EdgeFlow::TargetToSource))
.chain(
artifact
.child_ids()
.into_iter()
.zip(std::iter::repeat(EdgeFlow::SourceToTarget)),
)
{
let Some(target) = self.map.get(&target_id) else {
continue;
};
let edge_kind = match (artifact, target) {
(Artifact::Path(_), Artifact::Sweep(_)) | (Artifact::Sweep(_), Artifact::Path(_)) => {
EdgeKind::PathToSweep
}
_ => EdgeKind::Other,
};
let target_id = *stable_id_map.get(&target_id).unwrap();
add_unique_edge(&mut edges, source_id, target_id, flow, edge_kind);
}
}
edges.par_sort_by(|ak, _, bk, _| if ak.0 == bk.0 { ak.1.cmp(&bk.1) } else { ak.0.cmp(&bk.0) });
for ((source_id, target_id), edge) in edges {
let extra = match edge.kind {
EdgeKind::PathToSweep => "-",
EdgeKind::Other => "",
};
match edge.flow {
EdgeFlow::SourceToTarget => match edge.direction {
EdgeDirection::Forward => {
writeln!(output, "{prefix}{source_id} x{extra}--> {target_id}")?;
}
EdgeDirection::Backward => {
writeln!(output, "{prefix}{source_id} <{extra}--x {target_id}")?;
}
EdgeDirection::Bidirectional => {
writeln!(output, "{prefix}{source_id} {extra}--- {target_id}")?;
}
},
EdgeFlow::TargetToSource => match edge.direction {
EdgeDirection::Forward => {
writeln!(output, "{prefix}{target_id} x{extra}--> {source_id}")?;
}
EdgeDirection::Backward => {
writeln!(output, "{prefix}{target_id} <{extra}--x {source_id}")?;
}
EdgeDirection::Bidirectional => {
writeln!(output, "{prefix}{target_id} {extra}--- {source_id}")?;
}
},
}
}
Ok(())
}
}
#[test]
fn surface_blend_creates_blend_sweep_artifact() {
let path_one_id = ArtifactId::new(Uuid::new_v4());
let path_two_id = ArtifactId::new(Uuid::new_v4());
let source_surface_one_id = ArtifactId::new(Uuid::new_v4());
let source_surface_two_id = ArtifactId::new(Uuid::new_v4());
let source_code_ref = CodeRef::placeholder(SourceRange::synthetic());
let mut artifacts = IndexMap::new();
artifacts.insert(
source_surface_one_id,
Artifact::Sweep(Sweep {
id: source_surface_one_id,
sub_type: SweepSubType::Extrusion,
path_id: path_one_id,
surface_ids: Vec::new(),
edge_ids: Vec::new(),
code_ref: source_code_ref.clone(),
trajectory_id: None,
method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
consumed: false,
}),
);
artifacts.insert(
source_surface_two_id,
Artifact::Sweep(Sweep {
id: source_surface_two_id,
sub_type: SweepSubType::Extrusion,
path_id: path_two_id,
surface_ids: Vec::new(),
edge_ids: Vec::new(),
code_ref: source_code_ref,
trajectory_id: None,
method: kittycad_modeling_cmds::shared::ExtrudeMethod::Merge,
consumed: false,
}),
);
let cmd_id = Uuid::new_v4();
let command = ModelingCmd::from(
kcmc::each_cmd::SurfaceBlend::builder()
.surfaces(vec![
kcmc::shared::SurfaceEdgeReference::builder()
.object_id(Uuid::from(source_surface_one_id))
.edges(vec![
kcmc::shared::FractionOfEdge::builder()
.edge_id(Uuid::new_v4())
.lower_bound(0.0)
.upper_bound(1.0)
.build(),
])
.build(),
kcmc::shared::SurfaceEdgeReference::builder()
.object_id(Uuid::from(source_surface_two_id))
.edges(vec![
kcmc::shared::FractionOfEdge::builder()
.edge_id(Uuid::new_v4())
.lower_bound(0.0)
.upper_bound(1.0)
.build(),
])
.build(),
])
.build(),
);
let artifact_command = ArtifactCommand {
cmd_id,
range: SourceRange::synthetic(),
command,
};
let ast = crate::parsing::parse_str("", ModuleId::default()).unwrap();
let programs = crate::execution::ProgramLookup::new(ast, Default::default());
let updated = artifacts_to_update(
&artifacts,
&artifact_command,
&FnvHashMap::default(),
&FnvHashMap::default(),
&programs,
0,
&IndexMap::default(),
&FnvHashMap::default(),
)
.unwrap();
assert_eq!(updated.len(), 1);
let Artifact::Sweep(blend_sweep) = &updated[0] else {
panic!("Expected SurfaceBlend to create a sweep artifact, got: {updated:?}");
};
assert_eq!(blend_sweep.id, ArtifactId::new(cmd_id));
assert_eq!(blend_sweep.sub_type, SweepSubType::Blend);
assert_eq!(blend_sweep.path_id, path_one_id);
assert_eq!(blend_sweep.trajectory_id, Some(path_two_id));
assert_eq!(blend_sweep.method, kittycad_modeling_cmds::shared::ExtrudeMethod::New);
assert!(!blend_sweep.consumed);
}
#[test]
fn create_region_creates_region_path_sub_type() {
let origin_path_id = ArtifactId::new(Uuid::new_v4());
let origin_plane_id = ArtifactId::new(Uuid::new_v4());
let source_code_ref = CodeRef::placeholder(SourceRange::synthetic());
let mut artifacts = IndexMap::new();
artifacts.insert(
origin_path_id,
Artifact::Path(Path {
id: origin_path_id,
sub_type: PathSubType::Sketch,
plane_id: origin_plane_id,
seg_ids: Vec::new(),
consumed: false,
sweep_id: None,
trajectory_sweep_id: None,
solid2d_id: None,
code_ref: source_code_ref,
composite_solid_id: None,
sketch_block_id: None,
origin_path_id: None,
inner_path_id: None,
outer_path_id: None,
}),
);
let cmd_id = Uuid::new_v4();
let command = ModelingCmd::from(
kcmc::each_cmd::CreateRegion::builder()
.object_id(Uuid::from(origin_path_id))
.segment(Uuid::new_v4())
.intersection_segment(Uuid::new_v4())
.intersection_index(-1)
.curve_clockwise(false)
.build(),
);
let artifact_command = ArtifactCommand {
cmd_id,
range: SourceRange::synthetic(),
command,
};
let ast = crate::parsing::parse_str("", ModuleId::default()).unwrap();
let programs = crate::execution::ProgramLookup::new(ast, Default::default());
let updated = artifacts_to_update(
&artifacts,
&artifact_command,
&FnvHashMap::default(),
&FnvHashMap::default(),
&programs,
0,
&IndexMap::default(),
&FnvHashMap::default(),
)
.unwrap();
assert_eq!(updated.len(), 1);
let Artifact::Path(region_path) = &updated[0] else {
panic!("Expected CreateRegion to create a path artifact, got: {updated:?}");
};
assert_eq!(region_path.id, ArtifactId::new(cmd_id));
assert_eq!(region_path.sub_type, PathSubType::Region);
assert_eq!(region_path.plane_id, origin_plane_id);
assert_eq!(region_path.sketch_block_id, None);
assert_eq!(region_path.origin_path_id, Some(origin_path_id));
}
#[test]
fn primitive_edge_does_not_replace_existing_segment_artifact() {
let shared_id = ArtifactId::new(Uuid::new_v4());
let path_id = ArtifactId::new(Uuid::new_v4());
let solid_id = ArtifactId::new(Uuid::new_v4());
let mut map = IndexMap::new();
map.insert(
shared_id,
Artifact::Segment(Segment {
id: shared_id,
path_id,
original_seg_id: None,
surface_id: None,
edge_ids: Vec::new(),
edge_cut_id: None,
code_ref: CodeRef::placeholder(SourceRange::synthetic()),
common_surface_ids: Vec::new(),
}),
);
merge_artifact_into_map(
&mut map,
Artifact::PrimitiveEdge(PrimitiveEdge {
id: shared_id,
solid_id,
code_ref: CodeRef::placeholder(SourceRange::synthetic()),
}),
);
assert!(matches!(map.get(&shared_id), Some(Artifact::Segment(_))));
}
#[test]
fn primitive_face_does_not_replace_existing_cap_artifact() {
let shared_id = ArtifactId::new(Uuid::new_v4());
let sweep_id = ArtifactId::new(Uuid::new_v4());
let solid_id = ArtifactId::new(Uuid::new_v4());
let mut map = IndexMap::new();
map.insert(
shared_id,
Artifact::Cap(Cap {
id: shared_id,
sub_type: CapSubType::End,
edge_cut_edge_ids: Vec::new(),
sweep_id,
path_ids: Vec::new(),
face_code_ref: CodeRef::placeholder(SourceRange::synthetic()),
cmd_id: Uuid::new_v4(),
}),
);
merge_artifact_into_map(
&mut map,
Artifact::PrimitiveFace(PrimitiveFace {
id: shared_id,
solid_id,
code_ref: CodeRef::placeholder(SourceRange::synthetic()),
}),
);
assert!(matches!(map.get(&shared_id), Some(Artifact::Cap(_))));
}