Skip to main content

kcl_api/
artifact.rs

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    // TODO: We should implement this in Rust.
33    #[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    /// The XY plane.
53    #[display("XY")]
54    Xy,
55    /// The opposite side of the XY plane.
56    #[display("-XY")]
57    NegXy,
58    /// The XZ plane.
59    #[display("XZ")]
60    Xz,
61    /// The opposite side of the XZ plane.
62    #[display("-XZ")]
63    NegXz,
64    /// The YZ plane.
65    #[display("YZ")]
66    Yz,
67    /// The opposite side of the YZ plane.
68    #[display("-YZ")]
69    NegYz,
70}
71
72/// API-owned point data used by artifact payloads so the wire model does not
73/// depend on kcl-lib's execution geometry types.
74#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Default, ts_rs::TS)]
75#[ts(export_to = "Artifact.ts")]
76pub struct ArtifactPoint3d {
77    pub x: f64,
78    pub y: f64,
79    pub z: f64,
80    pub units: Option<UnitLength>,
81}
82
83/// API-owned plane data used by artifacts. It mirrors kcl-lib's evaluated
84/// plane JSON shape while keeping kcl-api independent of execution internals.
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
86#[ts(export_to = "Artifact.ts")]
87#[serde(rename_all = "camelCase")]
88pub struct ArtifactPlaneInfo {
89    pub origin: ArtifactPoint3d,
90    pub x_axis: ArtifactPoint3d,
91    pub y_axis: ArtifactPoint3d,
92    pub z_axis: ArtifactPoint3d,
93}
94
95/// API-owned sweep method equivalent to the engine extrusion method, avoiding
96/// a kcl-api dependency on kittycad-modeling-cmds.
97#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
98#[ts(export_to = "Artifact.ts")]
99#[serde(rename_all = "snake_case")]
100pub enum ArtifactSweepMethod {
101    New,
102    Merge,
103}
104
105/// API-owned camera orientation used by named-view artifacts, equivalent to
106/// the KCL enum `std::view::Orientation`. Keeping it here means the wire model
107/// does not depend on kcl-lib's execution types.
108#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
109#[ts(export_to = "Artifact.ts")]
110#[serde(rename_all = "camelCase")]
111pub enum ArtifactOrientation {
112    Front,
113    Back,
114    Left,
115    Right,
116    Top,
117    Bottom,
118    Isometric,
119}
120
121/// API-owned camera projection used by named-view artifacts, equivalent to the
122/// KCL enum `std::view::Projection`. See [`ArtifactOrientation`].
123#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
124#[ts(export_to = "Artifact.ts")]
125#[serde(rename_all = "camelCase")]
126pub enum ArtifactProjection {
127    Orthographic,
128    Perspective,
129}
130
131/// API-owned visibility baseline used by named-view artifacts, equivalent to
132/// the KCL enum `std::view::Visibility`. `Show` means every object is visible
133/// except those the view's hide list names; `Hide` means the reverse.
134#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
135#[ts(export_to = "Artifact.ts")]
136#[serde(rename_all = "camelCase")]
137pub enum ArtifactVisibility {
138    Show,
139    Hide,
140}
141
142/// Where a named view's camera looks from: one variant per KCL constructor
143/// function, so a stored view can never carry both a curated orientation and a
144/// custom direction.
145#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
146#[ts(export_to = "Artifact.ts")]
147#[serde(tag = "type", rename_all = "camelCase")]
148pub enum ArtifactCameraLook {
149    /// A curated orientation, from `view::oriented`.
150    Oriented { orientation: ArtifactOrientation },
151    /// A custom look direction, from `view::directed`. Both vectors are
152    /// directions rather than positions, so their `units` are absent, and both
153    /// are already normalized.
154    Directed {
155        direction: ArtifactPoint3d,
156        up: ArtifactPoint3d,
157    },
158}
159
160/// API-owned camera intent used by named-view artifacts. It mirrors kcl-lib's
161/// `CameraView` JSON shape while keeping kcl-api independent of execution
162/// internals, as [`ArtifactPlaneInfo`] does for planes.
163///
164/// The values are intent, not a snapshot of engine camera state: an absent
165/// field is resolved by whichever consumer activates the view. Every length is
166/// in millimeters, converted when the view was constructed.
167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
168#[ts(export_to = "Artifact.ts")]
169#[serde(rename_all = "camelCase")]
170pub struct ArtifactCameraView {
171    pub look: ArtifactCameraLook,
172    /// The point the camera looks at, in millimeters. Absent means: center on
173    /// the bounds of the model at activation.
174    pub target: Option<ArtifactPoint3d>,
175    /// The distance from the camera to the target, in millimeters. Absent
176    /// means: fit the model at activation.
177    pub distance: Option<f64>,
178    pub projection: ArtifactProjection,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
182#[ts(export_to = "Artifact.ts")]
183#[serde(rename_all = "camelCase")]
184pub struct CompositeSolid {
185    pub id: ArtifactId,
186    /// Whether this artifact has been used in a subsequent operation
187    pub consumed: bool,
188    pub sub_type: CompositeSolidSubType,
189    /// Index of this output in the expression result, for operations that
190    /// return multiple selectable bodies from one KCL variable.
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub output_index: Option<usize>,
193    /// Constituent solids of the composite solid.
194    pub solid_ids: Vec<ArtifactId>,
195    /// Tool solids used for asymmetric operations like subtract.
196    pub tool_ids: Vec<ArtifactId>,
197    pub code_ref: CodeRef,
198    /// This is the ID of the composite solid that this is part of, if any, as a
199    /// composite solid can be used as input for another composite solid.
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub composite_solid_id: Option<ArtifactId>,
202    /// Pattern operations that use this composite solid as their source.
203    #[serde(default, skip_serializing_if = "Vec::is_empty")]
204    pub pattern_ids: Vec<ArtifactId>,
205}
206
207#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
208#[ts(export_to = "Artifact.ts")]
209#[serde(rename_all = "camelCase")]
210pub enum CompositeSolidSubType {
211    Intersect,
212    Subtract,
213    Split,
214    Union,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
218#[ts(export_to = "Artifact.ts")]
219#[serde(rename_all = "camelCase")]
220pub struct Plane {
221    pub id: ArtifactId,
222    pub path_ids: Vec<ArtifactId>,
223    pub code_ref: CodeRef,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
227#[ts(export_to = "Artifact.ts")]
228#[serde(rename_all = "camelCase")]
229pub struct Path {
230    pub id: ArtifactId,
231    pub sub_type: PathSubType,
232    pub plane_id: ArtifactId,
233    pub seg_ids: Vec<ArtifactId>,
234    /// Whether this artifact has been used in a subsequent operation
235    pub consumed: bool,
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    /// The sweep, if any, that this Path serves as the base path for.
238    /// corresponds to `path_id` on the Sweep.
239    pub sweep_id: Option<ArtifactId>,
240    /// The sweep, if any, that this Path serves as the trajectory for.
241    pub trajectory_sweep_id: Option<ArtifactId>,
242    #[serde(default, skip_serializing_if = "Option::is_none")]
243    pub solid2d_id: Option<ArtifactId>,
244    pub code_ref: CodeRef,
245    /// This is the ID of the composite solid that this is part of, if any, as
246    /// this can be used as input for another composite solid.
247    #[serde(default, skip_serializing_if = "Option::is_none")]
248    pub composite_solid_id: Option<ArtifactId>,
249    /// For sketch paths, the ID of the sketch block this path was created
250    /// from. `None` for region paths and paths created in other ways.
251    #[serde(default, skip_serializing_if = "Option::is_none")]
252    pub sketch_block_id: Option<ArtifactId>,
253    /// For region paths, the ID of the sketch path this region was created
254    /// from. `None` for sketch paths.
255    #[serde(default, skip_serializing_if = "Option::is_none")]
256    pub origin_path_id: Option<ArtifactId>,
257    /// The hole, if any, from a subtract2d() call.
258    #[serde(default, skip_serializing_if = "Option::is_none")]
259    pub inner_path_id: Option<ArtifactId>,
260    /// The `Path` that this is a hole of, if any. The inverse link of
261    /// `inner_path_id`.
262    #[serde(default, skip_serializing_if = "Option::is_none")]
263    pub outer_path_id: Option<ArtifactId>,
264    /// Pattern operations that use this path as their source.
265    #[serde(default, skip_serializing_if = "Vec::is_empty")]
266    pub pattern_ids: Vec<ArtifactId>,
267}
268
269#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
270#[ts(export_to = "Artifact.ts")]
271#[serde(rename_all = "camelCase")]
272pub enum PathSubType {
273    Sketch,
274    Region,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
278#[ts(export_to = "Artifact.ts")]
279#[serde(rename_all = "camelCase")]
280pub struct Segment {
281    pub id: ArtifactId,
282    pub path_id: ArtifactId,
283    /// The original segment this segment was cloned from, if any. For clones
284    /// of clones, this continues to point to the originating segment.
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub source_segment_id: Option<ArtifactId>,
287    /// If this artifact is a segment in a region, the segment in the original
288    /// sketch that this was derived from.
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub original_seg_id: Option<ArtifactId>,
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub surface_id: Option<ArtifactId>,
293    pub edge_ids: Vec<ArtifactId>,
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub edge_cut_id: Option<ArtifactId>,
296    pub code_ref: CodeRef,
297    pub common_surface_ids: Vec<ArtifactId>,
298}
299
300/// A sweep is a more generic term for extrude, revolve, loft, sweep, and blend.
301#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
302#[ts(export_to = "Artifact.ts")]
303#[serde(rename_all = "camelCase")]
304pub struct Sweep {
305    pub id: ArtifactId,
306    pub sub_type: SweepSubType,
307    #[serde(default, skip_serializing_if = "Option::is_none")]
308    pub path_id: Option<ArtifactId>,
309    pub surface_ids: Vec<ArtifactId>,
310    pub edge_ids: Vec<ArtifactId>,
311    pub code_ref: CodeRef,
312    /// The original sweep this body was cloned from, if any. For clones of
313    /// clones, this continues to point to the originating sweep.
314    #[serde(default, skip_serializing_if = "Option::is_none")]
315    pub source_sweep_id: Option<ArtifactId>,
316    /// ID of trajectory path for sweep, if any
317    /// Only applicable to SweepSubType::Sweep and SweepSubType::Blend, which
318    /// can use a second path-like input
319    pub trajectory_id: Option<ArtifactId>,
320    pub method: ArtifactSweepMethod,
321    /// Whether this artifact has been used in a subsequent operation
322    pub consumed: bool,
323    /// Pattern operations that use this sweep as their source.
324    #[serde(default, skip_serializing_if = "Vec::is_empty")]
325    pub pattern_ids: Vec<ArtifactId>,
326}
327
328#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
329#[ts(export_to = "Artifact.ts")]
330#[serde(rename_all = "camelCase")]
331pub enum SweepSubType {
332    Extrusion,
333    ExtrusionTwist,
334    Revolve,
335    RevolveAboutEdge,
336    Loft,
337    Blend,
338    Sweep,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
342#[ts(export_to = "Artifact.ts")]
343#[serde(rename_all = "camelCase")]
344pub struct Solid2d {
345    pub id: ArtifactId,
346    pub path_id: ArtifactId,
347}
348
349#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
350#[ts(export_to = "Artifact.ts")]
351#[serde(rename_all = "camelCase")]
352pub struct PrimitiveFace {
353    pub id: ArtifactId,
354    pub solid_id: ArtifactId,
355    pub code_ref: CodeRef,
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
359#[ts(export_to = "Artifact.ts")]
360#[serde(rename_all = "camelCase")]
361pub struct PrimitiveEdge {
362    pub id: ArtifactId,
363    pub solid_id: ArtifactId,
364    pub code_ref: CodeRef,
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
368#[ts(export_to = "Artifact.ts")]
369#[serde(rename_all = "camelCase")]
370pub struct PlaneOfFace {
371    pub id: ArtifactId,
372    pub face_id: ArtifactId,
373    pub code_ref: CodeRef,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
377#[ts(export_to = "Artifact.ts")]
378#[serde(rename_all = "camelCase")]
379pub struct StartSketchOnFace {
380    pub id: ArtifactId,
381    pub face_id: ArtifactId,
382    pub code_ref: CodeRef,
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 StartSketchOnPlane {
389    pub id: ArtifactId,
390    pub plane_id: ArtifactId,
391    pub code_ref: CodeRef,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
395#[ts(export_to = "Artifact.ts")]
396#[serde(rename_all = "camelCase")]
397pub struct SketchBlock {
398    pub id: ArtifactId,
399    /// The semantic standard plane name when the sketch block is on a standard plane.
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub standard_plane: Option<PlaneName>,
402    /// The concrete plane artifact ID backing the sketch block, when one is available.
403    #[serde(default, skip_serializing_if = "Option::is_none")]
404    pub plane_id: Option<ArtifactId>,
405    /// The evaluated plane data backing the sketch block, when the sketch is on a plane.
406    #[serde(default, skip_serializing_if = "Option::is_none")]
407    pub plane_info: Option<ArtifactPlaneInfo>,
408    /// The path artifact ID created from the sketch block, if there is one.
409    /// There are edge cases when a path isn't created, like when there are no
410    /// segments.
411    #[serde(default, skip_serializing_if = "Option::is_none")]
412    pub path_id: Option<ArtifactId>,
413    pub code_ref: CodeRef,
414    /// The sketch ID (ObjectId) for the sketch scene object.
415    pub sketch_id: ObjectId,
416}
417
418#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
419#[ts(export_to = "Artifact.ts")]
420#[serde(rename_all = "camelCase")]
421pub enum SketchBlockConstraintType {
422    Angle,
423    Coincident,
424    Distance,
425    Diameter,
426    EqualRadius,
427    Fixed,
428    HorizontalDistance,
429    VerticalDistance,
430    Horizontal,
431    LinesEqualLength,
432    Midpoint,
433    Parallel,
434    Perpendicular,
435    Radius,
436    Symmetric,
437    Tangent,
438    Vertical,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
442#[ts(export_to = "Artifact.ts")]
443#[serde(rename_all = "camelCase")]
444pub struct SketchBlockConstraint {
445    pub id: ArtifactId,
446    /// The sketch ID (ObjectId) that owns this constraint.
447    pub sketch_id: ObjectId,
448    /// The constraint ID (ObjectId) for the constraint scene object.
449    pub constraint_id: ObjectId,
450    pub constraint_type: SketchBlockConstraintType,
451    pub code_ref: CodeRef,
452}
453
454#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
455#[ts(export_to = "Artifact.ts")]
456#[serde(rename_all = "camelCase")]
457pub struct Wall {
458    pub id: ArtifactId,
459    pub seg_id: ArtifactId,
460    pub edge_cut_edge_ids: Vec<ArtifactId>,
461    pub sweep_id: ArtifactId,
462    pub path_ids: Vec<ArtifactId>,
463    /// This is for the sketch-on-face plane, not for the wall itself.  Traverse
464    /// to the extrude and/or segment to get the wall's code_ref.
465    pub face_code_ref: CodeRef,
466    /// The command ID that got the data for this wall. Used for stable sorting.
467    pub cmd_id: Uuid,
468}
469
470#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
471#[ts(export_to = "Artifact.ts")]
472#[serde(rename_all = "camelCase")]
473pub struct Cap {
474    pub id: ArtifactId,
475    pub sub_type: CapSubType,
476    pub edge_cut_edge_ids: Vec<ArtifactId>,
477    pub sweep_id: ArtifactId,
478    pub path_ids: Vec<ArtifactId>,
479    /// This is for the sketch-on-face plane, not for the cap itself.  Traverse
480    /// to the extrude and/or segment to get the cap's code_ref.
481    pub face_code_ref: CodeRef,
482    /// The command ID that got the data for this cap. Used for stable sorting.
483    pub cmd_id: Uuid,
484}
485
486#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
487#[ts(export_to = "Artifact.ts")]
488#[serde(rename_all = "camelCase")]
489pub enum CapSubType {
490    Start,
491    End,
492}
493
494#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
495#[ts(export_to = "Artifact.ts")]
496#[serde(rename_all = "camelCase")]
497pub struct SweepEdge {
498    pub id: ArtifactId,
499    pub sub_type: SweepEdgeSubType,
500    pub seg_id: ArtifactId,
501    pub cmd_id: Uuid,
502    // This is only used for sorting, not for the actual artifact.
503    #[serde(skip)]
504    pub index: usize,
505    pub sweep_id: ArtifactId,
506    pub common_surface_ids: Vec<ArtifactId>,
507}
508
509#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
510#[ts(export_to = "Artifact.ts")]
511#[serde(rename_all = "camelCase")]
512pub enum SweepEdgeSubType {
513    Opposite,
514    Adjacent,
515    PreviousAdjacent,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
519#[ts(export_to = "Artifact.ts")]
520#[serde(rename_all = "camelCase")]
521pub struct EdgeCut {
522    pub id: ArtifactId,
523    pub sub_type: EdgeCutSubType,
524    /// Index of the source selector in an `edges` argument, when this edge cut
525    /// was created from an edge-reference command.
526    #[serde(default, skip_serializing_if = "Option::is_none")]
527    pub source_selector_index: Option<usize>,
528    #[serde(default, skip_serializing_if = "Option::is_none")]
529    pub consumed_edge_id: Option<ArtifactId>,
530    pub edge_ids: Vec<ArtifactId>,
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub surface_id: Option<ArtifactId>,
533    pub code_ref: CodeRef,
534}
535
536#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
537#[ts(export_to = "Artifact.ts")]
538#[serde(rename_all = "camelCase")]
539pub enum EdgeCutSubType {
540    Fillet,
541    Chamfer,
542    Custom,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
546#[ts(export_to = "Artifact.ts")]
547#[serde(rename_all = "camelCase")]
548pub struct EdgeCutEdge {
549    pub id: ArtifactId,
550    pub edge_cut_id: ArtifactId,
551    pub surface_id: ArtifactId,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
555#[ts(export_to = "Artifact.ts")]
556#[serde(rename_all = "camelCase")]
557pub struct Helix {
558    pub id: ArtifactId,
559    /// The axis of the helix.  Currently this is always an edge ID, but we may
560    /// add axes to the graph.
561    pub axis_id: Option<ArtifactId>,
562    pub code_ref: CodeRef,
563    /// The sweep, if any, that this Helix serves as the trajectory for.
564    pub trajectory_sweep_id: Option<ArtifactId>,
565    /// Whether this artifact has been used in a subsequent operation
566    pub consumed: bool,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
570#[ts(export_to = "Artifact.ts")]
571#[serde(rename_all = "camelCase")]
572pub struct ImportedGeometryArtifact {
573    pub id: ArtifactId,
574    pub code_ref: CodeRef,
575    #[serde(default)]
576    pub consumed: bool,
577}
578
579#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
580#[ts(export_to = "Artifact.ts")]
581#[serde(rename_all = "camelCase")]
582pub struct GdtAnnotationArtifact {
583    pub id: ArtifactId,
584    pub code_ref: CodeRef,
585    #[serde(default)]
586    pub consumed: bool,
587}
588
589/// A named view declared in KCL by `view::named`: a display name, camera intent
590/// and the objects the view shows or hides.
591///
592/// The view is data for a consumer to activate later, so this artifact holds no
593/// engine geometry and its creation sends no engine command. The objects are
594/// named by ARTIFACT id, so a consumer resolves each one through the artifact
595/// graph; an extruded solid's engine id differs from its artifact id, and the
596/// translation belongs to whoever applies the view.
597#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
598#[ts(export_to = "Artifact.ts")]
599#[serde(rename_all = "camelCase")]
600pub struct NamedViewArtifact {
601    pub id: ArtifactId,
602    /// The display name the author gave the view. Required, and unique among
603    /// the views declared by the same module.
604    pub name: String,
605    pub camera: ArtifactCameraView,
606    /// Whether objects start visible or hidden, which decides which of the two
607    /// lists below can act.
608    pub baseline: ArtifactVisibility,
609    /// Artifacts the view shows. Meaningful under a `Hide` baseline, where they
610    /// are the only visible objects.
611    pub show_ids: Vec<ArtifactId>,
612    /// Artifacts the view hides. Meaningful under a `Show` baseline, where they
613    /// are the only hidden objects.
614    pub hide_ids: Vec<ArtifactId>,
615    pub code_ref: CodeRef,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
619#[ts(export_to = "Artifact.ts")]
620#[serde(rename_all = "camelCase")]
621pub struct Pattern {
622    pub id: ArtifactId,
623    pub sub_type: PatternSubType,
624    /// Geometry artifact that was the source of the pattern operation.
625    pub source_id: ArtifactId,
626    /// IDs of copied top-level objects created by the pattern operation.
627    pub copy_ids: Vec<ArtifactId>,
628    /// IDs of copied faces created by the pattern operation.
629    pub copy_face_ids: Vec<ArtifactId>,
630    /// IDs of copied edges created by the pattern operation.
631    pub copy_edge_ids: Vec<ArtifactId>,
632    pub code_ref: CodeRef,
633}
634
635#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
636#[ts(export_to = "Artifact.ts")]
637#[serde(rename_all = "camelCase")]
638pub enum PatternSubType {
639    Circular,
640    Linear,
641    Transform,
642}
643
644#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
645#[ts(export_to = "Artifact.ts")]
646#[serde(tag = "type", rename_all = "camelCase")]
647pub enum Artifact {
648    CompositeSolid(CompositeSolid),
649    Plane(Plane),
650    Path(Path),
651    Segment(Segment),
652    Solid2d(Solid2d),
653    PrimitiveFace(PrimitiveFace),
654    PrimitiveEdge(PrimitiveEdge),
655    PlaneOfFace(PlaneOfFace),
656    StartSketchOnFace(StartSketchOnFace),
657    StartSketchOnPlane(StartSketchOnPlane),
658    SketchBlock(SketchBlock),
659    SketchBlockConstraint(SketchBlockConstraint),
660    Sweep(Sweep),
661    Wall(Wall),
662    Cap(Cap),
663    SweepEdge(SweepEdge),
664    EdgeCut(EdgeCut),
665    EdgeCutEdge(EdgeCutEdge),
666    Helix(Helix),
667    ImportedGeometry(ImportedGeometryArtifact),
668    GdtAnnotation(GdtAnnotationArtifact),
669    NamedView(NamedViewArtifact),
670    Pattern(Pattern),
671}
672
673impl Artifact {
674    pub fn id(&self) -> ArtifactId {
675        match self {
676            Self::CompositeSolid(a) => a.id,
677            Self::Plane(a) => a.id,
678            Self::Path(a) => a.id,
679            Self::Segment(a) => a.id,
680            Self::Solid2d(a) => a.id,
681            Self::PrimitiveFace(a) => a.id,
682            Self::PrimitiveEdge(a) => a.id,
683            Self::PlaneOfFace(a) => a.id,
684            Self::StartSketchOnFace(a) => a.id,
685            Self::StartSketchOnPlane(a) => a.id,
686            Self::SketchBlock(a) => a.id,
687            Self::SketchBlockConstraint(a) => a.id,
688            Self::Sweep(a) => a.id,
689            Self::Wall(a) => a.id,
690            Self::Cap(a) => a.id,
691            Self::SweepEdge(a) => a.id,
692            Self::EdgeCut(a) => a.id,
693            Self::EdgeCutEdge(a) => a.id,
694            Self::Helix(a) => a.id,
695            Self::ImportedGeometry(a) => a.id,
696            Self::GdtAnnotation(a) => a.id,
697            Self::NamedView(a) => a.id,
698            Self::Pattern(a) => a.id,
699        }
700    }
701
702    /// The [`CodeRef`] for the artifact itself. See also
703    /// [`Self::face_code_ref`].
704    pub fn code_ref(&self) -> Option<&CodeRef> {
705        match self {
706            Self::CompositeSolid(a) => Some(&a.code_ref),
707            Self::Plane(a) => Some(&a.code_ref),
708            Self::Path(a) => Some(&a.code_ref),
709            Self::Segment(a) => Some(&a.code_ref),
710            Self::Solid2d(_) => None,
711            Self::PrimitiveFace(a) => Some(&a.code_ref),
712            Self::PrimitiveEdge(a) => Some(&a.code_ref),
713            Self::PlaneOfFace(a) => Some(&a.code_ref),
714            Self::StartSketchOnFace(a) => Some(&a.code_ref),
715            Self::StartSketchOnPlane(a) => Some(&a.code_ref),
716            Self::SketchBlock(a) => Some(&a.code_ref),
717            Self::SketchBlockConstraint(a) => Some(&a.code_ref),
718            Self::Sweep(a) => Some(&a.code_ref),
719            Self::Wall(_) | Self::Cap(_) | Self::SweepEdge(_) => None,
720            Self::EdgeCut(a) => Some(&a.code_ref),
721            Self::EdgeCutEdge(_) => None,
722            Self::Helix(a) => Some(&a.code_ref),
723            Self::ImportedGeometry(a) => Some(&a.code_ref),
724            Self::GdtAnnotation(a) => Some(&a.code_ref),
725            Self::NamedView(a) => Some(&a.code_ref),
726            Self::Pattern(a) => Some(&a.code_ref),
727        }
728    }
729
730    /// The [`CodeRef`] referring to the face artifact that it's on, not the
731    /// artifact itself.
732    pub fn face_code_ref(&self) -> Option<&CodeRef> {
733        match self {
734            Self::PrimitiveFace(a) => Some(&a.code_ref),
735            Self::Wall(a) => Some(&a.face_code_ref),
736            Self::Cap(a) => Some(&a.face_code_ref),
737            _ => None,
738        }
739    }
740}
741
742#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, ts_rs::TS)]
743#[ts(export_to = "Artifact.ts")]
744#[serde(rename_all = "camelCase")]
745pub struct ArtifactGraph {
746    map: IndexMap<ArtifactId, Artifact>,
747    item_count: usize,
748}
749
750impl ArtifactGraph {
751    pub fn from_parts(map: IndexMap<ArtifactId, Artifact>, item_count: usize) -> Self {
752        Self { map, item_count }
753    }
754
755    pub fn into_parts(self) -> (IndexMap<ArtifactId, Artifact>, usize) {
756        (self.map, self.item_count)
757    }
758
759    pub fn item_count(&self) -> usize {
760        self.item_count
761    }
762
763    pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
764        self.map.get(id)
765    }
766
767    pub fn len(&self) -> usize {
768        self.map.len()
769    }
770
771    pub fn is_empty(&self) -> bool {
772        self.map.is_empty()
773    }
774
775    pub fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
776        self.map.iter()
777    }
778
779    pub fn values(&self) -> impl Iterator<Item = &Artifact> {
780        self.map.values()
781    }
782
783    pub fn clear(&mut self) {
784        self.map.clear();
785        self.item_count = 0;
786    }
787}
788
789#[cfg(test)]
790mod tests {
791    use super::*;
792
793    #[test]
794    fn default_artifact_graph_json_round_trip() {
795        let graph = ArtifactGraph::default();
796        let json = serde_json::to_string(&graph).unwrap();
797
798        assert_eq!(json, r#"{"map":{},"itemCount":0}"#);
799        assert_eq!(serde_json::from_str::<ArtifactGraph>(&json).unwrap(), graph);
800    }
801
802    #[test]
803    fn artifact_sweep_method_uses_engine_json_shape() {
804        assert_eq!(serde_json::to_string(&ArtifactSweepMethod::New).unwrap(), r#""new""#);
805        assert_eq!(
806            serde_json::to_string(&ArtifactSweepMethod::Merge).unwrap(),
807            r#""merge""#
808        );
809    }
810}