Skip to main content

kcl_lib/execution/
kcl_value_view.rs

1pub use kcl_api::kcl_value_view::*;
2use serde::Serialize;
3
4use crate::exec::KclValue;
5use crate::execution::Metadata;
6use crate::execution::TagIdentifier;
7use crate::execution::geometry as runtime;
8use crate::parsing::ast::types::TagNode;
9
10fn json_view<T: Serialize>(value: &T) -> serde_json::Value {
11    serde_json::to_value(value).unwrap_or(serde_json::Value::Null)
12}
13
14fn tag_declarator_view(tag: TagNode) -> TagDeclaratorView {
15    TagDeclaratorView {
16        comment_start: tag.comment_start,
17        end: tag.end,
18        module_id: tag.module_id,
19        start: tag.start,
20        type_: TagDeclaratorViewType::TagDeclarator,
21        name: tag.inner.name,
22        digest: tag.inner.digest,
23    }
24}
25
26fn tag_identifier_view(tag: TagIdentifier) -> TagIdentifierView {
27    TagIdentifierView {
28        type_: TagIdentifierViewType::TagIdentifier,
29        value: tag.value,
30    }
31}
32
33fn point3d_view(point: runtime::Point3d) -> Point3dView {
34    Point3dView {
35        x: point.x,
36        y: point.y,
37        z: point.z,
38        units: point.units,
39    }
40}
41
42fn plane_kind_view(kind: runtime::PlaneKind) -> PlaneKindView {
43    match kind {
44        runtime::PlaneKind::XY => PlaneKindView::XY,
45        runtime::PlaneKind::XZ => PlaneKindView::XZ,
46        runtime::PlaneKind::YZ => PlaneKindView::YZ,
47        runtime::PlaneKind::Custom => PlaneKindView::Custom,
48    }
49}
50
51fn plane_view(plane: runtime::Plane) -> PlaneView {
52    PlaneView {
53        id: plane.id,
54        artifact_id: plane.artifact_id,
55        object_id: plane.object_id,
56        kind: plane_kind_view(plane.kind),
57        origin: point3d_view(plane.info.origin),
58        x_axis: point3d_view(plane.info.x_axis),
59        y_axis: point3d_view(plane.info.y_axis),
60        z_axis: point3d_view(plane.info.z_axis),
61    }
62}
63
64fn face_view(face: runtime::Face) -> FaceView {
65    FaceView {
66        id: face.id,
67        artifact_id: face.artifact_id,
68        object_id: face.object_id,
69        value: face.value,
70        x_axis: point3d_view(face.x_axis),
71        y_axis: point3d_view(face.y_axis),
72        parent_solid: face_parent_solid_view(face.parent_solid),
73        units: face.units,
74    }
75}
76
77fn face_parent_solid_view(parent: runtime::FaceParentSolid) -> FaceParentSolidView {
78    FaceParentSolidView {
79        solid_id: parent.solid_id,
80        creator_sketch_id: parent.creator_sketch_id,
81        creator_sketch_is_closed: parent.creator_sketch_is_closed.map(profile_closed_view),
82        edge_cut_ids: parent.edge_cut_ids,
83    }
84}
85
86fn sketch_surface_view(surface: runtime::SketchSurface) -> SketchSurfaceView {
87    match surface {
88        runtime::SketchSurface::Plane(plane) => SketchSurfaceView::Plane(Box::new(plane_view(*plane))),
89        runtime::SketchSurface::Face(face) => SketchSurfaceView::Face(Box::new(face_view(*face))),
90    }
91}
92
93fn geo_meta_view(id: uuid::Uuid, metadata: Metadata) -> GeoMetaView {
94    GeoMetaView {
95        id,
96        source_range: metadata.source_range,
97    }
98}
99
100fn base_path_view(base: runtime::BasePath) -> BasePathView {
101    BasePathView {
102        from: base.from,
103        to: base.to,
104        units: base.units,
105        tag: base.tag.map(tag_declarator_view),
106        geo_meta: geo_meta_view(base.geo_meta.id, base.geo_meta.metadata),
107    }
108}
109
110fn path_view(path: runtime::Path) -> PathView {
111    match path {
112        runtime::Path::ToPoint { base } => PathView::ToPoint {
113            base: base_path_view(base),
114        },
115        runtime::Path::TangentialArcTo { base, center, ccw } => PathView::TangentialArcTo {
116            base: base_path_view(base),
117            center,
118            ccw,
119        },
120        runtime::Path::TangentialArc { base, center, ccw } => PathView::TangentialArc {
121            base: base_path_view(base),
122            center,
123            ccw,
124        },
125        runtime::Path::Circle {
126            base,
127            center,
128            radius,
129            ccw,
130        } => PathView::Circle {
131            base: base_path_view(base),
132            center,
133            radius,
134            ccw,
135        },
136        runtime::Path::CircleThreePoint { base, p1, p2, p3 } => PathView::CircleThreePoint {
137            base: base_path_view(base),
138            p1,
139            p2,
140            p3,
141        },
142        runtime::Path::ArcThreePoint { base, p1, p2, p3 } => PathView::ArcThreePoint {
143            base: base_path_view(base),
144            p1,
145            p2,
146            p3,
147        },
148        runtime::Path::Horizontal { base, x } => PathView::Horizontal {
149            base: base_path_view(base),
150            x,
151        },
152        runtime::Path::AngledLineTo { base, x, y } => PathView::AngledLineTo {
153            base: base_path_view(base),
154            x,
155            y,
156        },
157        runtime::Path::Base { base } => PathView::Base {
158            base: base_path_view(base),
159        },
160        runtime::Path::Arc {
161            base,
162            center,
163            radius,
164            ccw,
165        } => PathView::Arc {
166            base: base_path_view(base),
167            center,
168            radius,
169            ccw,
170        },
171        runtime::Path::Ellipse {
172            base,
173            center,
174            major_axis,
175            minor_radius,
176            ccw,
177        } => PathView::Ellipse {
178            base: base_path_view(base),
179            center,
180            major_axis,
181            minor_radius,
182            ccw,
183        },
184        runtime::Path::Conic { base } => PathView::Conic {
185            base: base_path_view(base),
186        },
187        runtime::Path::Bezier {
188            base,
189            control1,
190            control2,
191        } => PathView::Bezier {
192            base: base_path_view(base),
193            control1,
194            control2,
195        },
196    }
197}
198
199fn profile_closed_view(closed: runtime::ProfileClosed) -> ProfileClosedView {
200    match closed {
201        runtime::ProfileClosed::No => ProfileClosedView::No,
202        runtime::ProfileClosed::Maybe => ProfileClosedView::Maybe,
203        runtime::ProfileClosed::Implicitly => ProfileClosedView::Implicitly,
204        runtime::ProfileClosed::Explicitly => ProfileClosedView::Explicitly,
205    }
206}
207
208fn sketch_view(sketch: runtime::Sketch) -> SketchView {
209    SketchView {
210        type_: SketchViewType::Sketch,
211        id: sketch.id,
212        original_id: sketch.original_id,
213        paths: sketch.paths.into_iter().map(path_view).collect(),
214        inner_paths: sketch.inner_paths.into_iter().map(path_view).collect(),
215        on: sketch_surface_view(sketch.on),
216        start: base_path_view(sketch.start),
217        tags: sketch
218            .tags
219            .into_iter()
220            .map(|(name, tag)| (name, tag_identifier_view(tag)))
221            .collect(),
222        artifact_id: sketch.artifact_id,
223        units: sketch.units,
224        is_closed: profile_closed_view(sketch.is_closed),
225    }
226}
227
228fn surface_view(face_id: uuid::Uuid, tag: Option<TagNode>, geo_meta: runtime::GeoMeta) -> SurfaceView {
229    SurfaceView {
230        face_id,
231        tag: tag.map(tag_declarator_view),
232        geo_meta: geo_meta_view(geo_meta.id, geo_meta.metadata),
233    }
234}
235
236fn extrude_surface_view(surface: runtime::ExtrudeSurface) -> ExtrudeSurfaceView {
237    match surface {
238        runtime::ExtrudeSurface::ExtrudePlane(surface) => {
239            ExtrudeSurfaceView::ExtrudePlane(surface_view(surface.face_id, surface.tag, surface.geo_meta))
240        }
241        runtime::ExtrudeSurface::ExtrudeArc(surface) => {
242            ExtrudeSurfaceView::ExtrudeArc(surface_view(surface.face_id, surface.tag, surface.geo_meta))
243        }
244        runtime::ExtrudeSurface::Chamfer(surface) => {
245            ExtrudeSurfaceView::Chamfer(surface_view(surface.face_id, surface.tag, surface.geo_meta))
246        }
247        runtime::ExtrudeSurface::Fillet(surface) => {
248            ExtrudeSurfaceView::Fillet(surface_view(surface.face_id, surface.tag, surface.geo_meta))
249        }
250    }
251}
252
253fn solid_creator_view(creator: runtime::SolidCreator) -> SolidCreatorView {
254    match creator {
255        runtime::SolidCreator::Sketch(sketch) => SolidCreatorView::Sketch(sketch_view(sketch)),
256        runtime::SolidCreator::Face(face) => SolidCreatorView::Face {
257            face_id: face.face_id,
258            solid_id: face.solid_id,
259            sketch: sketch_view(face.sketch),
260        },
261        runtime::SolidCreator::Edge(edge) => SolidCreatorView::Edge {
262            edge_id: edge.edge_id,
263            body_id: edge.body_id,
264        },
265        runtime::SolidCreator::Procedural => SolidCreatorView::Procedural,
266    }
267}
268
269fn edge_cut_view(edge_cut: runtime::EdgeCut) -> EdgeCutView {
270    match edge_cut {
271        runtime::EdgeCut::Fillet {
272            id,
273            radius,
274            edge_id,
275            tag,
276        } => EdgeCutView::Fillet {
277            id,
278            radius: NumericValueView {
279                n: radius.n,
280                ty: radius.ty,
281            },
282            edge_id,
283            tag: (*tag).map(tag_declarator_view),
284        },
285        runtime::EdgeCut::Chamfer {
286            id,
287            length,
288            edge_id,
289            tag,
290        } => EdgeCutView::Chamfer {
291            id,
292            length: NumericValueView {
293                n: length.n,
294                ty: length.ty,
295            },
296            edge_id,
297            tag: (*tag).map(tag_declarator_view),
298        },
299    }
300}
301
302fn solid_view(solid: runtime::Solid) -> SolidView {
303    let original_id = solid.original_id();
304    let topology_id = solid.topology_id();
305    SolidView {
306        type_: SolidViewType::Solid,
307        id: solid.id,
308        original_id,
309        topology_id,
310        artifact_id: solid.artifact_id,
311        value: solid.value.into_iter().map(extrude_surface_view).collect(),
312        faces: solid
313            .faces
314            .into_iter()
315            .map(|(name, tag)| (name, tag_identifier_view(tag)))
316            .collect(),
317        creator: solid_creator_view(solid.creator),
318        start_cap_id: solid.start_cap_id,
319        end_cap_id: solid.end_cap_id,
320        edge_cuts: solid.edge_cuts.into_iter().map(edge_cut_view).collect(),
321        units: solid.units,
322        sectional: solid.sectional,
323    }
324}
325
326impl From<KclValue> for KclValueView {
327    fn from(full: KclValue) -> Self {
328        match full {
329            KclValue::Uuid { value, .. } => Self::Uuid { value },
330            KclValue::Bool { value, .. } => Self::Bool { value },
331            KclValue::Number { value, ty, .. } => Self::Number { value, ty },
332            KclValue::String { value, .. } => Self::String { value },
333            KclValue::Enum { value } => Self::Enum {
334                enum_name: value.enum_id().declared_name().to_owned(),
335                variant: value.variant().to_owned(),
336            },
337            KclValue::SketchVar { value } => Self::SketchVar {
338                value: Box::new(SketchVarView {
339                    initial_value: value.initial_value,
340                    ty: value.ty,
341                }),
342            },
343            KclValue::SketchConstraint { value } => Self::SketchConstraint {
344                value: json_view(&value),
345            },
346            KclValue::Tuple { value, .. } => Self::Tuple {
347                value: value.into_iter().map(Self::from).collect(),
348            },
349            KclValue::HomArray { value, .. } => Self::HomArray {
350                value: value.into_iter().map(Self::from).collect(),
351            },
352            KclValue::Object {
353                value, constrainable, ..
354            } => {
355                let mut fields: Vec<_> = value.into_iter().collect();
356                fields.sort_unstable_by(|(left, _), (right, _)| left.cmp(right));
357                Self::Object {
358                    value: fields
359                        .into_iter()
360                        .map(|(name, value)| (name, Self::from(value)))
361                        .collect(),
362                    constrainable,
363                }
364            }
365            KclValue::TagIdentifier(tag) => Self::TagIdentifier { value: tag.value },
366            KclValue::TagDeclarator(tag) => Self::TagDeclarator {
367                value: tag.inner.name.clone(),
368            },
369            KclValue::GdtAnnotation { value } => Self::GdtAnnotation {
370                value: Box::new(GdtAnnotationView { id: value.id }),
371            },
372            KclValue::CameraView { value } => Self::CameraView {
373                value: json_view(&value),
374            },
375            KclValue::NamedView { value } => Self::NamedView {
376                value: json_view(&value),
377            },
378            KclValue::Plane { value } => Self::Plane {
379                value: Box::new(plane_view(*value)),
380            },
381            KclValue::Face { value } => Self::Face {
382                value: Box::new(face_view(*value)),
383            },
384            KclValue::BoundedEdge { value, .. } => Self::BoundedEdge {
385                value: BoundedEdgeView {
386                    face_id: value.face_id,
387                    edge_id: value.edge_id,
388                    lower_bound: value.lower_bound,
389                    upper_bound: value.upper_bound,
390                },
391            },
392            KclValue::Segment { value } => Self::Segment {
393                value: json_view(&value),
394            },
395            KclValue::Sketch { value } => Self::Sketch {
396                value: Box::new(sketch_view(*value)),
397            },
398            KclValue::Solid { value } => Self::Solid {
399                value: Box::new(solid_view(*value)),
400            },
401            KclValue::Helix { value } => Self::Helix {
402                value: Box::new(HelixView {
403                    value: value.value,
404                    artifact_id: value.artifact_id,
405                    revolutions: value.revolutions,
406                    angle_start: value.angle_start,
407                    ccw: value.ccw,
408                    cylinder_id: value.cylinder_id,
409                    units: value.units,
410                }),
411            },
412            KclValue::ImportedGeometry(value) => Self::ImportedGeometry(ImportedGeometryView {
413                id: value.id,
414                value: value.value,
415            }),
416            KclValue::Function { .. } => Self::Function {},
417            KclValue::Module { value, .. } => Self::Module { value },
418            KclValue::Type { experimental, .. } => Self::Type { experimental },
419            KclValue::KclNone { .. } => Self::KclNone {},
420        }
421    }
422}
423
424/// Runtime helpers for API-owned path views.
425#[allow(dead_code)]
426pub trait PathViewExt {
427    fn get_base(&self) -> &BasePathView;
428    fn get_id(&self) -> uuid::Uuid {
429        self.get_base().geo_meta.id
430    }
431    fn get_tag(&self) -> Option<TagDeclaratorView> {
432        self.get_base().tag.clone()
433    }
434    fn arc_center_and_ccw(&self) -> Option<([f64; 2], bool)>;
435}
436
437impl PathViewExt for PathView {
438    fn get_base(&self) -> &BasePathView {
439        match self {
440            Self::ToPoint { base }
441            | Self::TangentialArcTo { base, .. }
442            | Self::TangentialArc { base, .. }
443            | Self::Circle { base, .. }
444            | Self::CircleThreePoint { base, .. }
445            | Self::ArcThreePoint { base, .. }
446            | Self::Horizontal { base, .. }
447            | Self::AngledLineTo { base, .. }
448            | Self::Base { base }
449            | Self::Arc { base, .. }
450            | Self::Ellipse { base, .. }
451            | Self::Conic { base }
452            | Self::Bezier { base, .. } => base,
453        }
454    }
455
456    fn arc_center_and_ccw(&self) -> Option<([f64; 2], bool)> {
457        match self {
458            Self::TangentialArcTo { center, ccw, .. }
459            | Self::TangentialArc { center, ccw, .. }
460            | Self::Arc { center, ccw, .. } => Some((*center, *ccw)),
461            Self::ArcThreePoint { p1, p2, p3, .. } => {
462                let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
463                Some((circle.center, crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0))
464            }
465            _ => None,
466        }
467    }
468}
469
470/// Runtime navigation helpers for API-owned solid views.
471#[allow(dead_code)]
472pub trait SolidViewExt {
473    fn sketch(&self) -> Option<&SketchView>;
474    fn original_id(&self) -> uuid::Uuid;
475    fn topology_id(&self) -> uuid::Uuid;
476}
477
478impl SolidViewExt for SolidView {
479    fn sketch(&self) -> Option<&SketchView> {
480        match &self.creator {
481            SolidCreatorView::Sketch(sketch) | SolidCreatorView::Face { sketch, .. } => Some(sketch),
482            SolidCreatorView::Edge { .. } | SolidCreatorView::Procedural => None,
483        }
484    }
485
486    fn original_id(&self) -> uuid::Uuid {
487        self.original_id
488    }
489
490    fn topology_id(&self) -> uuid::Uuid {
491        self.topology_id
492    }
493}
494
495/// Runtime helpers for compact surface views.
496#[allow(dead_code)]
497pub trait ExtrudeSurfaceViewExt {
498    fn get_id(&self) -> uuid::Uuid;
499    fn get_tag(&self) -> Option<TagDeclaratorView>;
500    fn face_id(&self) -> uuid::Uuid;
501}
502
503impl ExtrudeSurfaceViewExt for ExtrudeSurfaceView {
504    fn get_id(&self) -> uuid::Uuid {
505        match self {
506            Self::ExtrudePlane(surface)
507            | Self::ExtrudeArc(surface)
508            | Self::Chamfer(surface)
509            | Self::Fillet(surface) => surface.geo_meta.id,
510        }
511    }
512
513    fn get_tag(&self) -> Option<TagDeclaratorView> {
514        match self {
515            Self::ExtrudePlane(surface)
516            | Self::ExtrudeArc(surface)
517            | Self::Chamfer(surface)
518            | Self::Fillet(surface) => surface.tag.clone(),
519        }
520    }
521
522    fn face_id(&self) -> uuid::Uuid {
523        match self {
524            Self::ExtrudePlane(surface)
525            | Self::ExtrudeArc(surface)
526            | Self::Chamfer(surface)
527            | Self::Fillet(surface) => surface.face_id,
528        }
529    }
530}
531
532/// Runtime helpers for compact edge-cut views.
533#[allow(dead_code)]
534pub trait EdgeCutViewExt {
535    fn id(&self) -> uuid::Uuid;
536    fn edge_id(&self) -> uuid::Uuid;
537    fn tag(&self) -> Option<TagDeclaratorView>;
538}
539
540impl EdgeCutViewExt for EdgeCutView {
541    fn id(&self) -> uuid::Uuid {
542        match self {
543            Self::Fillet { id, .. } | Self::Chamfer { id, .. } => *id,
544        }
545    }
546
547    fn edge_id(&self) -> uuid::Uuid {
548        match self {
549            Self::Fillet { edge_id, .. } | Self::Chamfer { edge_id, .. } => *edge_id,
550        }
551    }
552
553    fn tag(&self) -> Option<TagDeclaratorView> {
554        match self {
555            Self::Fillet { tag, .. } | Self::Chamfer { tag, .. } => tag.clone(),
556        }
557    }
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    fn number(value: f64) -> KclValue {
565        KclValue::Number {
566            value,
567            ty: Default::default(),
568            meta: Vec::new(),
569        }
570    }
571
572    #[test]
573    fn object_views_have_stable_field_order() {
574        let runtime_value = KclValue::Object {
575            value: std::collections::HashMap::from([
576                ("zeta".to_owned(), number(2.0)),
577                ("alpha".to_owned(), number(1.0)),
578            ]),
579            constrainable: false,
580            object_kind: Default::default(),
581            meta: Vec::new(),
582        };
583
584        let KclValueView::Object { value, .. } = KclValueView::from(runtime_value) else {
585            panic!("expected object view");
586        };
587
588        assert_eq!(value.keys().map(String::as_str).collect::<Vec<_>>(), ["alpha", "zeta"]);
589    }
590}