1use indexmap::IndexMap;
2use kcl_error::SourceRange;
3use parse_display::Display;
4use parse_display::FromStr;
5use schemars::JsonSchema;
6use serde::Deserialize;
7use serde::Serialize;
8use serde::ser::SerializeSeq;
9use uuid::Uuid;
10
11use crate::ArtifactId;
12use crate::NodePath;
13use crate::ObjectId;
14use crate::UnitLength;
15
16pub type DummyPathToNode = Vec<()>;
17
18fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
19where
20 S: serde::Serializer,
21{
22 let seq = serializer.serialize_seq(Some(0))?;
23 seq.end()
24}
25
26#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
27#[ts(export_to = "Artifact.ts")]
28#[serde(rename_all = "camelCase")]
29pub struct CodeRef {
30 pub range: SourceRange,
31 pub node_path: NodePath,
32 #[serde(default, serialize_with = "serialize_dummy_path_to_node")]
34 #[ts(type = "Array<[string | number, string]>")]
35 pub path_to_node: DummyPathToNode,
36}
37
38impl CodeRef {
39 pub fn placeholder(range: SourceRange) -> Self {
40 Self {
41 range,
42 node_path: Default::default(),
43 path_to_node: Vec::new(),
44 }
45 }
46}
47
48#[derive(Debug, Hash, Eq, Copy, Clone, Deserialize, Serialize, JsonSchema, PartialEq, ts_rs::TS, Display, FromStr)]
49#[ts(export)]
50#[serde(rename_all = "camelCase")]
51pub enum PlaneName {
52 #[display("XY")]
54 Xy,
55 #[display("-XY")]
57 NegXy,
58 #[display("XZ")]
60 Xz,
61 #[display("-XZ")]
63 NegXz,
64 #[display("YZ")]
66 Yz,
67 #[display("-YZ")]
69 NegYz,
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default, ts_rs::TS)]
73#[ts(export_to = "Artifact.ts")]
74pub struct ArtifactPoint3d {
77 pub x: f64,
78 pub y: f64,
79 pub z: f64,
80 pub units: Option<UnitLength>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
84#[ts(export_to = "Artifact.ts")]
85#[serde(rename_all = "camelCase")]
86pub struct ArtifactPlaneInfo {
89 pub origin: ArtifactPoint3d,
90 pub x_axis: ArtifactPoint3d,
91 pub y_axis: ArtifactPoint3d,
92 pub z_axis: ArtifactPoint3d,
93}
94
95#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
96#[ts(export_to = "Artifact.ts")]
97#[serde(rename_all = "snake_case")]
98pub enum ArtifactSweepMethod {
101 New,
102 Merge,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
106#[ts(export_to = "Artifact.ts")]
107#[serde(rename_all = "camelCase")]
108pub struct CompositeSolid {
109 pub id: ArtifactId,
110 pub consumed: bool,
112 pub sub_type: CompositeSolidSubType,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub output_index: Option<usize>,
117 pub solid_ids: Vec<ArtifactId>,
119 pub tool_ids: Vec<ArtifactId>,
121 pub code_ref: CodeRef,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub composite_solid_id: Option<ArtifactId>,
126 #[serde(default, skip_serializing_if = "Vec::is_empty")]
128 pub pattern_ids: Vec<ArtifactId>,
129}
130
131#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
132#[ts(export_to = "Artifact.ts")]
133#[serde(rename_all = "camelCase")]
134pub enum CompositeSolidSubType {
135 Intersect,
136 Subtract,
137 Split,
138 Union,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
142#[ts(export_to = "Artifact.ts")]
143#[serde(rename_all = "camelCase")]
144pub struct Plane {
145 pub id: ArtifactId,
146 pub path_ids: Vec<ArtifactId>,
147 pub code_ref: CodeRef,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
151#[ts(export_to = "Artifact.ts")]
152#[serde(rename_all = "camelCase")]
153pub struct Path {
154 pub id: ArtifactId,
155 pub sub_type: PathSubType,
156 pub plane_id: ArtifactId,
157 pub seg_ids: Vec<ArtifactId>,
158 pub consumed: bool,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub sweep_id: Option<ArtifactId>,
164 pub trajectory_sweep_id: Option<ArtifactId>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub solid2d_id: Option<ArtifactId>,
168 pub code_ref: CodeRef,
169 #[serde(default, skip_serializing_if = "Option::is_none")]
172 pub composite_solid_id: Option<ArtifactId>,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub sketch_block_id: Option<ArtifactId>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub origin_path_id: Option<ArtifactId>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub inner_path_id: Option<ArtifactId>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
187 pub outer_path_id: Option<ArtifactId>,
188 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 pub pattern_ids: Vec<ArtifactId>,
191}
192
193#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
194#[ts(export_to = "Artifact.ts")]
195#[serde(rename_all = "camelCase")]
196pub enum PathSubType {
197 Sketch,
198 Region,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
202#[ts(export_to = "Artifact.ts")]
203#[serde(rename_all = "camelCase")]
204pub struct Segment {
205 pub id: ArtifactId,
206 pub path_id: ArtifactId,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub original_seg_id: Option<ArtifactId>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub surface_id: Option<ArtifactId>,
213 pub edge_ids: Vec<ArtifactId>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub edge_cut_id: Option<ArtifactId>,
216 pub code_ref: CodeRef,
217 pub common_surface_ids: Vec<ArtifactId>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
222#[ts(export_to = "Artifact.ts")]
223#[serde(rename_all = "camelCase")]
224pub struct Sweep {
225 pub id: ArtifactId,
226 pub sub_type: SweepSubType,
227 pub path_id: ArtifactId,
228 pub surface_ids: Vec<ArtifactId>,
229 pub edge_ids: Vec<ArtifactId>,
230 pub code_ref: CodeRef,
231 pub trajectory_id: Option<ArtifactId>,
235 pub method: ArtifactSweepMethod,
236 pub consumed: bool,
238 #[serde(default, skip_serializing_if = "Vec::is_empty")]
240 pub pattern_ids: Vec<ArtifactId>,
241}
242
243#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
244#[ts(export_to = "Artifact.ts")]
245#[serde(rename_all = "camelCase")]
246pub enum SweepSubType {
247 Extrusion,
248 ExtrusionTwist,
249 Revolve,
250 RevolveAboutEdge,
251 Loft,
252 Blend,
253 Sweep,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
257#[ts(export_to = "Artifact.ts")]
258#[serde(rename_all = "camelCase")]
259pub struct Solid2d {
260 pub id: ArtifactId,
261 pub path_id: ArtifactId,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
265#[ts(export_to = "Artifact.ts")]
266#[serde(rename_all = "camelCase")]
267pub struct PrimitiveFace {
268 pub id: ArtifactId,
269 pub solid_id: ArtifactId,
270 pub code_ref: CodeRef,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
274#[ts(export_to = "Artifact.ts")]
275#[serde(rename_all = "camelCase")]
276pub struct PrimitiveEdge {
277 pub id: ArtifactId,
278 pub solid_id: ArtifactId,
279 pub code_ref: CodeRef,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
283#[ts(export_to = "Artifact.ts")]
284#[serde(rename_all = "camelCase")]
285pub struct PlaneOfFace {
286 pub id: ArtifactId,
287 pub face_id: ArtifactId,
288 pub code_ref: CodeRef,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
292#[ts(export_to = "Artifact.ts")]
293#[serde(rename_all = "camelCase")]
294pub struct StartSketchOnFace {
295 pub id: ArtifactId,
296 pub face_id: ArtifactId,
297 pub code_ref: CodeRef,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
301#[ts(export_to = "Artifact.ts")]
302#[serde(rename_all = "camelCase")]
303pub struct StartSketchOnPlane {
304 pub id: ArtifactId,
305 pub plane_id: ArtifactId,
306 pub code_ref: CodeRef,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
310#[ts(export_to = "Artifact.ts")]
311#[serde(rename_all = "camelCase")]
312pub struct SketchBlock {
313 pub id: ArtifactId,
314 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub standard_plane: Option<PlaneName>,
317 #[serde(default, skip_serializing_if = "Option::is_none")]
319 pub plane_id: Option<ArtifactId>,
320 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub plane_info: Option<ArtifactPlaneInfo>,
323 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub path_id: Option<ArtifactId>,
328 pub code_ref: CodeRef,
329 pub sketch_id: ObjectId,
331}
332
333#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
334#[ts(export_to = "Artifact.ts")]
335#[serde(rename_all = "camelCase")]
336pub enum SketchBlockConstraintType {
337 Angle,
338 Coincident,
339 Distance,
340 Diameter,
341 EqualRadius,
342 Fixed,
343 HorizontalDistance,
344 VerticalDistance,
345 Horizontal,
346 LinesEqualLength,
347 Midpoint,
348 Parallel,
349 Perpendicular,
350 Radius,
351 Symmetric,
352 Tangent,
353 Vertical,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
357#[ts(export_to = "Artifact.ts")]
358#[serde(rename_all = "camelCase")]
359pub struct SketchBlockConstraint {
360 pub id: ArtifactId,
361 pub sketch_id: ObjectId,
363 pub constraint_id: ObjectId,
365 pub constraint_type: SketchBlockConstraintType,
366 pub code_ref: CodeRef,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
370#[ts(export_to = "Artifact.ts")]
371#[serde(rename_all = "camelCase")]
372pub struct Wall {
373 pub id: ArtifactId,
374 pub seg_id: ArtifactId,
375 pub edge_cut_edge_ids: Vec<ArtifactId>,
376 pub sweep_id: ArtifactId,
377 pub path_ids: Vec<ArtifactId>,
378 pub face_code_ref: CodeRef,
381 pub cmd_id: Uuid,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
386#[ts(export_to = "Artifact.ts")]
387#[serde(rename_all = "camelCase")]
388pub struct Cap {
389 pub id: ArtifactId,
390 pub sub_type: CapSubType,
391 pub edge_cut_edge_ids: Vec<ArtifactId>,
392 pub sweep_id: ArtifactId,
393 pub path_ids: Vec<ArtifactId>,
394 pub face_code_ref: CodeRef,
397 pub cmd_id: Uuid,
399}
400
401#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
402#[ts(export_to = "Artifact.ts")]
403#[serde(rename_all = "camelCase")]
404pub enum CapSubType {
405 Start,
406 End,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
410#[ts(export_to = "Artifact.ts")]
411#[serde(rename_all = "camelCase")]
412pub struct SweepEdge {
413 pub id: ArtifactId,
414 pub sub_type: SweepEdgeSubType,
415 pub seg_id: ArtifactId,
416 pub cmd_id: Uuid,
417 #[serde(skip)]
419 pub index: usize,
420 pub sweep_id: ArtifactId,
421 pub common_surface_ids: Vec<ArtifactId>,
422}
423
424#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
425#[ts(export_to = "Artifact.ts")]
426#[serde(rename_all = "camelCase")]
427pub enum SweepEdgeSubType {
428 Opposite,
429 Adjacent,
430 PreviousAdjacent,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
434#[ts(export_to = "Artifact.ts")]
435#[serde(rename_all = "camelCase")]
436pub struct EdgeCut {
437 pub id: ArtifactId,
438 pub sub_type: EdgeCutSubType,
439 pub consumed_edge_id: ArtifactId,
440 pub edge_ids: Vec<ArtifactId>,
441 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub surface_id: Option<ArtifactId>,
443 pub code_ref: CodeRef,
444}
445
446#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
447#[ts(export_to = "Artifact.ts")]
448#[serde(rename_all = "camelCase")]
449pub enum EdgeCutSubType {
450 Fillet,
451 Chamfer,
452 Custom,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
456#[ts(export_to = "Artifact.ts")]
457#[serde(rename_all = "camelCase")]
458pub struct EdgeCutEdge {
459 pub id: ArtifactId,
460 pub edge_cut_id: ArtifactId,
461 pub surface_id: ArtifactId,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
465#[ts(export_to = "Artifact.ts")]
466#[serde(rename_all = "camelCase")]
467pub struct Helix {
468 pub id: ArtifactId,
469 pub axis_id: Option<ArtifactId>,
472 pub code_ref: CodeRef,
473 pub trajectory_sweep_id: Option<ArtifactId>,
475 pub consumed: bool,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
480#[ts(export_to = "Artifact.ts")]
481#[serde(rename_all = "camelCase")]
482pub struct GdtAnnotationArtifact {
483 pub id: ArtifactId,
484 pub code_ref: CodeRef,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
488#[ts(export_to = "Artifact.ts")]
489#[serde(rename_all = "camelCase")]
490pub struct Pattern {
491 pub id: ArtifactId,
492 pub sub_type: PatternSubType,
493 pub source_id: ArtifactId,
495 pub copy_ids: Vec<ArtifactId>,
497 pub copy_face_ids: Vec<ArtifactId>,
499 pub copy_edge_ids: Vec<ArtifactId>,
501 pub code_ref: CodeRef,
502}
503
504#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
505#[ts(export_to = "Artifact.ts")]
506#[serde(rename_all = "camelCase")]
507pub enum PatternSubType {
508 Circular,
509 Linear,
510 Transform,
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
514#[ts(export_to = "Artifact.ts")]
515#[serde(tag = "type", rename_all = "camelCase")]
516pub enum Artifact {
517 CompositeSolid(CompositeSolid),
518 Plane(Plane),
519 Path(Path),
520 Segment(Segment),
521 Solid2d(Solid2d),
522 PrimitiveFace(PrimitiveFace),
523 PrimitiveEdge(PrimitiveEdge),
524 PlaneOfFace(PlaneOfFace),
525 StartSketchOnFace(StartSketchOnFace),
526 StartSketchOnPlane(StartSketchOnPlane),
527 SketchBlock(SketchBlock),
528 SketchBlockConstraint(SketchBlockConstraint),
529 Sweep(Sweep),
530 Wall(Wall),
531 Cap(Cap),
532 SweepEdge(SweepEdge),
533 EdgeCut(EdgeCut),
534 EdgeCutEdge(EdgeCutEdge),
535 Helix(Helix),
536 GdtAnnotation(GdtAnnotationArtifact),
537 Pattern(Pattern),
538}
539
540impl Artifact {
541 pub fn id(&self) -> ArtifactId {
542 match self {
543 Self::CompositeSolid(a) => a.id,
544 Self::Plane(a) => a.id,
545 Self::Path(a) => a.id,
546 Self::Segment(a) => a.id,
547 Self::Solid2d(a) => a.id,
548 Self::PrimitiveFace(a) => a.id,
549 Self::PrimitiveEdge(a) => a.id,
550 Self::PlaneOfFace(a) => a.id,
551 Self::StartSketchOnFace(a) => a.id,
552 Self::StartSketchOnPlane(a) => a.id,
553 Self::SketchBlock(a) => a.id,
554 Self::SketchBlockConstraint(a) => a.id,
555 Self::Sweep(a) => a.id,
556 Self::Wall(a) => a.id,
557 Self::Cap(a) => a.id,
558 Self::SweepEdge(a) => a.id,
559 Self::EdgeCut(a) => a.id,
560 Self::EdgeCutEdge(a) => a.id,
561 Self::Helix(a) => a.id,
562 Self::GdtAnnotation(a) => a.id,
563 Self::Pattern(a) => a.id,
564 }
565 }
566
567 pub fn code_ref(&self) -> Option<&CodeRef> {
570 match self {
571 Self::CompositeSolid(a) => Some(&a.code_ref),
572 Self::Plane(a) => Some(&a.code_ref),
573 Self::Path(a) => Some(&a.code_ref),
574 Self::Segment(a) => Some(&a.code_ref),
575 Self::Solid2d(_) => None,
576 Self::PrimitiveFace(a) => Some(&a.code_ref),
577 Self::PrimitiveEdge(a) => Some(&a.code_ref),
578 Self::PlaneOfFace(a) => Some(&a.code_ref),
579 Self::StartSketchOnFace(a) => Some(&a.code_ref),
580 Self::StartSketchOnPlane(a) => Some(&a.code_ref),
581 Self::SketchBlock(a) => Some(&a.code_ref),
582 Self::SketchBlockConstraint(a) => Some(&a.code_ref),
583 Self::Sweep(a) => Some(&a.code_ref),
584 Self::Wall(_) | Self::Cap(_) | Self::SweepEdge(_) => None,
585 Self::EdgeCut(a) => Some(&a.code_ref),
586 Self::EdgeCutEdge(_) => None,
587 Self::Helix(a) => Some(&a.code_ref),
588 Self::GdtAnnotation(a) => Some(&a.code_ref),
589 Self::Pattern(a) => Some(&a.code_ref),
590 }
591 }
592
593 pub fn face_code_ref(&self) -> Option<&CodeRef> {
596 match self {
597 Self::PrimitiveFace(a) => Some(&a.code_ref),
598 Self::Wall(a) => Some(&a.face_code_ref),
599 Self::Cap(a) => Some(&a.face_code_ref),
600 _ => None,
601 }
602 }
603}
604
605#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, ts_rs::TS)]
606#[ts(export_to = "Artifact.ts")]
607#[serde(rename_all = "camelCase")]
608pub struct ArtifactGraph {
609 map: IndexMap<ArtifactId, Artifact>,
610 item_count: usize,
611}
612
613impl ArtifactGraph {
614 pub fn from_parts(map: IndexMap<ArtifactId, Artifact>, item_count: usize) -> Self {
615 Self { map, item_count }
616 }
617
618 pub fn into_parts(self) -> (IndexMap<ArtifactId, Artifact>, usize) {
619 (self.map, self.item_count)
620 }
621
622 pub fn item_count(&self) -> usize {
623 self.item_count
624 }
625
626 pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
627 self.map.get(id)
628 }
629
630 pub fn len(&self) -> usize {
631 self.map.len()
632 }
633
634 pub fn is_empty(&self) -> bool {
635 self.map.is_empty()
636 }
637
638 pub fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
639 self.map.iter()
640 }
641
642 pub fn values(&self) -> impl Iterator<Item = &Artifact> {
643 self.map.values()
644 }
645
646 pub fn clear(&mut self) {
647 self.map.clear();
648 self.item_count = 0;
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 #[test]
657 fn default_artifact_graph_json_round_trip() {
658 let graph = ArtifactGraph::default();
659 let json = serde_json::to_string(&graph).unwrap();
660
661 assert_eq!(json, r#"{"map":{},"itemCount":0}"#);
662 assert_eq!(serde_json::from_str::<ArtifactGraph>(&json).unwrap(), graph);
663 }
664
665 #[test]
666 fn artifact_sweep_method_uses_engine_json_shape() {
667 assert_eq!(serde_json::to_string(&ArtifactSweepMethod::New).unwrap(), r#""new""#);
668 assert_eq!(
669 serde_json::to_string(&ArtifactSweepMethod::Merge).unwrap(),
670 r#""merge""#
671 );
672 }
673}