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