Skip to main content

kcl_api/
artifact.rs

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