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