1pub mod async_tasks;
4#[cfg(target_arch = "wasm32")]
5#[cfg(feature = "engine")]
6pub mod conn_wasm;
7#[cfg(feature = "engine")]
8pub mod engine_manager;
9
10use std::sync::Arc;
11use std::sync::atomic::AtomicUsize;
12use std::sync::atomic::Ordering;
13
14pub use async_tasks::AsyncTasks;
15use indexmap::IndexMap;
16pub use kcl_api::PlaneName;
17use kcl_api::UnitLength;
18use kcmc::ModelingCmd;
19use kcmc::each_cmd as mcmd;
20use kcmc::websocket::WebSocketRequest;
21use kittycad_modeling_cmds::{self as kcmc};
22use tokio::sync::RwLock;
23use uuid::Uuid;
24
25use crate::SourceRange;
26use crate::execution::PlaneInfo;
27use crate::execution::Point3d;
28use crate::unit_conversion::ToKcmc;
29
30lazy_static::lazy_static! {
31 pub static ref GRID_OBJECT_ID: uuid::Uuid = uuid::Uuid::parse_str("cfa78409-653d-4c26-96f1-7c45fb784840").unwrap();
32
33 pub static ref GRID_SCALE_TEXT_OBJECT_ID: uuid::Uuid = uuid::Uuid::parse_str("10782f33-f588-4668-8bcd-040502d26590").unwrap();
34
35 pub static ref DEFAULT_PLANE_INFO: IndexMap<PlaneName, PlaneInfo> = IndexMap::from([
36 (
37 PlaneName::Xy,
38 PlaneInfo {
39 origin: Point3d::new(0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
40 x_axis: Point3d::new(1.0, 0.0, 0.0, None),
41 y_axis: Point3d::new(0.0, 1.0, 0.0, None),
42 z_axis: Point3d::new(0.0, 0.0, 1.0, None),
43 },
44 ),
45 (
46 PlaneName::NegXy,
47 PlaneInfo {
48 origin: Point3d::new( 0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
49 x_axis: Point3d::new(-1.0, 0.0, 0.0, None),
50 y_axis: Point3d::new( 0.0, 1.0, 0.0, None),
51 z_axis: Point3d::new( 0.0, 0.0, -1.0, None),
52 },
53 ),
54 (
55 PlaneName::Xz,
56 PlaneInfo {
57 origin: Point3d::new(0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
58 x_axis: Point3d::new(1.0, 0.0, 0.0, None),
59 y_axis: Point3d::new(0.0, 0.0, 1.0, None),
60 z_axis: Point3d::new(0.0, -1.0, 0.0, None),
61 },
62 ),
63 (
64 PlaneName::NegXz,
65 PlaneInfo {
66 origin: Point3d::new( 0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
67 x_axis: Point3d::new(-1.0, 0.0, 0.0, None),
68 y_axis: Point3d::new( 0.0, 0.0, 1.0, None),
69 z_axis: Point3d::new( 0.0, 1.0, 0.0, None),
70 },
71 ),
72 (
73 PlaneName::Yz,
74 PlaneInfo {
75 origin: Point3d::new(0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
76 x_axis: Point3d::new(0.0, 1.0, 0.0, None),
77 y_axis: Point3d::new(0.0, 0.0, 1.0, None),
78 z_axis: Point3d::new(1.0, 0.0, 0.0, None),
79 },
80 ),
81 (
82 PlaneName::NegYz,
83 PlaneInfo {
84 origin: Point3d::new( 0.0, 0.0, 0.0, Some(UnitLength::Millimeters)),
85 x_axis: Point3d::new( 0.0, -1.0, 0.0, None),
86 y_axis: Point3d::new( 0.0, 0.0, 1.0, None),
87 z_axis: Point3d::new(-1.0, 0.0, 0.0, None),
88 },
89 ),
90 ]);
91}
92
93#[derive(Debug, Clone)]
99pub struct EngineBatchContext {
100 batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
101 batch_end: Arc<RwLock<IndexMap<Uuid, (WebSocketRequest, SourceRange)>>>,
102}
103
104impl Default for EngineBatchContext {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110impl EngineBatchContext {
111 pub fn new() -> Self {
112 Self {
113 batch: Arc::new(RwLock::new(Vec::new())),
114 batch_end: Arc::new(RwLock::new(IndexMap::new())),
115 }
116 }
117
118 pub async fn is_empty(&self) -> bool {
119 self.batch.read().await.is_empty() && self.batch_end.read().await.is_empty()
120 }
121
122 async fn clear(&self) {
123 self.batch.write().await.clear();
124 self.batch_end.write().await.clear();
125 }
126
127 async fn push(&self, req: WebSocketRequest, source_range: SourceRange) {
128 self.batch.write().await.push((req, source_range));
129 }
130
131 async fn extend(&self, requests: Vec<(WebSocketRequest, SourceRange)>) {
132 self.batch.write().await.extend(requests);
133 }
134
135 async fn insert_end(&self, id: Uuid, req: WebSocketRequest, source_range: SourceRange) {
136 self.batch_end.write().await.insert(id, (req, source_range));
137 }
138
139 pub(crate) async fn move_batch_end_to_batch(&self, ids: Vec<Uuid>) {
140 let mut moved = Vec::new();
141 {
142 let mut batch_end = self.batch_end.write().await;
143 for id in ids {
144 let Some(item) = batch_end.shift_remove(&id) else {
145 continue;
146 };
147 moved.push(item);
148 }
149 }
150
151 self.extend(moved).await;
152 }
153
154 async fn take_batch(&self) -> Vec<(WebSocketRequest, SourceRange)> {
155 std::mem::take(&mut *self.batch.write().await)
156 }
157
158 async fn take_batch_end(&self) -> IndexMap<Uuid, (WebSocketRequest, SourceRange)> {
159 std::mem::take(&mut *self.batch_end.write().await)
160 }
161}
162
163#[derive(Default, Debug)]
164pub struct EngineStats {
165 pub commands_batched: AtomicUsize,
166 pub batches_sent: AtomicUsize,
167}
168
169impl Clone for EngineStats {
170 fn clone(&self) -> Self {
171 Self {
172 commands_batched: AtomicUsize::new(self.commands_batched.load(Ordering::Relaxed)),
173 batches_sent: AtomicUsize::new(self.batches_sent.load(Ordering::Relaxed)),
174 }
175 }
176}
177
178#[cfg(not(target_arch = "wasm32"))]
180pub fn new_zoo_client(token: Option<String>, engine_addr: Option<String>) -> anyhow::Result<kittycad::Client> {
181 let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
182 let http_client = reqwest::Client::builder()
183 .user_agent(user_agent)
184 .timeout(std::time::Duration::from_secs(600))
186 .connect_timeout(std::time::Duration::from_secs(60));
187 let ws_client = reqwest::Client::builder()
188 .user_agent(user_agent)
189 .timeout(std::time::Duration::from_secs(600))
191 .connect_timeout(std::time::Duration::from_secs(60))
192 .connection_verbose(true)
193 .tcp_keepalive(std::time::Duration::from_secs(600))
194 .http1_only();
195
196 let zoo_token_env = std::env::var("ZOO_API_TOKEN");
197
198 let token = if let Some(token) = token {
199 token
200 } else if let Ok(token) = std::env::var("KITTYCAD_API_TOKEN") {
201 if let Ok(zoo_token) = zoo_token_env
202 && zoo_token != token
203 {
204 return Err(anyhow::anyhow!(
205 "Both environment variables KITTYCAD_API_TOKEN=`{}` and ZOO_API_TOKEN=`{}` are set. Use only one.",
206 token,
207 zoo_token
208 ));
209 }
210 token
211 } else if let Ok(token) = zoo_token_env {
212 token
213 } else {
214 return Err(anyhow::anyhow!(
215 "No API token found in environment variables. Use ZOO_API_TOKEN"
216 ));
217 };
218
219 let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
221 let kittycad_host_env = std::env::var("KITTYCAD_HOST");
223 if let Some(addr) = engine_addr {
224 client.set_base_url(addr);
225 } else if let Ok(addr) = std::env::var("ZOO_HOST") {
226 if let Ok(kittycad_host) = kittycad_host_env
227 && kittycad_host != addr
228 {
229 return Err(anyhow::anyhow!(
230 "Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one.",
231 kittycad_host,
232 addr
233 ));
234 }
235 client.set_base_url(addr);
236 } else if let Ok(addr) = kittycad_host_env {
237 client.set_base_url(addr);
238 }
239
240 Ok(client)
241}
242
243#[derive(Copy, Clone, Debug)]
244pub enum GridScaleBehavior {
245 ScaleWithZoom,
246 Fixed(Option<UnitLength>),
247}
248
249impl GridScaleBehavior {
250 fn into_modeling_cmd(self) -> ModelingCmd {
251 const NUMBER_OF_GRID_COLUMNS: f32 = 10.0;
252 match self {
253 GridScaleBehavior::ScaleWithZoom => ModelingCmd::from(mcmd::SetGridAutoScale::builder().build()),
254 GridScaleBehavior::Fixed(unit_length) => ModelingCmd::from(
255 mcmd::SetGridScale::builder()
256 .value(NUMBER_OF_GRID_COLUMNS)
257 .units(unit_length.unwrap_or(UnitLength::Millimeters).to_kcmc())
258 .build(),
259 ),
260 }
261 }
262}