1#![allow(async_fn_in_trait)]
4
5pub use kcl_api::ObjectId;
6use kcl_api::UnitLength;
7use kcl_error::SourceRange;
8use serde::Deserialize;
9use serde::Serialize;
10
11use crate::ExecOutcome;
12pub use crate::ExecutorSettings as Settings;
13use crate::NodePath;
14use crate::engine::PlaneName;
15use crate::execution::ArtifactId;
16use crate::pretty::NumericSuffix;
17
18pub trait LifecycleApi {
19 async fn open_project(&self, project: ProjectId, files: Vec<File>, open_file: FileId) -> Result<()>;
20 async fn get_project(&self, project: ProjectId) -> Result<Vec<File>>;
21 async fn add_file(&self, project: ProjectId, file: File) -> Result<()>;
22 async fn get_file(&self, project: ProjectId, file: FileId) -> Result<File>;
23 async fn remove_file(&self, project: ProjectId, file: FileId) -> Result<()>;
24 async fn update_file(&self, project: ProjectId, file: FileId, text: String) -> Result<()>;
26 async fn switch_file(&self, project: ProjectId, file: FileId) -> Result<()>;
27 async fn refresh(&self, project: ProjectId) -> Result<()>;
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
31#[ts(export, export_to = "FrontendApi.ts")]
32pub struct SceneGraph {
33 pub project: ProjectId,
34 pub file: FileId,
35 pub version: Version,
36
37 pub objects: Vec<Object>,
38 pub settings: Settings,
39 pub sketch_mode: Option<ObjectId>,
40}
41
42impl SceneGraph {
43 pub fn empty(project: ProjectId, file: FileId, version: Version) -> Self {
44 SceneGraph {
45 project,
46 file,
47 version,
48 objects: Vec::new(),
49 settings: Default::default(),
50 sketch_mode: None,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Serialize, ts_rs::TS)]
56#[ts(export, export_to = "FrontendApi.ts")]
57pub struct SceneGraphDelta {
58 pub new_graph: SceneGraph,
59 pub new_objects: Vec<ObjectId>,
60 pub invalidates_ids: bool,
61 pub exec_outcome: ExecOutcome,
62}
63
64impl SceneGraphDelta {
65 pub fn new(
66 new_graph: SceneGraph,
67 new_objects: Vec<ObjectId>,
68 invalidates_ids: bool,
69 exec_outcome: ExecOutcome,
70 ) -> Self {
71 SceneGraphDelta {
72 new_graph,
73 new_objects,
74 invalidates_ids,
75 exec_outcome,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
81#[ts(export, export_to = "FrontendApi.ts")]
82pub struct SourceDelta {
83 pub text: String,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ts_rs::TS)]
87pub struct SketchCheckpointId(u64);
88
89impl SketchCheckpointId {
90 pub(crate) fn new(n: u64) -> Self {
91 Self(n)
92 }
93}
94
95#[derive(Debug, Clone, Serialize, ts_rs::TS)]
96#[ts(export, export_to = "FrontendApi.ts")]
97#[serde(rename_all = "camelCase")]
98pub struct SketchMutationOutcome {
99 pub source_delta: SourceDelta,
100 pub scene_graph_delta: SceneGraphDelta,
101 pub checkpoint_id: Option<SketchCheckpointId>,
102}
103
104#[derive(Debug, Clone, Serialize, ts_rs::TS)]
105#[ts(export, export_to = "FrontendApi.ts")]
106#[serde(rename_all = "camelCase")]
107pub struct NewSketchOutcome {
108 pub source_delta: SourceDelta,
109 pub scene_graph_delta: SceneGraphDelta,
110 pub sketch_id: ObjectId,
111 pub checkpoint_id: Option<SketchCheckpointId>,
112}
113
114#[derive(Debug, Clone, Serialize, ts_rs::TS)]
115#[ts(export, export_to = "FrontendApi.ts")]
116#[serde(rename_all = "camelCase")]
117pub struct EditSketchOutcome {
118 pub scene_graph_delta: SceneGraphDelta,
119 pub checkpoint_id: Option<SketchCheckpointId>,
120}
121
122#[derive(Debug, Clone, Serialize, ts_rs::TS)]
123#[ts(export, export_to = "FrontendApi.ts")]
124#[serde(rename_all = "camelCase")]
125pub struct RestoreSketchCheckpointOutcome {
126 pub source_delta: SourceDelta,
127 pub scene_graph_delta: SceneGraphDelta,
128}
129
130#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ts_rs::TS)]
131#[ts(export, export_to = "FrontendApi.ts", rename = "ApiVersion")]
132pub struct Version(pub usize);
133
134#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
135#[ts(export, export_to = "FrontendApi.ts", rename = "ApiProjectId")]
136pub struct ProjectId(pub usize);
137
138#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
139#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFileId")]
140pub struct FileId(pub usize);
141
142#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
143#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFile")]
144pub struct File {
145 pub id: FileId,
146 pub path: String,
147 pub text: String,
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
151#[ts(export, export_to = "FrontendApi.ts", rename = "ApiObject")]
152pub struct Object {
153 pub id: ObjectId,
154 pub kind: ObjectKind,
155 pub label: String,
156 pub comments: String,
157 pub artifact_id: ArtifactId,
158 pub source: SourceRef,
159}
160
161impl Object {
162 pub fn placeholder(id: ObjectId, range: SourceRange, node_path: Option<NodePath>) -> Self {
163 Object {
164 id,
165 kind: ObjectKind::Nil,
166 label: Default::default(),
167 comments: Default::default(),
168 artifact_id: ArtifactId::placeholder(),
169 source: SourceRef::new(range, node_path),
170 }
171 }
172}
173
174#[allow(clippy::large_enum_variant)]
175#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
176#[ts(export, export_to = "FrontendApi.ts", rename = "ApiObjectKind")]
177#[serde(tag = "type")]
178pub enum ObjectKind {
179 Nil,
181 Plane(Plane),
182 Face(Face),
183 Wall(Wall),
184 Cap(Cap),
185 Sketch(crate::frontend::sketch::Sketch),
186 Segment {
189 segment: crate::frontend::sketch::Segment,
190 },
191 Constraint {
192 constraint: crate::frontend::sketch::Constraint,
193 },
194}
195
196impl ObjectKind {
197 pub fn human_friendly_kind_with_article(&self) -> &'static str {
200 match self {
201 Self::Nil => "a Nil",
202 Self::Plane(..) => "a Plane",
203 Self::Face(..) => "a Face",
204 Self::Wall(..) => "a Wall",
205 Self::Cap(..) => "a Cap",
206 Self::Sketch(..) => "a Sketch",
207 Self::Segment { .. } => "a Segment",
208 Self::Constraint { .. } => "a Constraint",
209 }
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
214#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPlane")]
215#[serde(rename_all = "camelCase")]
216pub enum Plane {
217 Object(ObjectId),
218 Default(PlaneName),
219}
220
221#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
222#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFace")]
223#[serde(rename_all = "camelCase")]
224pub struct Face {
225 pub id: ObjectId,
226}
227
228#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
229#[ts(export, export_to = "FrontendApi.ts", rename = "ApiWall")]
230#[serde(rename_all = "camelCase")]
231pub struct Wall {
232 pub id: ObjectId,
233}
234
235#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
236#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCap")]
237#[serde(rename_all = "camelCase")]
238pub struct Cap {
239 pub id: ObjectId,
240 pub kind: CapKind,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
244#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCapKind")]
245#[serde(rename_all = "camelCase")]
246pub enum CapKind {
247 Start,
248 End,
249}
250
251#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
252#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSourceRef")]
253#[serde(tag = "type")]
254pub enum SourceRef {
255 Simple {
256 range: SourceRange,
257 node_path: Option<NodePath>,
258 },
259 BackTrace {
260 ranges: Vec<(SourceRange, Option<NodePath>)>,
261 },
262}
263
264impl From<SourceRange> for SourceRef {
265 fn from(value: SourceRange) -> Self {
266 Self::Simple {
267 range: value,
268 node_path: None,
269 }
270 }
271}
272
273impl SourceRef {
274 pub fn new(range: SourceRange, node_path: Option<NodePath>) -> Self {
275 Self::Simple { range, node_path }
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
280#[ts(export, export_to = "FrontendApi.ts")]
281pub struct Number {
282 pub value: f64,
283 pub units: NumericSuffix,
284}
285
286impl TryFrom<crate::std::args::TyF64> for Number {
287 type Error = crate::execution::types::NumericSuffixTypeConvertError;
288
289 fn try_from(value: crate::std::args::TyF64) -> std::result::Result<Self, Self::Error> {
290 Ok(Number {
291 value: value.n,
292 units: value.ty.try_into()?,
293 })
294 }
295}
296
297impl Number {
298 pub fn round(&self, digits: u8) -> Self {
299 let factor = 10f64.powi(digits as i32);
300 let rounded_value = (self.value * factor).round() / factor;
301 let value = if rounded_value == -0.0 { 0.0 } else { rounded_value };
303 Number {
304 value,
305 units: self.units,
306 }
307 }
308}
309
310impl From<(f64, UnitLength)> for Number {
311 fn from((value, units): (f64, UnitLength)) -> Self {
312 let units_suffix = NumericSuffix::from(units);
315 Number {
316 value,
317 units: units_suffix,
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
323#[ts(export, export_to = "FrontendApi.ts")]
324#[serde(tag = "type")]
325pub enum Expr {
326 Number(Number),
327 Var(Number),
328 Variable(String),
329}
330
331#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
332#[ts(export, export_to = "FrontendApi.ts")]
333pub struct Error {
334 pub msg: String,
335}
336
337impl Error {
338 pub fn file_id_in_use(id: FileId, path: &str) -> Self {
339 Error {
340 msg: format!("File ID already in use: {id:?}, currently used for `{path}`"),
341 }
342 }
343
344 pub fn file_id_not_found(project_id: ProjectId, file_id: FileId) -> Self {
345 Error {
346 msg: format!("File ID not found in project: {file_id:?}, project: {project_id:?}"),
347 }
348 }
349
350 pub fn bad_project(found: ProjectId, expected: Option<ProjectId>) -> Self {
351 let msg = match expected {
352 Some(expected) => format!("Project ID mismatch found: {found:?}, expected: {expected:?}"),
353 None => format!("No open project, found: {found:?}"),
354 };
355 Error { msg }
356 }
357
358 pub fn bad_version(found: Version, expected: Version) -> Self {
359 Error {
360 msg: format!("Version mismatch found: {found:?}, expected: {expected:?}"),
361 }
362 }
363
364 pub fn bad_file(found: FileId, expected: Option<FileId>) -> Self {
365 let msg = match expected {
366 Some(expected) => format!("File ID mismatch found: {found:?}, expected: {expected:?}"),
367 None => format!("File ID not found: {found:?}"),
368 };
369 Error { msg }
370 }
371
372 pub fn serialize(e: impl serde::ser::Error) -> Self {
373 Error {
374 msg: format!(
375 "Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
376 ),
377 }
378 }
379
380 pub fn deserialize(name: &str, e: impl serde::de::Error) -> Self {
381 Error {
382 msg: format!(
383 "Could not deserialize argument `{name}`. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
384 ),
385 }
386 }
387}
388
389pub type Result<T> = std::result::Result<T, Error>;