use indexmap::IndexMap;
use kcl_error::ModuleId;
use kcl_error::SourceRange;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use crate::ArtifactId;
use crate::NumericType;
use crate::ObjectId;
use crate::UnitLength;
pub type KclObjectFields = IndexMap<String, KclValueView>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum KclValueView {
Uuid {
value: uuid::Uuid,
},
Bool {
value: bool,
},
Number {
value: f64,
ty: NumericType,
},
String {
value: String,
},
Enum {
enum_name: String,
variant: String,
},
SketchVar {
value: Box<SketchVarView>,
},
SketchConstraint {
value: JsonValue,
},
Tuple {
value: Vec<KclValueView>,
},
HomArray {
value: Vec<KclValueView>,
},
Object {
value: KclObjectFields,
constrainable: bool,
},
TagIdentifier(TagIdentifierView),
TagDeclarator {
value: String,
},
GdtAnnotation {
value: Box<GdtAnnotationView>,
},
CameraView {
value: JsonValue,
},
NamedView {
value: JsonValue,
},
Plane {
value: Box<PlaneView>,
},
Face {
value: Box<FaceView>,
},
BoundedEdge {
value: BoundedEdgeView,
},
Segment {
value: JsonValue,
},
Sketch {
value: Box<SketchView>,
},
Solid {
value: Box<SolidView>,
},
Helix {
value: Box<HelixView>,
},
ImportedGeometry(ImportedGeometryView),
Function {},
Module {
value: ModuleId,
},
Type {
experimental: bool,
},
KclNone {},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SketchVarView {
pub initial_value: f64,
pub ty: NumericType,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub enum TagIdentifierViewType {
#[default]
TagIdentifier,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct TagIdentifierView {
#[serde(rename = "type")]
#[ts(rename = "type", type = "\"TagIdentifier\"")]
pub type_: TagIdentifierViewType,
pub value: String,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub enum TagDeclaratorViewType {
#[default]
TagDeclarator,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct TagDeclaratorView {
pub comment_start: usize,
pub end: usize,
pub module_id: ModuleId,
pub start: usize,
#[serde(rename = "type")]
#[ts(rename = "type", type = "\"TagDeclarator\"")]
pub type_: TagDeclaratorViewType,
#[serde(rename = "value")]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<[u8; 32]>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct GdtAnnotationView {
pub id: uuid::Uuid,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point3dView {
pub x: f64,
pub y: f64,
pub z: f64,
pub units: Option<UnitLength>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct PlaneView {
pub artifact_id: ArtifactId,
pub id: uuid::Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub object_id: Option<ObjectId>,
pub kind: PlaneKindView,
pub origin: Point3dView,
pub x_axis: Point3dView,
pub y_axis: Point3dView,
pub z_axis: Point3dView,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub enum PlaneKindView {
#[serde(rename = "XY", alias = "xy")]
XY,
#[serde(rename = "XZ", alias = "xz")]
XZ,
#[serde(rename = "YZ", alias = "yz")]
YZ,
Custom,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct FaceView {
pub id: uuid::Uuid,
pub artifact_id: ArtifactId,
pub object_id: ObjectId,
pub value: String,
pub x_axis: Point3dView,
pub y_axis: Point3dView,
pub parent_solid: FaceParentSolidView,
pub units: UnitLength,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct FaceParentSolidView {
pub solid_id: uuid::Uuid,
pub creator_sketch_id: Option<uuid::Uuid>,
pub creator_sketch_is_closed: Option<ProfileClosedView>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cut_ids: Vec<uuid::Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct BoundedEdgeView {
pub face_id: uuid::Uuid,
pub edge_id: Option<uuid::Uuid>,
pub lower_bound: f32,
pub upper_bound: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ImportedGeometryView {
pub id: uuid::Uuid,
pub value: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct HelixView {
pub value: uuid::Uuid,
pub artifact_id: ArtifactId,
pub revolutions: f64,
pub angle_start: f64,
pub ccw: bool,
pub cylinder_id: Option<uuid::Uuid>,
pub units: UnitLength,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum SketchSurfaceView {
Plane(Box<PlaneView>),
Face(Box<FaceView>),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct BasePathView {
#[ts(type = "[number, number]")]
pub from: [f64; 2],
#[ts(type = "[number, number]")]
pub to: [f64; 2],
pub units: UnitLength,
pub tag: Option<TagDeclaratorView>,
#[serde(rename = "__geoMeta")]
pub geo_meta: GeoMetaView,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct GeoMetaView {
pub id: uuid::Uuid,
pub source_range: SourceRange,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum PathView {
ToPoint {
#[serde(flatten)]
base: BasePathView,
},
TangentialArcTo {
#[serde(flatten)]
base: BasePathView,
center: [f64; 2],
ccw: bool,
},
TangentialArc {
#[serde(flatten)]
base: BasePathView,
center: [f64; 2],
ccw: bool,
},
Circle {
#[serde(flatten)]
base: BasePathView,
center: [f64; 2],
radius: f64,
ccw: bool,
},
CircleThreePoint {
#[serde(flatten)]
base: BasePathView,
p1: [f64; 2],
p2: [f64; 2],
p3: [f64; 2],
},
ArcThreePoint {
#[serde(flatten)]
base: BasePathView,
p1: [f64; 2],
p2: [f64; 2],
p3: [f64; 2],
},
Horizontal {
#[serde(flatten)]
base: BasePathView,
x: f64,
},
AngledLineTo {
#[serde(flatten)]
base: BasePathView,
x: Option<f64>,
y: Option<f64>,
},
Base {
#[serde(flatten)]
base: BasePathView,
},
Arc {
#[serde(flatten)]
base: BasePathView,
center: [f64; 2],
radius: f64,
ccw: bool,
},
Ellipse {
#[serde(flatten)]
base: BasePathView,
center: [f64; 2],
major_axis: [f64; 2],
minor_radius: f64,
ccw: bool,
},
Conic {
#[serde(flatten)]
base: BasePathView,
},
Bezier {
#[serde(flatten)]
base: BasePathView,
control1: [f64; 2],
control2: [f64; 2],
},
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub enum ProfileClosedView {
No,
Maybe,
Implicitly,
Explicitly,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub enum SketchViewType {
#[default]
Sketch,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export, rename = "SketchView")]
#[serde(rename_all = "camelCase")]
pub struct SketchView {
#[serde(rename = "type")]
#[ts(rename = "type", type = "\"Sketch\"")]
pub type_: SketchViewType,
pub id: uuid::Uuid,
pub paths: Vec<PathView>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub inner_paths: Vec<PathView>,
pub on: SketchSurfaceView,
pub start: BasePathView,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub tags: IndexMap<String, TagIdentifierView>,
pub artifact_id: ArtifactId,
pub original_id: uuid::Uuid,
pub units: UnitLength,
pub is_closed: ProfileClosedView,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum ExtrudeSurfaceView {
ExtrudePlane(SurfaceView),
ExtrudeArc(SurfaceView),
Chamfer(SurfaceView),
Fillet(SurfaceView),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct SurfaceView {
pub face_id: uuid::Uuid,
pub tag: Option<TagDeclaratorView>,
#[serde(flatten)]
pub geo_meta: GeoMetaView,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct NumericValueView {
pub n: f64,
pub ty: NumericType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum EdgeCutView {
Fillet {
id: uuid::Uuid,
radius: NumericValueView,
#[serde(rename = "edgeId")]
#[ts(rename = "edgeId")]
edge_id: uuid::Uuid,
tag: Option<TagDeclaratorView>,
},
Chamfer {
id: uuid::Uuid,
length: NumericValueView,
#[serde(rename = "edgeId")]
#[ts(rename = "edgeId")]
edge_id: uuid::Uuid,
tag: Option<TagDeclaratorView>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "creatorType", rename_all = "camelCase")]
pub enum SolidCreatorView {
Sketch(SketchView),
Face {
face_id: uuid::Uuid,
solid_id: uuid::Uuid,
sketch: SketchView,
},
Edge {
edge_id: uuid::Uuid,
body_id: uuid::Uuid,
},
Procedural,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub enum SolidViewType {
#[default]
Solid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export, rename = "SolidView")]
#[serde(rename_all = "camelCase")]
pub struct SolidView {
#[serde(rename = "type")]
#[ts(rename = "type", type = "\"Solid\"")]
pub type_: SolidViewType,
pub id: uuid::Uuid,
pub original_id: uuid::Uuid,
pub topology_id: uuid::Uuid,
pub artifact_id: ArtifactId,
pub value: Vec<ExtrudeSurfaceView>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub faces: IndexMap<String, TagIdentifierView>,
#[serde(rename = "sketch")]
pub creator: SolidCreatorView,
pub start_cap_id: Option<uuid::Uuid>,
pub end_cap_id: Option<uuid::Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cuts: Vec<EdgeCutView>,
pub units: UnitLength,
pub sectional: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_view_round_trips_through_json() {
let value = KclValueView::Object {
value: IndexMap::from([
(
"length".to_owned(),
KclValueView::Number {
value: 12.5,
ty: NumericType::default(),
},
),
(
"tag".to_owned(),
KclValueView::TagDeclarator {
value: "edge01".to_owned(),
},
),
]),
constrainable: false,
};
let json = serde_json::to_value(&value).unwrap();
let round_trip = serde_json::from_value(json).unwrap();
assert_eq!(value, round_trip);
}
#[test]
fn tag_declarator_keeps_the_existing_wire_shape() {
let value = KclValueView::TagDeclarator {
value: "edge01".to_owned(),
};
assert_eq!(
serde_json::to_value(value).unwrap(),
serde_json::json!({ "type": "TagDeclarator", "value": "edge01" })
);
}
#[test]
fn nested_tag_views_keep_their_discriminators_and_source_location() {
let identifier = TagIdentifierView {
type_: TagIdentifierViewType::TagIdentifier,
value: "edge01".to_owned(),
};
assert_eq!(
serde_json::to_value(identifier).unwrap(),
serde_json::json!({ "type": "TagIdentifier", "value": "edge01" })
);
let declarator = TagDeclaratorView {
comment_start: 4,
end: 10,
module_id: ModuleId::default(),
start: 5,
type_: TagDeclaratorViewType::TagDeclarator,
name: "edge01".to_owned(),
digest: None,
};
assert_eq!(
serde_json::to_value(declarator).unwrap(),
serde_json::json!({
"commentStart": 4,
"end": 10,
"moduleId": 0,
"start": 5,
"type": "TagDeclarator",
"value": "edge01"
})
);
}
#[test]
fn edge_cut_views_keep_dimensions_and_camel_case_edge_ids() {
let edge_id = uuid::Uuid::nil();
let value = EdgeCutView::Fillet {
id: uuid::Uuid::nil(),
radius: NumericValueView {
n: 2.0,
ty: NumericType::default(),
},
edge_id,
tag: None,
};
let json = serde_json::to_value(value).unwrap();
assert_eq!(json["radius"]["n"], 2.0);
assert_eq!(json["edgeId"], edge_id.to_string());
assert!(json.get("edge_id").is_none());
let value = EdgeCutView::Chamfer {
id: uuid::Uuid::nil(),
length: NumericValueView {
n: 3.0,
ty: NumericType::default(),
},
edge_id,
tag: None,
};
let json = serde_json::to_value(value).unwrap();
assert_eq!(json["length"]["n"], 3.0);
assert_eq!(json["edgeId"], edge_id.to_string());
}
#[test]
fn face_parent_solid_view_keeps_face_provenance() {
let solid_id = uuid::Uuid::nil();
let value = FaceParentSolidView {
solid_id,
creator_sketch_id: Some(solid_id),
creator_sketch_is_closed: Some(ProfileClosedView::Explicitly),
edge_cut_ids: vec![solid_id],
};
let json = serde_json::to_value(value).unwrap();
assert_eq!(json["solidId"], solid_id.to_string());
assert_eq!(json["creatorSketchId"], solid_id.to_string());
assert_eq!(json["creatorSketchIsClosed"], "explicitly");
assert_eq!(json["edgeCutIds"][0], solid_id.to_string());
}
}