1#![allow(async_fn_in_trait)]
4
5use kcl_api::Number;
6use kcl_api::Object;
7use kcl_api::ObjectId;
8use kcl_api::ObjectKind;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::ExecOutcome;
13pub use crate::ExecutorSettings as Settings;
14
15pub trait LifecycleApi {
16 async fn open_project(&self, project: ProjectId, files: Vec<File>, open_file: FileId) -> Result<()>;
17 async fn get_project(&self, project: ProjectId) -> Result<Vec<File>>;
18 async fn add_file(&self, project: ProjectId, file: File) -> Result<()>;
19 async fn get_file(&self, project: ProjectId, file: FileId) -> Result<File>;
20 async fn remove_file(&self, project: ProjectId, file: FileId) -> Result<()>;
21 async fn update_file(&self, project: ProjectId, file: FileId, text: String) -> Result<()>;
23 async fn switch_file(&self, project: ProjectId, file: FileId) -> Result<()>;
24 async fn refresh(&self, project: ProjectId) -> Result<()>;
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, ts_rs::TS)]
28#[ts(export, export_to = "FrontendApi.ts")]
29pub struct SceneGraph {
30 pub project: ProjectId,
31 pub file: FileId,
32 pub version: Version,
33
34 pub objects: Vec<Object>,
35 pub settings: Settings,
36 pub sketch_mode: Option<ObjectId>,
37}
38
39impl SceneGraph {
40 pub fn empty(project: ProjectId, file: FileId, version: Version) -> Self {
41 SceneGraph {
42 project,
43 file,
44 version,
45 objects: Vec::new(),
46 settings: Default::default(),
47 sketch_mode: None,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Serialize, ts_rs::TS)]
53#[ts(export, export_to = "FrontendApi.ts")]
54pub struct SceneGraphDelta {
55 pub new_graph: SceneGraph,
56 pub new_objects: Vec<ObjectId>,
57 pub invalidates_ids: bool,
58 pub exec_outcome: ExecOutcome,
59}
60
61impl SceneGraphDelta {
62 pub fn new(
63 new_graph: SceneGraph,
64 new_objects: Vec<ObjectId>,
65 invalidates_ids: bool,
66 exec_outcome: ExecOutcome,
67 ) -> Self {
68 SceneGraphDelta {
69 new_graph,
70 new_objects,
71 invalidates_ids,
72 exec_outcome,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
78#[ts(export, export_to = "FrontendApi.ts")]
79pub struct SourceDelta {
80 pub text: String,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ts_rs::TS)]
84pub struct SketchCheckpointId(u64);
85
86impl SketchCheckpointId {
87 pub(crate) fn new(n: u64) -> Self {
88 Self(n)
89 }
90}
91
92#[derive(Debug, Clone, Serialize, ts_rs::TS)]
93#[ts(export, export_to = "FrontendApi.ts")]
94#[serde(rename_all = "camelCase")]
95pub struct SketchMutationOutcome {
96 pub source_delta: SourceDelta,
97 pub scene_graph_delta: SceneGraphDelta,
98 pub checkpoint_id: Option<SketchCheckpointId>,
99}
100
101#[derive(Debug, Clone, Serialize, ts_rs::TS)]
102#[ts(export, export_to = "FrontendApi.ts")]
103#[serde(rename_all = "camelCase")]
104pub struct NewSketchOutcome {
105 pub source_delta: SourceDelta,
106 pub scene_graph_delta: SceneGraphDelta,
107 pub sketch_id: ObjectId,
108 pub checkpoint_id: Option<SketchCheckpointId>,
109}
110
111#[derive(Debug, Clone, Serialize, ts_rs::TS)]
112#[ts(export, export_to = "FrontendApi.ts")]
113#[serde(rename_all = "camelCase")]
114pub struct EditSketchOutcome {
115 pub scene_graph_delta: SceneGraphDelta,
116 pub checkpoint_id: Option<SketchCheckpointId>,
117}
118
119#[derive(Debug, Clone, Serialize, ts_rs::TS)]
120#[ts(export, export_to = "FrontendApi.ts")]
121#[serde(rename_all = "camelCase")]
122pub struct RestoreSketchCheckpointOutcome {
123 pub source_delta: SourceDelta,
124 pub scene_graph_delta: SceneGraphDelta,
125}
126
127#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ts_rs::TS)]
128#[ts(export, export_to = "FrontendApi.ts", rename = "ApiVersion")]
129pub struct Version(pub usize);
130
131#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
132#[ts(export, export_to = "FrontendApi.ts", rename = "ApiProjectId")]
133pub struct ProjectId(pub usize);
134
135#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
136#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFileId")]
137pub struct FileId(pub usize);
138
139#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
140#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFile")]
141pub struct File {
142 pub id: FileId,
143 pub path: String,
144 pub text: String,
145}
146
147pub trait ObjectKindExt {
149 fn human_friendly_kind_with_article(&self) -> &'static str;
152}
153
154impl ObjectKindExt for ObjectKind {
155 fn human_friendly_kind_with_article(&self) -> &'static str {
156 match self {
157 Self::Nil => "a Nil",
158 Self::Plane(..) => "a Plane",
159 Self::Face(..) => "a Face",
160 Self::Wall(..) => "a Wall",
161 Self::Cap(..) => "a Cap",
162 Self::Sketch(..) => "a Sketch",
163 Self::Segment { .. } => "a Segment",
164 Self::Constraint { .. } => "a Constraint",
165 }
166 }
167}
168
169impl TryFrom<crate::std::args::TyF64> for Number {
170 type Error = kcl_api::NumericSuffixTypeConvertError;
171
172 fn try_from(value: crate::std::args::TyF64) -> std::result::Result<Self, Self::Error> {
173 Ok(Number {
174 value: value.n,
175 units: value.ty.try_into()?,
176 })
177 }
178}
179
180pub trait NumberExt {
182 fn round(&self, digits: u8) -> Self;
183}
184
185impl NumberExt for Number {
186 fn round(&self, digits: u8) -> Self {
187 let factor = 10f64.powi(digits as i32);
188 let rounded_value = (self.value * factor).round() / factor;
189 let value = if rounded_value == -0.0 { 0.0 } else { rounded_value };
191 Number {
192 value,
193 units: self.units,
194 }
195 }
196}
197
198#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
199#[ts(export, export_to = "FrontendApi.ts")]
200pub struct Error {
201 pub msg: String,
202}
203
204impl Error {
205 pub fn file_id_in_use(id: FileId, path: &str) -> Self {
206 Error {
207 msg: format!("File ID already in use: {id:?}, currently used for `{path}`"),
208 }
209 }
210
211 pub fn file_id_not_found(project_id: ProjectId, file_id: FileId) -> Self {
212 Error {
213 msg: format!("File ID not found in project: {file_id:?}, project: {project_id:?}"),
214 }
215 }
216
217 pub fn bad_project(found: ProjectId, expected: Option<ProjectId>) -> Self {
218 let msg = match expected {
219 Some(expected) => format!("Project ID mismatch found: {found:?}, expected: {expected:?}"),
220 None => format!("No open project, found: {found:?}"),
221 };
222 Error { msg }
223 }
224
225 pub fn bad_version(found: Version, expected: Version) -> Self {
226 Error {
227 msg: format!("Version mismatch found: {found:?}, expected: {expected:?}"),
228 }
229 }
230
231 pub fn bad_file(found: FileId, expected: Option<FileId>) -> Self {
232 let msg = match expected {
233 Some(expected) => format!("File ID mismatch found: {found:?}, expected: {expected:?}"),
234 None => format!("File ID not found: {found:?}"),
235 };
236 Error { msg }
237 }
238
239 pub fn serialize(e: impl serde::ser::Error) -> Self {
240 Error {
241 msg: format!(
242 "Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
243 ),
244 }
245 }
246
247 pub fn deserialize(name: &str, e: impl serde::de::Error) -> Self {
248 Error {
249 msg: format!(
250 "Could not deserialize argument `{name}`. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
251 ),
252 }
253 }
254}
255
256pub type Result<T> = std::result::Result<T, Error>;