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    pub path_id: ArtifactId,
308    pub surface_ids: Vec<ArtifactId>,
309    pub edge_ids: Vec<ArtifactId>,
310    pub code_ref: CodeRef,
311    /// The original sweep this body was cloned from, if any. For clones of
312    /// clones, this continues to point to the originating sweep.
313    #[serde(default, skip_serializing_if = "Option::is_none")]
314    pub source_sweep_id: Option<ArtifactId>,
315    /// ID of trajectory path for sweep, if any
316    /// Only applicable to SweepSubType::Sweep and SweepSubType::Blend, which
317    /// can use a second path-like input
318    pub trajectory_id: Option<ArtifactId>,
319    pub method: ArtifactSweepMethod,
320    /// Whether this artifact has been used in a subsequent operation
321    pub consumed: bool,
322    /// Pattern operations that use this sweep as their source.
323    #[serde(default, skip_serializing_if = "Vec::is_empty")]
324    pub pattern_ids: Vec<ArtifactId>,
325}
326
327#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
328#[ts(export_to = "Artifact.ts")]
329#[serde(rename_all = "camelCase")]
330pub enum SweepSubType {
331    Extrusion,
332    ExtrusionTwist,
333    Revolve,
334    RevolveAboutEdge,
335    Loft,
336    Blend,
337    Sweep,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
341#[ts(export_to = "Artifact.ts")]
342#[serde(rename_all = "camelCase")]
343pub struct Solid2d {
344    pub id: ArtifactId,
345    pub path_id: ArtifactId,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
349#[ts(export_to = "Artifact.ts")]
350#[serde(rename_all = "camelCase")]
351pub struct PrimitiveFace {
352    pub id: ArtifactId,
353    pub solid_id: ArtifactId,
354    pub code_ref: CodeRef,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
358#[ts(export_to = "Artifact.ts")]
359#[serde(rename_all = "camelCase")]
360pub struct PrimitiveEdge {
361    pub id: ArtifactId,
362    pub solid_id: ArtifactId,
363    pub code_ref: CodeRef,
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
367#[ts(export_to = "Artifact.ts")]
368#[serde(rename_all = "camelCase")]
369pub struct PlaneOfFace {
370    pub id: ArtifactId,
371    pub face_id: ArtifactId,
372    pub code_ref: CodeRef,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
376#[ts(export_to = "Artifact.ts")]
377#[serde(rename_all = "camelCase")]
378pub struct StartSketchOnFace {
379    pub id: ArtifactId,
380    pub face_id: ArtifactId,
381    pub code_ref: CodeRef,
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
385#[ts(export_to = "Artifact.ts")]
386#[serde(rename_all = "camelCase")]
387pub struct StartSketchOnPlane {
388    pub id: ArtifactId,
389    pub plane_id: ArtifactId,
390    pub code_ref: CodeRef,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
394#[ts(export_to = "Artifact.ts")]
395#[serde(rename_all = "camelCase")]
396pub struct SketchBlock {
397    pub id: ArtifactId,
398    /// The semantic standard plane name when the sketch block is on a standard plane.
399    #[serde(default, skip_serializing_if = "Option::is_none")]
400    pub standard_plane: Option<PlaneName>,
401    /// The concrete plane artifact ID backing the sketch block, when one is available.
402    #[serde(default, skip_serializing_if = "Option::is_none")]
403    pub plane_id: Option<ArtifactId>,
404    /// The evaluated plane data backing the sketch block, when the sketch is on a plane.
405    #[serde(default, skip_serializing_if = "Option::is_none")]
406    pub plane_info: Option<ArtifactPlaneInfo>,
407    /// The path artifact ID created from the sketch block, if there is one.
408    /// There are edge cases when a path isn't created, like when there are no
409    /// segments.
410    #[serde(default, skip_serializing_if = "Option::is_none")]
411    pub path_id: Option<ArtifactId>,
412    pub code_ref: CodeRef,
413    /// The sketch ID (ObjectId) for the sketch scene object.
414    pub sketch_id: ObjectId,
415}
416
417#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
418#[ts(export_to = "Artifact.ts")]
419#[serde(rename_all = "camelCase")]
420pub enum SketchBlockConstraintType {
421    Angle,
422    Coincident,
423    Distance,
424    Diameter,
425    EqualRadius,
426    Fixed,
427    HorizontalDistance,
428    VerticalDistance,
429    Horizontal,
430    LinesEqualLength,
431    Midpoint,
432    Parallel,
433    Perpendicular,
434    Radius,
435    Symmetric,
436    Tangent,
437    Vertical,
438}
439
440#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
441#[ts(export_to = "Artifact.ts")]
442#[serde(rename_all = "camelCase")]
443pub struct SketchBlockConstraint {
444    pub id: ArtifactId,
445    /// The sketch ID (ObjectId) that owns this constraint.
446    pub sketch_id: ObjectId,
447    /// The constraint ID (ObjectId) for the constraint scene object.
448    pub constraint_id: ObjectId,
449    pub constraint_type: SketchBlockConstraintType,
450    pub code_ref: CodeRef,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
454#[ts(export_to = "Artifact.ts")]
455#[serde(rename_all = "camelCase")]
456pub struct Wall {
457    pub id: ArtifactId,
458    pub seg_id: ArtifactId,
459    pub edge_cut_edge_ids: Vec<ArtifactId>,
460    pub sweep_id: ArtifactId,
461    pub path_ids: Vec<ArtifactId>,
462    /// This is for the sketch-on-face plane, not for the wall itself.  Traverse
463    /// to the extrude and/or segment to get the wall's code_ref.
464    pub face_code_ref: CodeRef,
465    /// The command ID that got the data for this wall. Used for stable sorting.
466    pub cmd_id: Uuid,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
470#[ts(export_to = "Artifact.ts")]
471#[serde(rename_all = "camelCase")]
472pub struct Cap {
473    pub id: ArtifactId,
474    pub sub_type: CapSubType,
475    pub edge_cut_edge_ids: Vec<ArtifactId>,
476    pub sweep_id: ArtifactId,
477    pub path_ids: Vec<ArtifactId>,
478    /// This is for the sketch-on-face plane, not for the cap itself.  Traverse
479    /// to the extrude and/or segment to get the cap's code_ref.
480    pub face_code_ref: CodeRef,
481    /// The command ID that got the data for this cap. Used for stable sorting.
482    pub cmd_id: Uuid,
483}
484
485#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
486#[ts(export_to = "Artifact.ts")]
487#[serde(rename_all = "camelCase")]
488pub enum CapSubType {
489    Start,
490    End,
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
494#[ts(export_to = "Artifact.ts")]
495#[serde(rename_all = "camelCase")]
496pub struct SweepEdge {
497    pub id: ArtifactId,
498    pub sub_type: SweepEdgeSubType,
499    pub seg_id: ArtifactId,
500    pub cmd_id: Uuid,
501    // This is only used for sorting, not for the actual artifact.
502    #[serde(skip)]
503    pub index: usize,
504    pub sweep_id: ArtifactId,
505    pub common_surface_ids: Vec<ArtifactId>,
506}
507
508#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
509#[ts(export_to = "Artifact.ts")]
510#[serde(rename_all = "camelCase")]
511pub enum SweepEdgeSubType {
512    Opposite,
513    Adjacent,
514    PreviousAdjacent,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
518#[ts(export_to = "Artifact.ts")]
519#[serde(rename_all = "camelCase")]
520pub struct EdgeCut {
521    pub id: ArtifactId,
522    pub sub_type: EdgeCutSubType,
523    pub consumed_edge_id: ArtifactId,
524    pub edge_ids: Vec<ArtifactId>,
525    #[serde(default, skip_serializing_if = "Option::is_none")]
526    pub surface_id: Option<ArtifactId>,
527    pub code_ref: CodeRef,
528}
529
530#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
531#[ts(export_to = "Artifact.ts")]
532#[serde(rename_all = "camelCase")]
533pub enum EdgeCutSubType {
534    Fillet,
535    Chamfer,
536    Custom,
537}
538
539#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
540#[ts(export_to = "Artifact.ts")]
541#[serde(rename_all = "camelCase")]
542pub struct EdgeCutEdge {
543    pub id: ArtifactId,
544    pub edge_cut_id: ArtifactId,
545    pub surface_id: ArtifactId,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
549#[ts(export_to = "Artifact.ts")]
550#[serde(rename_all = "camelCase")]
551pub struct Helix {
552    pub id: ArtifactId,
553    /// The axis of the helix.  Currently this is always an edge ID, but we may
554    /// add axes to the graph.
555    pub axis_id: Option<ArtifactId>,
556    pub code_ref: CodeRef,
557    /// The sweep, if any, that this Helix serves as the trajectory for.
558    pub trajectory_sweep_id: Option<ArtifactId>,
559    /// Whether this artifact has been used in a subsequent operation
560    pub consumed: bool,
561}
562
563#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
564#[ts(export_to = "Artifact.ts")]
565#[serde(rename_all = "camelCase")]
566pub struct ImportedGeometryArtifact {
567    pub id: ArtifactId,
568    pub code_ref: CodeRef,
569}
570
571#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
572#[ts(export_to = "Artifact.ts")]
573#[serde(rename_all = "camelCase")]
574pub struct GdtAnnotationArtifact {
575    pub id: ArtifactId,
576    pub code_ref: CodeRef,
577}
578
579/// A named view declared in KCL by `view::named`: a display name, camera intent
580/// and the objects the view shows or hides.
581///
582/// The view is data for a consumer to activate later, so this artifact holds no
583/// engine geometry and its creation sends no engine command. The objects are
584/// named by ARTIFACT id, so a consumer resolves each one through the artifact
585/// graph; an extruded solid's engine id differs from its artifact id, and the
586/// translation belongs to whoever applies the view.
587#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
588#[ts(export_to = "Artifact.ts")]
589#[serde(rename_all = "camelCase")]
590pub struct NamedViewArtifact {
591    pub id: ArtifactId,
592    /// The display name the author gave the view. Required, and unique among
593    /// the views declared by the same module.
594    pub name: String,
595    pub camera: ArtifactCameraView,
596    /// Whether objects start visible or hidden, which decides which of the two
597    /// lists below can act.
598    pub baseline: ArtifactVisibility,
599    /// Artifacts the view shows. Meaningful under a `Hide` baseline, where they
600    /// are the only visible objects.
601    pub show_ids: Vec<ArtifactId>,
602    /// Artifacts the view hides. Meaningful under a `Show` baseline, where they
603    /// are the only hidden objects.
604    pub hide_ids: Vec<ArtifactId>,
605    pub code_ref: CodeRef,
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
609#[ts(export_to = "Artifact.ts")]
610#[serde(rename_all = "camelCase")]
611pub struct Pattern {
612    pub id: ArtifactId,
613    pub sub_type: PatternSubType,
614    /// Geometry artifact that was the source of the pattern operation.
615    pub source_id: ArtifactId,
616    /// IDs of copied top-level objects created by the pattern operation.
617    pub copy_ids: Vec<ArtifactId>,
618    /// IDs of copied faces created by the pattern operation.
619    pub copy_face_ids: Vec<ArtifactId>,
620    /// IDs of copied edges created by the pattern operation.
621    pub copy_edge_ids: Vec<ArtifactId>,
622    pub code_ref: CodeRef,
623}
624
625#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, ts_rs::TS)]
626#[ts(export_to = "Artifact.ts")]
627#[serde(rename_all = "camelCase")]
628pub enum PatternSubType {
629    Circular,
630    Linear,
631    Transform,
632}
633
634#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, ts_rs::TS)]
635#[ts(export_to = "Artifact.ts")]
636#[serde(tag = "type", rename_all = "camelCase")]
637pub enum Artifact {
638    CompositeSolid(CompositeSolid),
639    Plane(Plane),
640    Path(Path),
641    Segment(Segment),
642    Solid2d(Solid2d),
643    PrimitiveFace(PrimitiveFace),
644    PrimitiveEdge(PrimitiveEdge),
645    PlaneOfFace(PlaneOfFace),
646    StartSketchOnFace(StartSketchOnFace),
647    StartSketchOnPlane(StartSketchOnPlane),
648    SketchBlock(SketchBlock),
649    SketchBlockConstraint(SketchBlockConstraint),
650    Sweep(Sweep),
651    Wall(Wall),
652    Cap(Cap),
653    SweepEdge(SweepEdge),
654    EdgeCut(EdgeCut),
655    EdgeCutEdge(EdgeCutEdge),
656    Helix(Helix),
657    ImportedGeometry(ImportedGeometryArtifact),
658    GdtAnnotation(GdtAnnotationArtifact),
659    NamedView(NamedViewArtifact),
660    Pattern(Pattern),
661}
662
663impl Artifact {
664    pub fn id(&self) -> ArtifactId {
665        match self {
666            Self::CompositeSolid(a) => a.id,
667            Self::Plane(a) => a.id,
668            Self::Path(a) => a.id,
669            Self::Segment(a) => a.id,
670            Self::Solid2d(a) => a.id,
671            Self::PrimitiveFace(a) => a.id,
672            Self::PrimitiveEdge(a) => a.id,
673            Self::PlaneOfFace(a) => a.id,
674            Self::StartSketchOnFace(a) => a.id,
675            Self::StartSketchOnPlane(a) => a.id,
676            Self::SketchBlock(a) => a.id,
677            Self::SketchBlockConstraint(a) => a.id,
678            Self::Sweep(a) => a.id,
679            Self::Wall(a) => a.id,
680            Self::Cap(a) => a.id,
681            Self::SweepEdge(a) => a.id,
682            Self::EdgeCut(a) => a.id,
683            Self::EdgeCutEdge(a) => a.id,
684            Self::Helix(a) => a.id,
685            Self::ImportedGeometry(a) => a.id,
686            Self::GdtAnnotation(a) => a.id,
687            Self::NamedView(a) => a.id,
688            Self::Pattern(a) => a.id,
689        }
690    }
691
692    /// The [`CodeRef`] for the artifact itself. See also
693    /// [`Self::face_code_ref`].
694    pub fn code_ref(&self) -> Option<&CodeRef> {
695        match self {
696            Self::CompositeSolid(a) => Some(&a.code_ref),
697            Self::Plane(a) => Some(&a.code_ref),
698            Self::Path(a) => Some(&a.code_ref),
699            Self::Segment(a) => Some(&a.code_ref),
700            Self::Solid2d(_) => None,
701            Self::PrimitiveFace(a) => Some(&a.code_ref),
702            Self::PrimitiveEdge(a) => Some(&a.code_ref),
703            Self::PlaneOfFace(a) => Some(&a.code_ref),
704            Self::StartSketchOnFace(a) => Some(&a.code_ref),
705            Self::StartSketchOnPlane(a) => Some(&a.code_ref),
706            Self::SketchBlock(a) => Some(&a.code_ref),
707            Self::SketchBlockConstraint(a) => Some(&a.code_ref),
708            Self::Sweep(a) => Some(&a.code_ref),
709            Self::Wall(_) | Self::Cap(_) | Self::SweepEdge(_) => None,
710            Self::EdgeCut(a) => Some(&a.code_ref),
711            Self::EdgeCutEdge(_) => None,
712            Self::Helix(a) => Some(&a.code_ref),
713            Self::ImportedGeometry(a) => Some(&a.code_ref),
714            Self::GdtAnnotation(a) => Some(&a.code_ref),
715            Self::NamedView(a) => Some(&a.code_ref),
716            Self::Pattern(a) => Some(&a.code_ref),
717        }
718    }
719
720    /// The [`CodeRef`] referring to the face artifact that it's on, not the
721    /// artifact itself.
722    pub fn face_code_ref(&self) -> Option<&CodeRef> {
723        match self {
724            Self::PrimitiveFace(a) => Some(&a.code_ref),
725            Self::Wall(a) => Some(&a.face_code_ref),
726            Self::Cap(a) => Some(&a.face_code_ref),
727            _ => None,
728        }
729    }
730}
731
732#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, ts_rs::TS)]
733#[ts(export_to = "Artifact.ts")]
734#[serde(rename_all = "camelCase")]
735pub struct ArtifactGraph {
736    map: IndexMap<ArtifactId, Artifact>,
737    item_count: usize,
738}
739
740impl ArtifactGraph {
741    pub fn from_parts(map: IndexMap<ArtifactId, Artifact>, item_count: usize) -> Self {
742        Self { map, item_count }
743    }
744
745    pub fn into_parts(self) -> (IndexMap<ArtifactId, Artifact>, usize) {
746        (self.map, self.item_count)
747    }
748
749    pub fn item_count(&self) -> usize {
750        self.item_count
751    }
752
753    pub fn get(&self, id: &ArtifactId) -> Option<&Artifact> {
754        self.map.get(id)
755    }
756
757    pub fn len(&self) -> usize {
758        self.map.len()
759    }
760
761    pub fn is_empty(&self) -> bool {
762        self.map.is_empty()
763    }
764
765    pub fn iter(&self) -> impl Iterator<Item = (&ArtifactId, &Artifact)> {
766        self.map.iter()
767    }
768
769    pub fn values(&self) -> impl Iterator<Item = &Artifact> {
770        self.map.values()
771    }
772
773    pub fn clear(&mut self) {
774        self.map.clear();
775        self.item_count = 0;
776    }
777}
778
779#[cfg(test)]
780mod tests {
781    use super::*;
782
783    #[test]
784    fn default_artifact_graph_json_round_trip() {
785        let graph = ArtifactGraph::default();
786        let json = serde_json::to_string(&graph).unwrap();
787
788        assert_eq!(json, r#"{"map":{},"itemCount":0}"#);
789        assert_eq!(serde_json::from_str::<ArtifactGraph>(&json).unwrap(), graph);
790    }
791
792    #[test]
793    fn artifact_sweep_method_uses_engine_json_shape() {
794        assert_eq!(serde_json::to_string(&ArtifactSweepMethod::New).unwrap(), r#""new""#);
795        assert_eq!(
796            serde_json::to_string(&ArtifactSweepMethod::Merge).unwrap(),
797            r#""merge""#
798        );
799    }
800}