Compare commits

...

9 Commits

Author SHA1 Message Date
3dfc2c86e1 Revert "Fix isomorphic-copy to work on Windows (#5417)" (#5424)
This reverts commit 18d87b99bd.
2025-02-19 11:31:14 -05:00
71647ede29 Release KCL 38 (#5421) 2025-02-19 10:26:02 -05:00
cd679f4be3 Fix units not getting set properly with whole module imports (#5418)
* Add test for whole modules with non-default units

* Update output files since adding test

* Fix to not drop batched commands due to isolated mode

* Update output after fix

* Update other sim test outputs
2025-02-19 16:11:11 +11:00
18d87b99bd Fix isomorphic-copy to work on Windows (#5417)
The current command uses the proper `copy` command but does not use
`\`'s, so it fails on my Windows machine.
2025-02-18 20:25:12 -05:00
70a2202877 change everything to rwlocks for thread safety (#5416)
* make everything in engine a rwlock and cleanup repetitive code

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-02-18 21:50:13 +00:00
f5c9f84ae9 test equipping tools mid tool use removes have baked expression (#5403)
test equiping tools mid tool use removes have baked expression
2025-02-19 07:45:44 +11:00
71f701dec7 Don't auto-hide menu bar on Windows and Linux (#5415)
This auto-hide behavior causes the bar to toggle visible and hidden when the user hits the <kbd>Alt</kbd> key, which can be quite often especially with Trackpad Friendly controls enabled.
2025-02-19 07:45:29 +11:00
bffbed1d42 Release KCL 37 (#5409)
* Use kcl-samples 'next' branch

* Release KCL 37
2025-02-18 12:29:34 -06:00
9f60ed8e75 Fix yarn lock after yarn install (#5408) 2025-02-18 12:38:28 -05:00
262 changed files with 1396 additions and 1005 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1353,6 +1353,99 @@ test.describe(`Sketching with offset planes`, () => {
})
test.describe('multi-profile sketching', () => {
test(
`test it removes half-finished expressions when changing tools in sketch mode`,
{ tag: ['@skipWin'] },
async ({ context, page, scene, toolbar, editor, homePage }) => {
// We seed the scene with a single offset plane
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`yo = 5
sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([121.52, 168.25], sketch001)
|> line(end = [115.04, 113.61])
|> line(end = [130.87, -97.79])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile002 = startProfileAt([117.2, 56.08], sketch001)
|> line(end = [166.82, 25.89])
|> yLine(-107.86, %)
`
)
})
await homePage.goToModelingScene()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
await page.waitForTimeout(600)
const [circlePoint1] = scene.makeMouseHelpers(700, 200)
await test.step('equip circle tool and click first point', async () => {
await toolbar.circleBtn.click()
await page.waitForTimeout(100)
await circlePoint1()
await editor.expectEditor.toContain('profile003 = circle({ center = [')
})
await test.step('equip line tool and verify circle code is removed', async () => {
await toolbar.lineBtn.click()
await editor.expectEditor.not.toContain('profile003 = circle({')
})
const [circle3Point1] = scene.makeMouseHelpers(650, 200)
const [circle3Point2] = scene.makeMouseHelpers(750, 200)
await test.step('equip three point circle tool and click first two points', async () => {
await toolbar.selectCircleThreePoint()
await page.waitForTimeout(100)
await circle3Point1()
await page.waitForTimeout(100)
await circle3Point2()
await editor.expectEditor.toContain('profile003 = circleThreePoint(')
})
await test.step('equip line tool and verify three point circle code is removed', async () => {
await toolbar.lineBtn.click()
await editor.expectEditor.not.toContain(
'profile003 = circleThreePoint('
)
})
const [cornerRectPoint1] = scene.makeMouseHelpers(600, 300)
await test.step('equip corner rectangle tool and click first point', async () => {
await toolbar.rectangleBtn.click()
await page.waitForTimeout(100)
await cornerRectPoint1()
await editor.expectEditor.toContain('profile003 = startProfileAt(')
})
await test.step('equip line tool and verify corner rectangle code is removed', async () => {
await toolbar.lineBtn.click()
await editor.expectEditor.not.toContain('profile003 = startProfileAt(')
})
const [centerRectPoint1] = scene.makeMouseHelpers(700, 300)
await test.step('equip center rectangle tool and click first point', async () => {
await toolbar.selectCenterRectangle()
await page.waitForTimeout(100)
await centerRectPoint1()
await editor.expectEditor.toContain('profile003 = startProfileAt(')
})
await test.step('equip line tool and verify center rectangle code is removed', async () => {
await toolbar.lineBtn.click()
await editor.expectEditor.not.toContain('profile003 = startProfileAt(')
})
}
)
test(
`snapToProfile start only works for current profile`,
{ tag: ['@skipWin'] },
@ -1885,7 +1978,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
)
test(
'Can delete a profile in the editor while is sketch mode, and sketch mode does not break, can ctrl+z to undo after constraint with variable was added',
{ tag: ['@skipWin'] },
{ tag: ['@skipWin', '@skipLinux'] },
async ({ scene, toolbar, editor, cmdBar, page, homePage }) => {
await page.addInitScript(async () => {
localStorage.setItem(
@ -1969,7 +2062,7 @@ profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
activeLines: ['|>line(end = [-0.41,6.99])'],
highlightedCode: 'line(end = [-0.41,6.99])',
})
}).toPass({ timeout: 10_000, intervals: [1000] })
}).toPass({ timeout: 30_000, intervals: [1500] })
await toolbar.lengthConstraintBtn.click()
await cmdBar.progressCmdBar()

View File

@ -85,7 +85,7 @@
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/offset-plane-kwargs/manifest.json",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/next/manifest.json",
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",

View File

@ -1574,7 +1574,7 @@ export const ModelingMachineProvider = ({
: updatedSketchNodePaths[0]
}
if (doesNeedSplitting) {
if (doesNeedSplitting || indexToDelete >= 0) {
await kclManager.executeAstMock(moddedAst)
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
}

View File

@ -32,7 +32,7 @@ child_process.spawnSync('git', [
'clone',
'--single-branch',
'--branch',
'achalmers/offset-plane-kwargs',
'next',
URL_GIT_KCL_SAMPLES,
DIR_KCL_SAMPLES,
])

View File

@ -375,14 +375,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
icon: 'line',
status: 'available',
disabled: (state) =>
state.matches('Sketch no face') ||
state.matches({
Sketch: { 'Rectangle tool': 'Awaiting second corner' },
}) ||
state.matches({
Sketch: { 'Circle tool': 'Awaiting Radius' },
}),
disabled: (state) => state.matches('Sketch no face'),
title: 'Line',
hotkey: (state) =>
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',

View File

@ -82,7 +82,7 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
}
if (!newWindow) {
newWindow = new BrowserWindow({
autoHideMenuBar: true,
autoHideMenuBar: false,
show: false,
width: 1800,
height: 1200,

View File

@ -730,7 +730,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.36"
version = "0.1.38"
dependencies = [
"Inflector",
"anyhow",
@ -1712,7 +1712,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.35"
version = "0.2.38"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1779,7 +1779,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.21"
version = "0.1.38"
dependencies = [
"anyhow",
"hyper 0.14.32",

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.36"
version = "0.1.38"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.21"
version = "0.1.38"
edition = "2021"
license = "MIT"

View File

@ -1,7 +1,4 @@
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use indexmap::IndexMap;
@ -24,22 +21,20 @@ const NEED_PLANES: bool = true;
#[derive(Debug, Clone)]
pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
core_test: Arc<Mutex<String>>,
batch: Arc<RwLock<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
core_test: Arc<RwLock<String>>,
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
}
impl EngineConnection {
pub async fn new(result: Arc<Mutex<String>>) -> Result<EngineConnection> {
if let Ok(mut code) = result.lock() {
code.push_str(CPP_PREFIX);
}
pub async fn new(result: Arc<RwLock<String>>) -> Result<EngineConnection> {
result.write().await.push_str(CPP_PREFIX);
Ok(EngineConnection {
batch: Arc::new(Mutex::new(Vec::new())),
batch_end: Arc::new(Mutex::new(IndexMap::new())),
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
core_test: result,
default_planes: Default::default(),
execution_kind: Default::default(),
@ -362,29 +357,29 @@ fn codegen_cpp_repl_uuid_setters(reps_id: &str, entity_ids: &[uuid::Uuid]) -> St
#[async_trait::async_trait]
impl kcl_lib::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>> {
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>> {
self.batch_end.clone()
}
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
IndexMap::new()
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
Arc::new(RwLock::new(IndexMap::new()))
}
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
Vec::new()
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
Arc::new(RwLock::new(Vec::new()))
}
fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.lock().unwrap();
async fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.read().await;
*guard
}
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.lock().unwrap();
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.write().await;
let original = *guard;
*guard = execution_kind;
original
@ -435,24 +430,18 @@ impl kcl_lib::EngineManager for EngineConnection {
}) => {
let mut responses = HashMap::new();
for request in requests {
let (new_code, this_response);
let (new_code, this_response) = self.handle_command(&request.cmd_id, &request.cmd);
if let Ok(mut test_code) = self.core_test.lock() {
(new_code, this_response) = self.handle_command(&request.cmd_id, &request.cmd);
if !new_code.is_empty() {
let new_code = new_code
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
+ "\n";
//println!("{new_code}");
test_code.push_str(&new_code);
}
} else {
this_response = OkModelingCmdResponse::Empty {};
if !new_code.is_empty() {
let new_code = new_code
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
+ "\n";
//println!("{new_code}");
self.core_test.write().await.push_str(&new_code);
}
responses.insert(
@ -470,24 +459,18 @@ impl kcl_lib::EngineManager for EngineConnection {
}
WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd, cmd_id }) => {
//also handle unbatched requests inline
let (new_code, this_response);
let (new_code, this_response) = self.handle_command(&cmd_id, &cmd);
if let Ok(mut test_code) = self.core_test.lock() {
(new_code, this_response) = self.handle_command(&cmd_id, &cmd);
if !new_code.is_empty() {
let new_code = new_code
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
+ "\n";
//println!("{new_code}");
test_code.push_str(&new_code);
}
} else {
this_response = OkModelingCmdResponse::Empty {};
if !new_code.is_empty() {
let new_code = new_code
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(" ")
+ "\n";
//println!("{new_code}");
self.core_test.write().await.push_str(&new_code);
}
Ok(WebSocketResponse::Success(kcmc::websocket::SuccessWebSocketResponse {

View File

@ -1,7 +1,8 @@
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use anyhow::Result;
use kcl_lib::{ExecState, ExecutorContext};
use tokio::sync::RwLock;
#[cfg(not(target_arch = "wasm32"))]
mod conn_mock_core;
@ -10,7 +11,7 @@ mod conn_mock_core;
pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
let program = kcl_lib::Program::parse_no_errs(code)?;
let result = Arc::new(Mutex::new("".into()));
let result = Arc::new(RwLock::new("".into()));
let ref_result = Arc::clone(&result);
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
@ -18,6 +19,6 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
)));
ctx.run(&program, &mut ExecState::new(&ctx.settings)).await?;
let result = result.lock().expect("mutex lock").clone();
let result = result.read().await.clone();
Ok(result)
}

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.35"
version = "0.2.38"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,13 +1,9 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use anyhow::{anyhow, Result};
use dashmap::DashMap;
use futures::{SinkExt, StreamExt};
use indexmap::IndexMap;
use kcmc::{
@ -17,9 +13,7 @@ use kcmc::{
},
ModelingCmd,
};
use kittycad_modeling_cmds::{
self as kcmc, id::ModelingCmdId, ok_response::OkModelingCmdResponse, websocket::ModelingBatch,
};
use kittycad_modeling_cmds::{self as kcmc};
use tokio::sync::{mpsc, oneshot, RwLock};
use tokio_tungstenite::tungstenite::Message as WsMsg;
use uuid::Uuid;
@ -43,21 +37,21 @@ type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocket
pub struct EngineConnection {
engine_req_tx: mpsc::Sender<ToEngineReq>,
shutdown_tx: mpsc::Sender<()>,
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
pending_errors: Arc<Mutex<Vec<String>>>,
responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>>,
pending_errors: Arc<RwLock<Vec<String>>>,
#[allow(dead_code)]
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
socket_health: Arc<RwLock<SocketHealth>>,
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
/// If the server sends session data, it'll be copied to here.
session_data: Arc<Mutex<Option<ModelingSessionData>>>,
session_data: Arc<RwLock<Option<ModelingSessionData>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
}
pub struct TcpRead {
@ -230,12 +224,12 @@ impl EngineConnection {
let mut tcp_read = TcpRead { stream: tcp_read };
let session_data: Arc<Mutex<Option<ModelingSessionData>>> = Arc::new(Mutex::new(None));
let session_data: Arc<RwLock<Option<ModelingSessionData>>> = Arc::new(RwLock::new(None));
let session_data2 = session_data.clone();
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
let responses: Arc<RwLock<IndexMap<uuid::Uuid, WebSocketResponse>>> = Arc::new(RwLock::new(IndexMap::new()));
let responses_clone = responses.clone();
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
let pending_errors = Arc::new(Mutex::new(Vec::new()));
let socket_health = Arc::new(RwLock::new(SocketHealth::Active));
let pending_errors = Arc::new(RwLock::new(Vec::new()));
let pending_errors_clone = pending_errors.clone();
let socket_health_tcp_read = socket_health.clone();
@ -260,7 +254,7 @@ impl EngineConnection {
let id: uuid::Uuid = (*resp_id).into();
match batch_response {
BatchResponse::Success { response } => {
responses_clone.insert(
responses_clone.write().await.insert(
id,
WebSocketResponse::Success(SuccessWebSocketResponse {
success: true,
@ -272,7 +266,7 @@ impl EngineConnection {
);
}
BatchResponse::Failure { errors } => {
responses_clone.insert(
responses_clone.write().await.insert(
id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
@ -288,7 +282,7 @@ impl EngineConnection {
resp: OkWebSocketResponseData::ModelingSessionData { session },
..
}) => {
let mut sd = session_data2.lock().unwrap();
let mut sd = session_data2.write().await;
sd.replace(session.clone());
}
WebSocketResponse::Failure(FailureWebSocketResponse {
@ -297,7 +291,7 @@ impl EngineConnection {
errors,
}) => {
if let Some(id) = request_id {
responses_clone.insert(
responses_clone.write().await.insert(
*id,
WebSocketResponse::Failure(FailureWebSocketResponse {
success: false,
@ -307,19 +301,20 @@ impl EngineConnection {
);
} else {
// Add it to our pending errors.
let mut pe = pending_errors_clone.lock().unwrap();
let mut pe = pending_errors_clone.write().await;
for error in errors {
if !pe.contains(&error.message) {
pe.push(error.message.clone());
}
}
drop(pe);
}
}
_ => {}
}
if let Some(id) = id {
responses_clone.insert(id, ws_resp.clone());
responses_clone.write().await.insert(id, ws_resp.clone());
}
}
Err(e) => {
@ -327,7 +322,7 @@ impl EngineConnection {
WebSocketReadError::Read(e) => crate::logln!("could not read from WS: {:?}", e),
WebSocketReadError::Deser(e) => crate::logln!("could not deserialize msg from WS: {:?}", e),
}
*socket_health_tcp_read.lock().unwrap() = SocketHealth::Inactive;
*socket_health_tcp_read.write().await = SocketHealth::Inactive;
return Err(e);
}
}
@ -343,70 +338,41 @@ impl EngineConnection {
responses,
pending_errors,
socket_health,
batch: Arc::new(Mutex::new(Vec::new())),
batch_end: Arc::new(Mutex::new(IndexMap::new())),
artifact_commands: Arc::new(Mutex::new(Vec::new())),
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
default_planes: Default::default(),
session_data,
execution_kind: Default::default(),
})
}
fn handle_command(
&self,
cmd: &ModelingCmd,
cmd_id: ModelingCmdId,
id_to_source_range: &HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
let cmd_id = *cmd_id.as_ref();
let range = id_to_source_range
.get(&cmd_id)
.copied()
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
// Add artifact command.
let mut artifact_commands = self.artifact_commands.lock().unwrap();
artifact_commands.push(ArtifactCommand {
cmd_id,
range,
command: cmd.clone(),
});
Ok(())
}
}
#[async_trait::async_trait]
impl EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
self.responses
.iter()
.map(|entry| {
let (k, v) = entry.pair();
(*k, v.clone())
})
.collect()
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
self.responses.clone()
}
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
let mut artifact_commands = self.artifact_commands.lock().unwrap();
std::mem::take(&mut *artifact_commands)
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
self.artifact_commands.clone()
}
fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.lock().unwrap();
async fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.read().await;
*guard
}
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.lock().unwrap();
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.write().await;
let original = *guard;
*guard = execution_kind;
original
@ -447,49 +413,8 @@ impl EngineManager for EngineConnection {
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
_id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
match &cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
for request in requests {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
}
}
WebSocketRequest::ModelingCmdReq(request) => {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
}
_ => {}
}
// In isolated mode, we don't send the command to the engine.
if self.execution_kind().is_isolated() {
return match &cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
let mut responses = HashMap::with_capacity(requests.len());
for request in requests {
responses.insert(
request.cmd_id,
BatchResponse::Success {
response: OkModelingCmdResponse::Empty {},
},
);
}
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::ModelingBatch { responses },
success: true,
}))
}
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Empty {},
},
success: true,
})),
};
}
let (tx, rx) = oneshot::channel();
// Send the request to the engine, via the actor.
@ -524,25 +449,24 @@ impl EngineManager for EngineConnection {
// Wait for the response.
let current_time = std::time::Instant::now();
while current_time.elapsed().as_secs() < 60 {
if let Ok(guard) = self.socket_health.lock() {
if *guard == SocketHealth::Inactive {
// Check if we have any pending errors.
let pe = self.pending_errors.lock().unwrap();
if !pe.is_empty() {
return Err(KclError::Engine(KclErrorDetails {
message: pe.join(", ").to_string(),
source_ranges: vec![source_range],
}));
} else {
return Err(KclError::Engine(KclErrorDetails {
message: "Modeling command failed: websocket closed early".to_string(),
source_ranges: vec![source_range],
}));
}
let guard = self.socket_health.read().await;
if *guard == SocketHealth::Inactive {
// Check if we have any pending errors.
let pe = self.pending_errors.read().await;
if !pe.is_empty() {
return Err(KclError::Engine(KclErrorDetails {
message: pe.join(", ").to_string(),
source_ranges: vec![source_range],
}));
} else {
return Err(KclError::Engine(KclErrorDetails {
message: "Modeling command failed: websocket closed early".to_string(),
source_ranges: vec![source_range],
}));
}
}
// We pop off the responses to cleanup our mappings.
if let Some((_, resp)) = self.responses.remove(&id) {
if let Some(resp) = self.responses.write().await.shift_remove(&id) {
return Ok(resp);
}
}
@ -553,17 +477,16 @@ impl EngineManager for EngineConnection {
}))
}
fn get_session_data(&self) -> Option<ModelingSessionData> {
self.session_data.lock().unwrap().clone()
async fn get_session_data(&self) -> Option<ModelingSessionData> {
self.session_data.read().await.clone()
}
async fn close(&self) {
let _ = self.shutdown_tx.send(()).await;
loop {
if let Ok(guard) = self.socket_health.lock() {
if *guard == SocketHealth::Inactive {
return;
}
let guard = self.socket_health.read().await;
if *guard == SocketHealth::Inactive {
return;
}
}
}

View File

@ -1,10 +1,7 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use indexmap::IndexMap;
@ -15,7 +12,8 @@ use kcmc::{
WebSocketResponse,
},
};
use kittycad_modeling_cmds::{self as kcmc, id::ModelingCmdId, ModelingCmd};
use kittycad_modeling_cmds::{self as kcmc};
use tokio::sync::RwLock;
use uuid::Uuid;
use super::ExecutionKind;
@ -28,71 +26,48 @@ use crate::{
#[derive(Debug, Clone)]
pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
}
impl EngineConnection {
pub async fn new() -> Result<EngineConnection> {
Ok(EngineConnection {
batch: Arc::new(Mutex::new(Vec::new())),
batch_end: Arc::new(Mutex::new(IndexMap::new())),
artifact_commands: Arc::new(Mutex::new(Vec::new())),
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(),
})
}
fn handle_command(
&self,
cmd: &ModelingCmd,
cmd_id: ModelingCmdId,
id_to_source_range: &HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
let cmd_id = *cmd_id.as_ref();
let range = id_to_source_range
.get(&cmd_id)
.copied()
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
// Add artifact command.
let mut artifact_commands = self.artifact_commands.lock().unwrap();
artifact_commands.push(ArtifactCommand {
cmd_id,
range,
command: cmd.clone(),
});
Ok(())
}
}
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
IndexMap::new()
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
Arc::new(RwLock::new(IndexMap::new()))
}
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
let mut artifact_commands = self.artifact_commands.lock().unwrap();
std::mem::take(&mut *artifact_commands)
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
self.artifact_commands.clone()
}
fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.lock().unwrap();
async fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.read().await;
*guard
}
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.lock().unwrap();
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.write().await;
let original = *guard;
*guard = execution_kind;
original
@ -119,7 +94,7 @@ impl crate::engine::EngineManager for EngineConnection {
id: uuid::Uuid,
_source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<Uuid, SourceRange>,
_id_to_source_range: HashMap<Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
match cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {
@ -130,7 +105,6 @@ impl crate::engine::EngineManager for EngineConnection {
// Create the empty responses.
let mut responses = HashMap::with_capacity(requests.len());
for request in requests {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
responses.insert(
request.cmd_id,
BatchResponse::Success {
@ -144,17 +118,13 @@ impl crate::engine::EngineManager for EngineConnection {
success: true,
}))
}
WebSocketRequest::ModelingCmdReq(request) => {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Empty {},
},
success: true,
}))
}
WebSocketRequest::ModelingCmdReq(_) => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Empty {},
},
success: true,
})),
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {

View File

@ -1,22 +1,12 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use indexmap::IndexMap;
use kcmc::{
id::ModelingCmdId,
ok_response::OkModelingCmdResponse,
websocket::{
BatchResponse, ModelingBatch, OkWebSocketResponseData, SuccessWebSocketResponse, WebSocketRequest,
WebSocketResponse,
},
ModelingCmd,
};
use kcmc::websocket::{WebSocketRequest, WebSocketResponse};
use kittycad_modeling_cmds as kcmc;
use tokio::sync::RwLock;
use uuid::Uuid;
use wasm_bindgen::prelude::*;
@ -54,11 +44,11 @@ extern "C" {
#[derive(Debug, Clone)]
pub struct EngineConnection {
manager: Arc<EngineCommandManager>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
responses: Arc<Mutex<IndexMap<Uuid, WebSocketResponse>>>,
artifact_commands: Arc<Mutex<Vec<ArtifactCommand>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
batch: Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
responses: Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>,
artifact_commands: Arc<RwLock<Vec<ArtifactCommand>>>,
execution_kind: Arc<RwLock<ExecutionKind>>,
}
// Safety: WebAssembly will only ever run in a single-threaded context.
@ -70,66 +60,101 @@ impl EngineConnection {
#[allow(clippy::arc_with_non_send_sync)]
Ok(EngineConnection {
manager: Arc::new(manager),
batch: Arc::new(Mutex::new(Vec::new())),
batch_end: Arc::new(Mutex::new(IndexMap::new())),
responses: Arc::new(Mutex::new(IndexMap::new())),
artifact_commands: Arc::new(Mutex::new(Vec::new())),
batch: Arc::new(RwLock::new(Vec::new())),
batch_end: Arc::new(RwLock::new(IndexMap::new())),
responses: Arc::new(RwLock::new(IndexMap::new())),
artifact_commands: Arc::new(RwLock::new(Vec::new())),
execution_kind: Default::default(),
})
}
}
impl EngineConnection {
fn handle_command(
async fn do_send_modeling_cmd(
&self,
cmd: &ModelingCmd,
cmd_id: ModelingCmdId,
id_to_source_range: &HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
let cmd_id = *cmd_id.as_ref();
let range = id_to_source_range
.get(&cmd_id)
.copied()
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
id: uuid::Uuid,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize id to source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
// Add artifact command.
let mut artifact_commands = self.artifact_commands.lock().unwrap();
artifact_commands.push(ArtifactCommand {
cmd_id,
range,
command: cmd.clone(),
});
Ok(())
let promise = self
.manager
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
// Parse the value as a string.
let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get string from response from engine: `{:?}`", value),
source_ranges: vec![source_range],
})
})?;
let ws_result: WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize response from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
Ok(ws_result)
}
}
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
let responses = self.responses.lock().unwrap();
responses.clone()
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>> {
self.responses.clone()
}
fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
let mut artifact_commands = self.artifact_commands.lock().unwrap();
std::mem::take(&mut *artifact_commands)
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
self.artifact_commands.clone()
}
fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.lock().unwrap();
async fn execution_kind(&self) -> ExecutionKind {
let guard = self.execution_kind.read().await;
*guard
}
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.lock().unwrap();
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
let mut guard = self.execution_kind.write().await;
let original = *guard;
*guard = execution_kind;
original
@ -214,100 +239,18 @@ impl crate::engine::EngineManager for EngineConnection {
cmd: WebSocketRequest,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
match &cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
for request in requests {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
}
}
WebSocketRequest::ModelingCmdReq(request) => {
self.handle_command(&request.cmd, request.cmd_id, &id_to_source_range)?;
}
_ => {}
let ws_result = self
.do_send_modeling_cmd(id, source_range, cmd, id_to_source_range)
.await?;
// In isolated mode, we don't save the response.
if self.execution_kind().await.is_isolated() {
return Ok(ws_result);
}
// In isolated mode, we don't send the command to the engine.
if self.execution_kind().is_isolated() {
return match &cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
let mut responses = HashMap::with_capacity(requests.len());
for request in requests {
responses.insert(
request.cmd_id,
BatchResponse::Success {
response: OkModelingCmdResponse::Empty {},
},
);
}
Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::ModelingBatch { responses },
success: true,
}))
}
_ => Ok(WebSocketResponse::Success(SuccessWebSocketResponse {
request_id: Some(id),
resp: OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Empty {},
},
success: true,
})),
};
}
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize id to source range: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
// Parse the value as a string.
let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get string from response from engine: `{:?}`", value),
source_ranges: vec![source_range],
})
})?;
let ws_result: WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize response from engine: {:?}", e),
source_ranges: vec![source_range],
})
})?;
let mut responses = self.responses.lock().unwrap();
let mut responses = self.responses.write().await;
responses.insert(id, ws_result.clone());
drop(responses);
Ok(ws_result)
}

View File

@ -8,14 +8,12 @@ pub mod conn_mock;
#[cfg(feature = "engine")]
pub mod conn_wasm;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use indexmap::IndexMap;
use kcmc::{
each_cmd as mcmd,
id::ModelingCmdId,
length_unit::LengthUnit,
ok_response::OkModelingCmdResponse,
shared::Color,
@ -28,6 +26,7 @@ use kcmc::{
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::{
@ -62,28 +61,38 @@ impl ExecutionKind {
#[async_trait::async_trait]
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the batch of commands to be sent to the engine.
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>;
fn batch(&self) -> Arc<RwLock<Vec<(WebSocketRequest, SourceRange)>>>;
/// Get the batch of end commands to be sent to the engine.
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
fn batch_end(&self) -> Arc<RwLock<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
/// Get the command responses from the engine.
fn responses(&self) -> IndexMap<Uuid, WebSocketResponse>;
fn responses(&self) -> Arc<RwLock<IndexMap<Uuid, WebSocketResponse>>>;
/// Take the artifact commands generated up to this point and clear them.
fn take_artifact_commands(&self) -> Vec<ArtifactCommand>;
/// Get the artifact commands that have accumulated so far.
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>>;
/// Clear all artifact commands that have accumulated so far.
fn clear_artifact_commands(&self) {
self.take_artifact_commands();
async fn clear_artifact_commands(&self) {
self.artifact_commands().write().await.clear();
}
/// Take the artifact commands that have accumulated so far and clear them.
async fn take_artifact_commands(&self) -> Vec<ArtifactCommand> {
std::mem::take(&mut *self.artifact_commands().write().await)
}
/// Take the responses that have accumulated so far and clear them.
async fn take_responses(&self) -> IndexMap<Uuid, WebSocketResponse> {
std::mem::take(&mut *self.responses().write().await)
}
/// Get the current execution kind.
fn execution_kind(&self) -> ExecutionKind;
async fn execution_kind(&self) -> ExecutionKind;
/// Replace the current execution kind with a new value and return the
/// existing value.
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
async fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
/// Get the default planes.
async fn default_planes(
@ -127,7 +136,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// Ensure artifact commands are cleared so that we don't accumulate them
// across runs.
self.clear_artifact_commands();
self.clear_artifact_commands().await;
// Do the after clear scene hook.
self.clear_scene_post_hook(id_generator, source_range).await?;
@ -151,6 +160,27 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(())
}
async fn handle_artifact_command(
&self,
cmd: &ModelingCmd,
cmd_id: ModelingCmdId,
id_to_source_range: &HashMap<Uuid, SourceRange>,
) -> Result<(), KclError> {
let cmd_id = *cmd_id.as_ref();
let range = id_to_source_range
.get(&cmd_id)
.copied()
.ok_or_else(|| KclError::internal(format!("Failed to get source range for command ID: {:?}", cmd_id)))?;
// Add artifact command.
self.artifact_commands().write().await.push(ArtifactCommand {
cmd_id,
range,
command: cmd.clone(),
});
Ok(())
}
async fn set_units(
&self,
units: crate::UnitLength,
@ -197,13 +227,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
// In isolated mode, we don't send the command to the engine.
if self.execution_kind().await.is_isolated() {
return Ok(());
}
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id.into(),
});
// Add cmd to the batch.
self.batch().lock().unwrap().push((req, source_range));
self.batch().write().await.push((req, source_range));
Ok(())
}
@ -217,13 +252,18 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
// In isolated mode, we don't send the command to the engine.
if self.execution_kind().await.is_isolated() {
return Ok(());
}
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
cmd: cmd.clone(),
cmd_id: id.into(),
});
// Add cmd to the batch end.
self.batch_end().lock().unwrap().insert(id, (req, source_range));
self.batch_end().write().await.insert(id, (req, source_range));
Ok(())
}
@ -249,11 +289,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
source_range: SourceRange,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
let all_requests = if batch_end {
let mut requests = self.batch().lock().unwrap().clone();
requests.extend(self.batch_end().lock().unwrap().values().cloned());
let mut requests = self.batch().read().await.clone();
requests.extend(self.batch_end().read().await.values().cloned());
requests
} else {
self.batch().lock().unwrap().clone()
self.batch().read().await.clone()
};
// Return early if we have no commands to send.
@ -304,10 +344,27 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
}
}
// Do the artifact commands.
for (req, _) in all_requests.iter() {
match &req {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { requests, .. }) => {
for request in requests {
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
.await?;
}
}
WebSocketRequest::ModelingCmdReq(request) => {
self.handle_artifact_command(&request.cmd, request.cmd_id, &id_to_source_range)
.await?;
}
_ => {}
}
}
// Throw away the old batch queue.
self.batch().lock().unwrap().clear();
self.batch().write().await.clear();
if batch_end {
self.batch_end().lock().unwrap().clear();
self.batch_end().write().await.clear();
}
// We pop off the responses to cleanup our mappings.
@ -596,7 +653,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get session data, if it has been received.
/// Returns None if the server never sent it.
fn get_session_data(&self) -> Option<ModelingSessionData> {
async fn get_session_data(&self) -> Option<ModelingSessionData> {
None
}

View File

@ -48,7 +48,7 @@ impl ExecutorContext {
let old_units = exec_state.length_unit();
exec_state.mod_local.settings.update_from_annotation(annotation)?;
let new_units = exec_state.length_unit();
if !self.engine.execution_kind().is_isolated() && old_units != new_units {
if !self.engine.execution_kind().await.is_isolated() && old_units != new_units {
self.engine
.set_units(new_units.into(), annotation.as_source_range())
.await?;
@ -393,7 +393,7 @@ impl ExecutorContext {
exec_state.global.mod_loader.enter_module(path);
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
exec_state.mut_memory().push_new_root_env();
let original_execution = self.engine.replace_execution_kind(exec_kind);
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
let result = self
.exec_program(program, exec_state, crate::execution::BodyType::Root)
@ -406,7 +406,7 @@ impl ExecutorContext {
if !exec_kind.is_isolated() && new_units != old_units {
self.engine.set_units(old_units.into(), Default::default()).await?;
}
self.engine.replace_execution_kind(original_execution);
self.engine.replace_execution_kind(original_execution).await;
result
.map_err(|err| {
@ -1720,13 +1720,12 @@ impl JsonSchema for FunctionParam<'_> {
#[cfg(test)]
mod test {
use super::*;
use crate::{
execution::{memory::ProgramMemory, parse_execute},
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
};
use super::*;
#[test]
fn test_assign_args_to_params() {
// Set up a little framework for this test.

View File

@ -289,7 +289,7 @@ pub struct PreImportedGeometry {
}
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
if ctxt.no_engine_commands() {
if ctxt.no_engine_commands().await {
return Ok(ImportedGeometry {
id: pre.id,
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),

View File

@ -484,8 +484,8 @@ impl ExecutorContext {
}
/// Returns true if we should not send engine commands for any reason.
pub fn no_engine_commands(&self) -> bool {
self.is_mock() || self.engine.execution_kind().is_isolated()
pub async fn no_engine_commands(&self) -> bool {
self.is_mock() || self.engine.execution_kind().await.is_isolated()
}
pub async fn send_clear_scene(
@ -713,7 +713,7 @@ impl ExecutorContext {
"Post interpretation KCL memory stats: {:#?}",
exec_state.memory().stats
));
let session_data = self.engine.get_session_data();
let session_data = self.engine.get_session_data().await;
Ok(session_data)
}
@ -734,8 +734,11 @@ impl ExecutorContext {
exec_state
.global
.artifact_commands
.extend(self.engine.take_artifact_commands());
exec_state.global.artifact_responses.extend(self.engine.responses());
.extend(self.engine.take_artifact_commands().await);
exec_state
.global
.artifact_responses
.extend(self.engine.take_responses().await);
// Build the artifact graph.
match build_artifact_graph(
&exec_state.global.artifact_commands,

View File

@ -949,6 +949,28 @@ mod import_foreign {
super::execute(TEST_NAME, false).await
}
}
mod assembly_non_default_units {
const TEST_NAME: &str = "assembly_non_default_units";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod array_elem_push_fail {
const TEST_NAME: &str = "array_elem_push_fail";

View File

@ -277,12 +277,12 @@ impl Args {
// before what ever we call next.
for id in ids {
// Pop it off the batch_end and add it to the batch.
let Some(item) = self.ctx.engine.batch_end().lock().unwrap().shift_remove(&id) else {
let Some(item) = self.ctx.engine.batch_end().write().await.shift_remove(&id) else {
// It might be in the batch already.
continue;
};
// Add it to the batch.
self.ctx.engine.batch().lock().unwrap().push(item);
self.ctx.engine.batch().write().await.push(item);
}
// Run flush.

View File

@ -232,8 +232,9 @@ pub(crate) async fn do_post_extrude(
sides: face_id_map,
start_cap_id,
end_cap_id,
} = analyze_faces(exec_state, &args, face_infos);
} = analyze_faces(exec_state, &args, face_infos).await;
// Iterate over the sketch.value array and add face_id to GeoMeta
let no_engine_commands = args.ctx.no_engine_commands().await;
let new_value = sketch
.paths
.iter()
@ -267,7 +268,7 @@ pub(crate) async fn do_post_extrude(
Some(extrude_surface)
}
}
} else if args.ctx.no_engine_commands() {
} else if no_engine_commands {
// Only pre-populate the extrude surface if we are in mock mode.
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
@ -313,12 +314,12 @@ struct Faces {
start_cap_id: Option<Uuid>,
}
fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
async fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
let mut faces = Faces {
sides: HashMap::with_capacity(face_infos.len()),
..Default::default()
};
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
faces.start_cap_id = Some(exec_state.next_uuid());
faces.end_cap_id = Some(exec_state.next_uuid());

View File

@ -222,7 +222,7 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
name = "getOppositeEdge",
}]
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid());
}
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
@ -299,7 +299,7 @@ async fn inner_get_next_adjacent_edge(
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid());
}
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
@ -384,7 +384,7 @@ async fn inner_get_previous_adjacent_edge(
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid());
}
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;

View File

@ -125,7 +125,7 @@ async fn inner_helix(
meta: vec![args.source_range.into()],
});
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
return Ok(helix_result);
}

View File

@ -112,7 +112,7 @@ async fn inner_mirror_2d(
SketchSet::Sketches(sketches) => sketches,
};
if args.ctx.no_engine_commands() {
if args.ctx.no_engine_commands().await {
return Ok(starting_sketches);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -0,0 +1,556 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact commands assembly_non_default_units.kcl
---
[
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.7,
"g": 0.28,
"b": 0.28,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.28,
"g": 0.7,
"b": 0.28,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.28,
"g": 0.28,
"b": 0.7,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": -1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 0.0,
"y": -1.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": -1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
33,
0
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
},
{
"cmdId": "[uuid]",
"range": [
0,
33,
1
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
},
{
"cmdId": "[uuid]",
"range": [
172,
191,
1
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 60.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
197,
239,
1
],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": -1.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
197,
239,
1
],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [
197,
239,
1
],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 1.0,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
197,
239,
1
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "arc",
"center": {
"x": 0.0,
"y": 0.0
},
"radius": 1.0,
"start": {
"unit": "degrees",
"value": 0.0
},
"end": {
"unit": "degrees",
"value": 360.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
197,
239,
1
],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
0,
33,
2
],
"command": {
"type": "set_scene_units",
"unit": "in"
}
},
{
"cmdId": "[uuid]",
"range": [
89,
108,
2
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 60.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
114,
156,
2
],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": -1.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
114,
156,
2
],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [
114,
156,
2
],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 1.0,
"y": 2.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
114,
156,
2
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "arc",
"center": {
"x": 0.0,
"y": 2.0
},
"radius": 1.0,
"start": {
"unit": "degrees",
"value": 0.0
},
"end": {
"unit": "degrees",
"value": 360.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
114,
156,
2
],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart assembly_non_default_units.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,21 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[197, 239, 1]"]
3["Segment<br>[197, 239, 1]"]
4[Solid2d]
end
subgraph path6 [Path]
6["Path<br>[114, 156, 2]"]
7["Segment<br>[114, 156, 2]"]
8[Solid2d]
end
1["Plane<br>[172, 191, 1]"]
5["Plane<br>[89, 108, 2]"]
1 --- 2
2 --- 3
2 --- 4
5 --- 6
6 --- 7
6 --- 8
```

View File

@ -0,0 +1,143 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing assembly_non_default_units.kcl
---
{
"Ok": {
"body": [
{
"end": 172,
"path": {
"type": "Kcl",
"filename": "other1.kcl"
},
"selector": {
"type": "None",
"alias": null
},
"start": 153,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"end": 192,
"path": {
"type": "Kcl",
"filename": "other2.kcl"
},
"selector": {
"type": "None",
"alias": null
},
"start": 173,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"end": 200,
"expression": {
"end": 200,
"name": "other1",
"start": 194,
"type": "Identifier",
"type": "Identifier"
},
"start": 194,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"end": 207,
"expression": {
"end": 207,
"name": "other2",
"start": 201,
"type": "Identifier",
"type": "Identifier"
},
"start": 201,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 208,
"innerAttrs": [
{
"end": 33,
"name": {
"end": 9,
"name": "settings",
"start": 1,
"type": "Identifier"
},
"properties": [
{
"end": 32,
"key": {
"end": 27,
"name": "defaultLengthUnit",
"start": 10,
"type": "Identifier"
},
"start": 10,
"type": "ObjectProperty",
"value": {
"end": 32,
"name": "in",
"start": 30,
"type": "Identifier",
"type": "Identifier"
}
}
],
"start": 0,
"type": "Annotation"
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"1": [
{
"end": 194,
"start": 192,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": [
{
"end": 36,
"start": 33,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
},
{
"end": 87,
"start": 36,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "Use a default unit that isn't the default of mm.",
"style": "line"
}
},
{
"end": 152,
"start": 88,
"type": "NonCodeNode",
"value": {
"type": "blockComment",
"value": "This should look like two circles barely touching each other.",
"style": "line"
}
}
]
},
"start": 0
}
}

View File

@ -0,0 +1,3 @@
@settings(defaultLengthUnit = in)
export radius = 1

View File

@ -0,0 +1,10 @@
@settings(defaultLengthUnit = in)
// Use a default unit that isn't the default of mm.
// This should look like two circles barely touching each other.
import "other1.kcl"
import "other2.kcl"
other1
other2

View File

@ -0,0 +1,5 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed assembly_non_default_units.kcl
---
[]

View File

@ -0,0 +1,8 @@
@settings(defaultLengthUnit = in)
// This is not used, but it triggers the problem.
import radius from "globals.kcl"
// Use the same units as in the main importing file.
startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)

View File

@ -0,0 +1,6 @@
@settings(defaultLengthUnit = in)
// Use the same units as in the main importing file.
startSketchOn('XZ')
|> circle({ center = [0, 2], radius = 1 }, %)

View File

@ -0,0 +1,64 @@
---
source: kcl/src/simulation_tests.rs
description: Variables in memory after executing assembly_non_default_units.kcl
---
{
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"ty": {
"type": "Unknown"
},
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"ty": {
"type": "Unknown"
},
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"ty": {
"type": "Unknown"
},
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"ty": {
"type": "Unknown"
},
"__meta": []
},
"other1": {
"type": "Module",
"value": 1,
"__meta": [
{
"sourceRange": [
153,
172,
0
]
}
]
},
"other2": {
"type": "Module",
"value": 2,
"__meta": [
{
"sourceRange": [
173,
192,
0
]
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Some files were not shown because too many files have changed in this diff Show More