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 PrimitiveFace(PrimitiveFacePlane),
220}
221
222#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
225#[ts(export, export_to = "FrontendApi.ts")]
226#[serde(rename_all = "camelCase")]
227pub struct PrimitiveFacePlane {
228 pub solid_id: uuid::Uuid,
230 pub index: usize,
231}
232
233#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
234#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFace")]
235#[serde(rename_all = "camelCase")]
236pub struct Face {
237 pub id: ObjectId,
238}
239
240#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
241#[ts(export, export_to = "FrontendApi.ts", rename = "ApiWall")]
242#[serde(rename_all = "camelCase")]
243pub struct Wall {
244 pub id: ObjectId,
245 #[serde(skip_deserializing)]
246 pub source: WallSource,
247 #[serde(default, skip_serializing_if = "Option::is_none")]
248 #[ts(optional)]
249 pub solid_output_index: Option<usize>,
250}
251
252#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
253#[ts(export, export_to = "FrontendApi.ts", rename = "ApiWallSource")]
254#[serde(rename_all = "camelCase")]
255pub struct WallSource {
256 pub solid: SourceRefRange,
257 pub sweep: SourceRefRange,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 #[ts(optional)]
260 pub path: Option<SourceRefRange>,
261 pub segment: SourceRefRange,
262}
263
264#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
265#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCap")]
266#[serde(rename_all = "camelCase")]
267pub struct Cap {
268 pub id: ObjectId,
269 pub kind: CapKind,
270 #[serde(skip_deserializing)]
271 pub source: CapSource,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 #[ts(optional)]
274 pub solid_output_index: Option<usize>,
275}
276
277#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
278#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCapSource")]
279#[serde(rename_all = "camelCase")]
280pub struct CapSource {
281 pub solid: SourceRefRange,
282 pub sweep: SourceRefRange,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
286#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCapKind")]
287#[serde(rename_all = "camelCase")]
288pub enum CapKind {
289 Start,
290 End,
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
294#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSourceRef")]
295#[serde(tag = "type")]
296pub enum SourceRef {
297 Simple {
298 range: SourceRange,
299 node_path: Option<NodePath>,
300 },
301 BackTrace {
302 ranges: Vec<(SourceRange, Option<NodePath>)>,
303 },
304}
305
306#[derive(Debug, Clone, Default, PartialEq, Serialize, ts_rs::TS)]
307#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSourceRefRange")]
308#[serde(rename_all = "camelCase")]
309pub struct SourceRefRange {
310 pub range: SourceRange,
311 pub node_path: Option<NodePath>,
312}
313
314impl From<SourceRange> for SourceRef {
315 fn from(value: SourceRange) -> Self {
316 Self::Simple {
317 range: value,
318 node_path: None,
319 }
320 }
321}
322
323impl SourceRef {
324 pub fn new(range: SourceRange, node_path: Option<NodePath>) -> Self {
325 Self::Simple { range, node_path }
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
330#[ts(export, export_to = "FrontendApi.ts")]
331pub struct Number {
332 pub value: f64,
333 pub units: NumericSuffix,
334}
335
336impl TryFrom<crate::std::args::TyF64> for Number {
337 type Error = crate::execution::types::NumericSuffixTypeConvertError;
338
339 fn try_from(value: crate::std::args::TyF64) -> std::result::Result<Self, Self::Error> {
340 Ok(Number {
341 value: value.n,
342 units: value.ty.try_into()?,
343 })
344 }
345}
346
347impl Number {
348 pub fn round(&self, digits: u8) -> Self {
349 let factor = 10f64.powi(digits as i32);
350 let rounded_value = (self.value * factor).round() / factor;
351 let value = if rounded_value == -0.0 { 0.0 } else { rounded_value };
353 Number {
354 value,
355 units: self.units,
356 }
357 }
358}
359
360impl From<(f64, UnitLength)> for Number {
361 fn from((value, units): (f64, UnitLength)) -> Self {
362 let units_suffix = NumericSuffix::from(units);
365 Number {
366 value,
367 units: units_suffix,
368 }
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
373#[ts(export, export_to = "FrontendApi.ts")]
374#[serde(tag = "type")]
375pub enum Expr {
376 Number(Number),
377 Var(Number),
378 Variable(String),
379}
380
381#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
382#[ts(export, export_to = "FrontendApi.ts")]
383pub struct Error {
384 pub msg: String,
385}
386
387impl Error {
388 pub fn file_id_in_use(id: FileId, path: &str) -> Self {
389 Error {
390 msg: format!("File ID already in use: {id:?}, currently used for `{path}`"),
391 }
392 }
393
394 pub fn file_id_not_found(project_id: ProjectId, file_id: FileId) -> Self {
395 Error {
396 msg: format!("File ID not found in project: {file_id:?}, project: {project_id:?}"),
397 }
398 }
399
400 pub fn bad_project(found: ProjectId, expected: Option<ProjectId>) -> Self {
401 let msg = match expected {
402 Some(expected) => format!("Project ID mismatch found: {found:?}, expected: {expected:?}"),
403 None => format!("No open project, found: {found:?}"),
404 };
405 Error { msg }
406 }
407
408 pub fn bad_version(found: Version, expected: Version) -> Self {
409 Error {
410 msg: format!("Version mismatch found: {found:?}, expected: {expected:?}"),
411 }
412 }
413
414 pub fn bad_file(found: FileId, expected: Option<FileId>) -> Self {
415 let msg = match expected {
416 Some(expected) => format!("File ID mismatch found: {found:?}, expected: {expected:?}"),
417 None => format!("File ID not found: {found:?}"),
418 };
419 Error { msg }
420 }
421
422 pub fn serialize(e: impl serde::ser::Error) -> Self {
423 Error {
424 msg: format!(
425 "Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
426 ),
427 }
428 }
429
430 pub fn deserialize(name: &str, e: impl serde::de::Error) -> Self {
431 Error {
432 msg: format!(
433 "Could not deserialize argument `{name}`. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
434 ),
435 }
436 }
437}
438
439pub type Result<T> = std::result::Result<T, Error>;