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
205#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
206#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPlane")]
207#[serde(rename_all = "camelCase")]
208pub enum Plane {
209 Object(ObjectId),
210 Default(PlaneName),
211}
212
213#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
214#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFace")]
215#[serde(rename_all = "camelCase")]
216pub struct Face {
217 pub id: ObjectId,
218}
219
220#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
221#[ts(export, export_to = "FrontendApi.ts", rename = "ApiWall")]
222#[serde(rename_all = "camelCase")]
223pub struct Wall {
224 pub id: ObjectId,
225}
226
227#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
228#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCap")]
229#[serde(rename_all = "camelCase")]
230pub struct Cap {
231 pub id: ObjectId,
232 pub kind: CapKind,
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
236#[ts(export, export_to = "FrontendApi.ts", rename = "ApiCapKind")]
237#[serde(rename_all = "camelCase")]
238pub enum CapKind {
239 Start,
240 End,
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
244#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSourceRef")]
245#[serde(tag = "type")]
246pub enum SourceRef {
247 Simple {
248 range: SourceRange,
249 node_path: Option<NodePath>,
250 },
251 BackTrace {
252 ranges: Vec<(SourceRange, Option<NodePath>)>,
253 },
254}
255
256impl From<SourceRange> for SourceRef {
257 fn from(value: SourceRange) -> Self {
258 Self::Simple {
259 range: value,
260 node_path: None,
261 }
262 }
263}
264
265impl SourceRef {
266 pub fn new(range: SourceRange, node_path: Option<NodePath>) -> Self {
267 Self::Simple { range, node_path }
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
272#[ts(export, export_to = "FrontendApi.ts")]
273pub struct Number {
274 pub value: f64,
275 pub units: NumericSuffix,
276}
277
278impl TryFrom<crate::std::args::TyF64> for Number {
279 type Error = crate::execution::types::NumericSuffixTypeConvertError;
280
281 fn try_from(value: crate::std::args::TyF64) -> std::result::Result<Self, Self::Error> {
282 Ok(Number {
283 value: value.n,
284 units: value.ty.try_into()?,
285 })
286 }
287}
288
289impl Number {
290 pub fn round(&self, digits: u8) -> Self {
291 let factor = 10f64.powi(digits as i32);
292 let rounded_value = (self.value * factor).round() / factor;
293 let value = if rounded_value == -0.0 { 0.0 } else { rounded_value };
295 Number {
296 value,
297 units: self.units,
298 }
299 }
300}
301
302impl From<(f64, UnitLength)> for Number {
303 fn from((value, units): (f64, UnitLength)) -> Self {
304 let units_suffix = NumericSuffix::from(units);
307 Number {
308 value,
309 units: units_suffix,
310 }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
315#[ts(export, export_to = "FrontendApi.ts")]
316#[serde(tag = "type")]
317pub enum Expr {
318 Number(Number),
319 Var(Number),
320 Variable(String),
321}
322
323#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
324#[ts(export, export_to = "FrontendApi.ts")]
325pub struct Error {
326 pub msg: String,
327}
328
329impl Error {
330 pub fn file_id_in_use(id: FileId, path: &str) -> Self {
331 Error {
332 msg: format!("File ID already in use: {id:?}, currently used for `{path}`"),
333 }
334 }
335
336 pub fn file_id_not_found(project_id: ProjectId, file_id: FileId) -> Self {
337 Error {
338 msg: format!("File ID not found in project: {file_id:?}, project: {project_id:?}"),
339 }
340 }
341
342 pub fn bad_project(found: ProjectId, expected: Option<ProjectId>) -> Self {
343 let msg = match expected {
344 Some(expected) => format!("Project ID mismatch found: {found:?}, expected: {expected:?}"),
345 None => format!("No open project, found: {found:?}"),
346 };
347 Error { msg }
348 }
349
350 pub fn bad_version(found: Version, expected: Version) -> Self {
351 Error {
352 msg: format!("Version mismatch found: {found:?}, expected: {expected:?}"),
353 }
354 }
355
356 pub fn bad_file(found: FileId, expected: Option<FileId>) -> Self {
357 let msg = match expected {
358 Some(expected) => format!("File ID mismatch found: {found:?}, expected: {expected:?}"),
359 None => format!("File ID not found: {found:?}"),
360 };
361 Error { msg }
362 }
363
364 pub fn serialize(e: impl serde::ser::Error) -> Self {
365 Error {
366 msg: format!(
367 "Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
368 ),
369 }
370 }
371
372 pub fn deserialize(name: &str, e: impl serde::de::Error) -> Self {
373 Error {
374 msg: format!(
375 "Could not deserialize argument `{name}`. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
376 ),
377 }
378 }
379}
380
381pub type Result<T> = std::result::Result<T, Error>;