Compare commits
4 Commits
wip-multi-
...
v0.22.6
Author | SHA1 | Date | |
---|---|---|---|
cd33b40c37 | |||
3300772e3d | |||
fa37752a41 | |||
34c5c153c8 |
@ -17,4 +17,11 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
currently move or transform the imported objects at all, once we have assemblies
|
currently move or transform the imported objects at all, once we have assemblies
|
||||||
this will work.
|
this will work.
|
||||||
|
|
||||||
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet cases work currently.
|
- **Fillets**: Fillets cannot intersect, you will get an error. Only simple fillet
|
||||||
|
cases work currently.
|
||||||
|
|
||||||
|
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||||
|
chamfer cases work currently.
|
||||||
|
|
||||||
|
- **Shell**: Shell is only working for `end` faces, not for `side` or `start`
|
||||||
|
faces. We are tracking the engine side bug on this.
|
||||||
|
@ -566,6 +566,56 @@ test('if you click the format button it formats your code', async ({
|
|||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hover over functions shows function description', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||||
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
|
if (message.text().includes('start kcl lsp')) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await lspStartPromise
|
||||||
|
|
||||||
|
// check no error to begin with
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// focus the editor
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
// Hover over the startSketchOn function
|
||||||
|
await page.getByText('startSketchOn').hover()
|
||||||
|
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByText('Start a sketch on a specific plane or face')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
// Hover over the line function
|
||||||
|
await page.getByText('line').first().hover()
|
||||||
|
await expect(page.locator('.hover-tooltip')).toBeVisible()
|
||||||
|
await expect(page.getByText('Draw a line')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
test('if you use the format keyboard binding it formats your code', async ({
|
test('if you use the format keyboard binding it formats your code', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.22.5",
|
"version": "0.22.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
|
@ -74,5 +74,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.22.5"
|
"version": "0.22.6"
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
if (pos === null) return null
|
if (pos === null) return null
|
||||||
const dom = document.createElement('div')
|
const dom = document.createElement('div')
|
||||||
dom.classList.add('documentation')
|
dom.classList.add('documentation')
|
||||||
|
dom.classList.add('hover-tooltip')
|
||||||
dom.style.zIndex = '99999999'
|
dom.style.zIndex = '99999999'
|
||||||
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents)
|
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents)
|
||||||
else dom.textContent = formatContents(contents)
|
else dom.textContent = formatContents(contents)
|
||||||
|
2
src/wasm-lib/Cargo.lock
generated
2
src/wasm-lib/Cargo.lock
generated
@ -1375,7 +1375,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.62"
|
version = "0.1.64"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.1.62"
|
version = "0.1.64"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -31,6 +31,7 @@ pub struct EngineConnection {
|
|||||||
tcp_read_handle: Arc<TcpReadHandle>,
|
tcp_read_handle: Arc<TcpReadHandle>,
|
||||||
socket_health: Arc<Mutex<SocketHealth>>,
|
socket_health: Arc<Mutex<SocketHealth>>,
|
||||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
|
batch_end: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
|
|
||||||
/// The default planes for the scene.
|
/// The default planes for the scene.
|
||||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||||
@ -236,6 +237,7 @@ impl EngineConnection {
|
|||||||
responses,
|
responses,
|
||||||
socket_health,
|
socket_health,
|
||||||
batch: Arc::new(Mutex::new(Vec::new())),
|
batch: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
batch_end: Arc::new(Mutex::new(Vec::new())),
|
||||||
default_planes: Default::default(),
|
default_planes: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -247,6 +249,10 @@ impl EngineManager for EngineConnection {
|
|||||||
self.batch.clone()
|
self.batch.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch_end(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
|
||||||
|
self.batch_end.clone()
|
||||||
|
}
|
||||||
|
|
||||||
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
||||||
{
|
{
|
||||||
let opt = self.default_planes.read().await.as_ref().cloned();
|
let opt = self.default_planes.read().await.as_ref().cloned();
|
||||||
|
@ -14,12 +14,14 @@ use crate::{errors::KclError, executor::DefaultPlanes};
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
|
batch_end: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EngineConnection {
|
impl EngineConnection {
|
||||||
pub async fn new() -> Result<EngineConnection> {
|
pub async fn new() -> Result<EngineConnection> {
|
||||||
Ok(EngineConnection {
|
Ok(EngineConnection {
|
||||||
batch: Arc::new(Mutex::new(Vec::new())),
|
batch: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
batch_end: Arc::new(Mutex::new(Vec::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,6 +32,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
|||||||
self.batch.clone()
|
self.batch.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch_end(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
|
||||||
|
self.batch_end.clone()
|
||||||
|
}
|
||||||
|
|
||||||
async fn default_planes(&self, _source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
async fn default_planes(&self, _source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
||||||
Ok(DefaultPlanes::default())
|
Ok(DefaultPlanes::default())
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ extern "C" {
|
|||||||
pub struct EngineConnection {
|
pub struct EngineConnection {
|
||||||
manager: Arc<EngineCommandManager>,
|
manager: Arc<EngineCommandManager>,
|
||||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
|
batch_end: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: WebAssembly will only ever run in a single-threaded context.
|
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||||
@ -50,6 +51,7 @@ impl EngineConnection {
|
|||||||
Ok(EngineConnection {
|
Ok(EngineConnection {
|
||||||
manager: Arc::new(manager),
|
manager: Arc::new(manager),
|
||||||
batch: Arc::new(Mutex::new(Vec::new())),
|
batch: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
batch_end: Arc::new(Mutex::new(Vec::new())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +62,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
|||||||
self.batch.clone()
|
self.batch.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn batch_end(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
|
||||||
|
self.batch_end.clone()
|
||||||
|
}
|
||||||
|
|
||||||
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
||||||
// Get the default planes.
|
// Get the default planes.
|
||||||
let promise = self.manager.get_default_planes().map_err(|e| {
|
let promise = self.manager.get_default_planes().map_err(|e| {
|
||||||
|
@ -13,7 +13,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use kittycad::types::{Color, ModelingCmd, OkWebSocketResponseData, WebSocketRequest};
|
use kittycad::types::{Color, ModelingCmd, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -27,6 +27,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
/// Get the batch of commands to be sent to the engine.
|
/// Get the batch of commands to be sent to the engine.
|
||||||
fn batch(&self) -> Arc<Mutex<Vec<(kittycad::types::WebSocketRequest, crate::executor::SourceRange)>>>;
|
fn batch(&self) -> Arc<Mutex<Vec<(kittycad::types::WebSocketRequest, crate::executor::SourceRange)>>>;
|
||||||
|
|
||||||
|
/// Get the batch of end commands to be sent to the engine.
|
||||||
|
fn batch_end(&self) -> Arc<Mutex<Vec<(kittycad::types::WebSocketRequest, crate::executor::SourceRange)>>>;
|
||||||
|
|
||||||
/// Get the default planes.
|
/// Get the default planes.
|
||||||
async fn default_planes(
|
async fn default_planes(
|
||||||
&self,
|
&self,
|
||||||
@ -59,7 +62,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
|
|
||||||
// Flush the batch queue, so clear is run right away.
|
// Flush the batch queue, so clear is run right away.
|
||||||
// Otherwise the hooks below won't work.
|
// Otherwise the hooks below won't work.
|
||||||
self.flush_batch(source_range).await?;
|
self.flush_batch(false, source_range).await?;
|
||||||
|
|
||||||
// Do the after clear scene hook.
|
// Do the after clear scene hook.
|
||||||
self.clear_scene_post_hook(source_range).await?;
|
self.clear_scene_post_hook(source_range).await?;
|
||||||
@ -85,6 +88,25 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a command to the batch that needs to be executed at the very end.
|
||||||
|
/// This for stuff like fillets or chamfers where if we execute too soon the
|
||||||
|
/// engine will eat the ID and we can't reference it for other commands.
|
||||||
|
async fn batch_end_cmd(
|
||||||
|
&self,
|
||||||
|
id: uuid::Uuid,
|
||||||
|
source_range: crate::executor::SourceRange,
|
||||||
|
cmd: &kittycad::types::ModelingCmd,
|
||||||
|
) -> Result<(), crate::errors::KclError> {
|
||||||
|
let req = WebSocketRequest::ModelingCmdReq {
|
||||||
|
cmd: cmd.clone(),
|
||||||
|
cmd_id: id,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add cmd to the batch end.
|
||||||
|
self.batch_end().lock().unwrap().push((req, source_range));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Send the modeling cmd and wait for the response.
|
/// Send the modeling cmd and wait for the response.
|
||||||
async fn send_modeling_cmd(
|
async fn send_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
@ -95,25 +117,33 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
self.batch_modeling_cmd(id, source_range, &cmd).await?;
|
self.batch_modeling_cmd(id, source_range, &cmd).await?;
|
||||||
|
|
||||||
// Flush the batch queue.
|
// Flush the batch queue.
|
||||||
self.flush_batch(source_range).await
|
self.flush_batch(false, source_range).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force flush the batch queue.
|
/// Force flush the batch queue.
|
||||||
async fn flush_batch(
|
async fn flush_batch(
|
||||||
&self,
|
&self,
|
||||||
|
// Whether or not to flush the end commands as well.
|
||||||
|
// We only do this at the very end of the file.
|
||||||
|
batch_end: bool,
|
||||||
source_range: crate::executor::SourceRange,
|
source_range: crate::executor::SourceRange,
|
||||||
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError> {
|
) -> Result<kittycad::types::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().clone());
|
||||||
|
requests
|
||||||
|
} else {
|
||||||
|
self.batch().lock().unwrap().clone()
|
||||||
|
};
|
||||||
|
|
||||||
// Return early if we have no commands to send.
|
// Return early if we have no commands to send.
|
||||||
if self.batch().lock().unwrap().is_empty() {
|
if all_requests.is_empty() {
|
||||||
return Ok(OkWebSocketResponseData::Modeling {
|
return Ok(OkWebSocketResponseData::Modeling {
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
|
modeling_response: kittycad::types::OkModelingCmdResponse::Empty {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let requests = self
|
let requests: Vec<ModelingCmdReq> = all_requests
|
||||||
.batch()
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(val, _)| match val {
|
.filter_map(|(val, _)| match val {
|
||||||
WebSocketRequest::ModelingCmdReq { cmd, cmd_id } => Some(kittycad::types::ModelingCmdReq {
|
WebSocketRequest::ModelingCmdReq { cmd, cmd_id } => Some(kittycad::types::ModelingCmdReq {
|
||||||
@ -123,15 +153,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
|
let batched_requests = WebSocketRequest::ModelingCmdBatchReq {
|
||||||
requests,
|
requests,
|
||||||
batch_id: uuid::Uuid::new_v4(),
|
batch_id: uuid::Uuid::new_v4(),
|
||||||
responses: true,
|
responses: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let final_req = if self.batch().lock().unwrap().len() == 1 {
|
let final_req = if all_requests.len() == 1 {
|
||||||
// We can unwrap here because we know the batch has only one element.
|
// We can unwrap here because we know the batch has only one element.
|
||||||
self.batch().lock().unwrap().first().unwrap().0.clone()
|
all_requests.first().unwrap().0.clone()
|
||||||
} else {
|
} else {
|
||||||
batched_requests
|
batched_requests
|
||||||
};
|
};
|
||||||
@ -139,7 +170,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
// Create the map of original command IDs to source range.
|
// Create the map of original command IDs to source range.
|
||||||
// This is for the wasm side, kurt needs it for selections.
|
// This is for the wasm side, kurt needs it for selections.
|
||||||
let mut id_to_source_range = std::collections::HashMap::new();
|
let mut id_to_source_range = std::collections::HashMap::new();
|
||||||
for (req, range) in self.batch().lock().unwrap().iter() {
|
for (req, range) in all_requests.iter() {
|
||||||
match req {
|
match req {
|
||||||
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
|
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
|
||||||
id_to_source_range.insert(*cmd_id, *range);
|
id_to_source_range.insert(*cmd_id, *range);
|
||||||
@ -155,6 +186,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
|
|
||||||
// Throw away the old batch queue.
|
// Throw away the old batch queue.
|
||||||
self.batch().lock().unwrap().clear();
|
self.batch().lock().unwrap().clear();
|
||||||
|
if batch_end {
|
||||||
|
self.batch_end().lock().unwrap().clear();
|
||||||
|
}
|
||||||
|
|
||||||
// We pop off the responses to cleanup our mappings.
|
// We pop off the responses to cleanup our mappings.
|
||||||
match final_req {
|
match final_req {
|
||||||
@ -182,6 +216,19 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
|
WebSocketRequest::ModelingCmdReq { cmd: _, cmd_id } => {
|
||||||
|
// You are probably wondering why we can't just return the source range we were
|
||||||
|
// passed with the function. Well this is actually really important.
|
||||||
|
// If this is the last command in the batch and there is only one and we've reached
|
||||||
|
// the end of the file, this will trigger a flush batch function, but it will just
|
||||||
|
// send default or the end of the file as it's source range not the origin of the
|
||||||
|
// request so we need the original request source range in case the engine returns
|
||||||
|
// an error.
|
||||||
|
let source_range = id_to_source_range.get(&cmd_id).cloned().ok_or_else(|| {
|
||||||
|
KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
|
||||||
|
source_ranges: vec![],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
let ws_resp = self
|
let ws_resp = self
|
||||||
.inner_send_modeling_cmd(cmd_id, source_range, final_req, id_to_source_range)
|
.inner_send_modeling_cmd(cmd_id, source_range, final_req, id_to_source_range)
|
||||||
.await?;
|
.await?;
|
||||||
@ -321,7 +368,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush the batch queue, so these planes are created right away.
|
// Flush the batch queue, so these planes are created right away.
|
||||||
self.flush_batch(source_range).await?;
|
self.flush_batch(false, source_range).await?;
|
||||||
|
|
||||||
Ok(DefaultPlanes {
|
Ok(DefaultPlanes {
|
||||||
xy: planes[&PlaneName::Xy],
|
xy: planes[&PlaneName::Xy],
|
||||||
|
@ -1248,7 +1248,14 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
if BodyType::Root == body_type {
|
if BodyType::Root == body_type {
|
||||||
// Flush the batch queue.
|
// Flush the batch queue.
|
||||||
self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
|
self.engine
|
||||||
|
.flush_batch(
|
||||||
|
// True here tells the engine to flush all the end commands as well like fillets
|
||||||
|
// and chamfers where the engine would otherwise eat the ID of the segments.
|
||||||
|
true,
|
||||||
|
SourceRange([program.end, program.end]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(memory.clone())
|
Ok(memory.clone())
|
||||||
|
@ -110,7 +110,7 @@ async fn inner_chamfer(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
args.batch_modeling_cmd(
|
args.batch_end_cmd(
|
||||||
uuid::Uuid::new_v4(),
|
uuid::Uuid::new_v4(),
|
||||||
ModelingCmd::Solid3DFilletEdge {
|
ModelingCmd::Solid3DFilletEdge {
|
||||||
edge_id,
|
edge_id,
|
||||||
|
@ -111,7 +111,7 @@ async fn inner_fillet(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
args.batch_modeling_cmd(
|
args.batch_end_cmd(
|
||||||
uuid::Uuid::new_v4(),
|
uuid::Uuid::new_v4(),
|
||||||
ModelingCmd::Solid3DFilletEdge {
|
ModelingCmd::Solid3DFilletEdge {
|
||||||
edge_id,
|
edge_id,
|
||||||
|
@ -224,6 +224,17 @@ impl Args {
|
|||||||
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
|
self.ctx.engine.batch_modeling_cmd(id, self.source_range, &cmd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a modeling command to the batch that gets executed at the end of the file.
|
||||||
|
// This is good for something like fillet or chamfer where the engine would
|
||||||
|
// eat the path id if we executed it right away.
|
||||||
|
pub async fn batch_end_cmd(
|
||||||
|
&self,
|
||||||
|
id: uuid::Uuid,
|
||||||
|
cmd: kittycad::types::ModelingCmd,
|
||||||
|
) -> Result<(), crate::errors::KclError> {
|
||||||
|
self.ctx.engine.batch_end_cmd(id, self.source_range, &cmd).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Send the modeling cmd and wait for the response.
|
/// Send the modeling cmd and wait for the response.
|
||||||
pub async fn send_modeling_cmd(
|
pub async fn send_modeling_cmd(
|
||||||
&self,
|
&self,
|
||||||
@ -233,6 +244,16 @@ impl Args {
|
|||||||
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
|
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flush the batch for our fillets/chamfers if there are any.
|
||||||
|
pub async fn flush_batch(&self) -> Result<(), KclError> {
|
||||||
|
if self.ctx.engine.batch_end().lock().unwrap().is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.ctx.engine.flush_batch(true, SourceRange::default()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||||
Ok(MemoryItem::UserVal(crate::executor::UserVal {
|
Ok(MemoryItem::UserVal(crate::executor::UserVal {
|
||||||
value: j,
|
value: j,
|
||||||
|
@ -1173,6 +1173,12 @@ pub(crate) async fn inner_start_profile_at(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
|
if let SketchSurface::Face(_) = &sketch_surface {
|
||||||
|
// Flush the batch for our fillets/chamfers if there are any.
|
||||||
|
// If we do not do these for sketch on face, things will fail with face does not exist.
|
||||||
|
args.flush_batch().await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Enter sketch mode on the surface.
|
// Enter sketch mode on the surface.
|
||||||
// We call this here so you can reuse the sketch surface for multiple sketches.
|
// We call this here so you can reuse the sketch surface for multiple sketches.
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
|
@ -1873,7 +1873,7 @@ const bracket = startSketchOn('XY')
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1443, 1443])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"Fillet failed\" }]" }"#
|
r#"engine: KclErrorDetails { source_ranges: [SourceRange([1336, 1442])], message: "Modeling command failed: [ApiError { error_code: BadRequest, message: \"Fillet failed\" }]" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2039,6 +2039,73 @@ extrude(10, sketch001)
|
|||||||
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_sketch_on_face_after_fillets_referencing_face() {
|
||||||
|
let code = r#"// Shelf Bracket
|
||||||
|
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
|
||||||
|
|
||||||
|
|
||||||
|
// Define our bracket feet lengths
|
||||||
|
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
|
||||||
|
const wallMountL = 6 // the length of the bracket
|
||||||
|
|
||||||
|
|
||||||
|
// Define constants required to calculate the thickness needed to support 300 lbs
|
||||||
|
const sigmaAllow = 35000 // psi
|
||||||
|
const width = 6 // inch
|
||||||
|
const p = 300 // Force on shelf - lbs
|
||||||
|
const L = 12 // inches
|
||||||
|
const M = L * p / 2 // Moment experienced at fixed end of bracket
|
||||||
|
const FOS = 2 // Factor of safety of 2 to be conservative
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate the thickness off the bending stress and factor of safety
|
||||||
|
const thickness = sqrt(6 * M * FOS / (width * sigmaAllow))
|
||||||
|
|
||||||
|
// 0.25 inch fillet radius
|
||||||
|
const filletR = 0.25
|
||||||
|
|
||||||
|
// Sketch the bracket and extrude with fillets
|
||||||
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, wallMountL], %, 'outerEdge')
|
||||||
|
|> line([-shelfMountL, 0], %, 'seg01')
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([shelfMountL - thickness, 0], %, 'innerEdge')
|
||||||
|
|> line([0, -wallMountL + thickness], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|
||||||
|
|> fillet({
|
||||||
|
radius: filletR,
|
||||||
|
tags: [
|
||||||
|
getPreviousAdjacentEdge('innerEdge', %)
|
||||||
|
]
|
||||||
|
}, %)
|
||||||
|
|> fillet({
|
||||||
|
radius: filletR + thickness,
|
||||||
|
tags: [
|
||||||
|
getPreviousAdjacentEdge('outerEdge', %)
|
||||||
|
]
|
||||||
|
}, %)
|
||||||
|
|
||||||
|
const sketch001 = startSketchOn(bracket, 'seg01')
|
||||||
|
|> startProfileAt([4.28, 3.83], %)
|
||||||
|
|> line([2.17, -0.03], %)
|
||||||
|
|> line([-0.07, -1.8], %)
|
||||||
|
|> line([-2.07, 0.05], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(10, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/sketch_on_face_after_fillets_referencing_face.png",
|
||||||
|
&result,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_circular_pattern3d_array_of_extrudes() {
|
async fn serial_test_circular_pattern3d_array_of_extrudes() {
|
||||||
let code = r#"const plane001 = startSketchOn('XZ')
|
let code = r#"const plane001 = startSketchOn('XZ')
|
||||||
@ -2074,3 +2141,131 @@ const pattn1 = patternLinear3d({
|
|||||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/pattern3d_array_of_extrudes.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/pattern3d_array_of_extrudes.png", &result, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_fillets_referencing_other_fillets() {
|
||||||
|
let code = r#"// Z-Bracket
|
||||||
|
|
||||||
|
// Z-brackets are designed to affix or hang objects from a wall by securing them to the wall's studs. These brackets offer support and mounting solutions for bulky or heavy items that may be challenging to attach directly. Serving as a protective feature, Z-brackets help prevent heavy loads from moving or toppling, enhancing safety in the environment where they are used.
|
||||||
|
|
||||||
|
// Define constants
|
||||||
|
const foot1Length = 4
|
||||||
|
const height = 4
|
||||||
|
const foot2Length = 5
|
||||||
|
const width = 4
|
||||||
|
const filletRad = 0.25
|
||||||
|
const thickness = 0.125
|
||||||
|
|
||||||
|
const cornerFilletRad = 0.5
|
||||||
|
|
||||||
|
const holeDia = 0.5
|
||||||
|
|
||||||
|
const sketch001 = startSketchOn("XZ")
|
||||||
|
|> startProfileAt([-foot1Length, 0], %)
|
||||||
|
|> line([0, thickness], %, 'cornerFillet1')
|
||||||
|
|> line([foot1Length, 0], %)
|
||||||
|
|> line([0, height], %, 'fillet1')
|
||||||
|
|> line([foot2Length, 0], %)
|
||||||
|
|> line([0, -thickness], %, 'cornerFillet2')
|
||||||
|
|> line([-foot2Length+thickness, 0], %)
|
||||||
|
|> line([0, -height], %, 'fillet2')
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
const baseExtrusion = extrude(width, sketch001)
|
||||||
|
|> fillet({
|
||||||
|
radius: cornerFilletRad,
|
||||||
|
tags: ["cornerFillet1", "cornerFillet2", getOppositeEdge("cornerFillet1", %), getOppositeEdge("cornerFillet2", %)],
|
||||||
|
}, %)
|
||||||
|
|> fillet({
|
||||||
|
radius: filletRad,
|
||||||
|
tags: [getPreviousAdjacentEdge("fillet1", %), getPreviousAdjacentEdge("fillet2", %)]
|
||||||
|
}, %)
|
||||||
|
|> fillet({
|
||||||
|
radius: filletRad + thickness,
|
||||||
|
tags: [getNextAdjacentEdge("fillet1", %), getNextAdjacentEdge("fillet2", %)],
|
||||||
|
}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/fillets_referencing_other_fillets.png",
|
||||||
|
&result,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_chamfers_referencing_other_chamfers() {
|
||||||
|
let code = r#"// Z-Bracket
|
||||||
|
|
||||||
|
// Z-brackets are designed to affix or hang objects from a wall by securing them to the wall's studs. These brackets offer support and mounting solutions for bulky or heavy items that may be challenging to attach directly. Serving as a protective feature, Z-brackets help prevent heavy loads from moving or toppling, enhancing safety in the environment where they are used.
|
||||||
|
|
||||||
|
// Define constants
|
||||||
|
const foot1Length = 4
|
||||||
|
const height = 4
|
||||||
|
const foot2Length = 5
|
||||||
|
const width = 4
|
||||||
|
const chamferRad = 0.25
|
||||||
|
const thickness = 0.125
|
||||||
|
|
||||||
|
const cornerChamferRad = 0.5
|
||||||
|
|
||||||
|
const holeDia = 0.5
|
||||||
|
|
||||||
|
const sketch001 = startSketchOn("XZ")
|
||||||
|
|> startProfileAt([-foot1Length, 0], %)
|
||||||
|
|> line([0, thickness], %, 'cornerChamfer1')
|
||||||
|
|> line([foot1Length, 0], %)
|
||||||
|
|> line([0, height], %, 'chamfer1')
|
||||||
|
|> line([foot2Length, 0], %)
|
||||||
|
|> line([0, -thickness], %, 'cornerChamfer2')
|
||||||
|
|> line([-foot2Length+thickness, 0], %)
|
||||||
|
|> line([0, -height], %, 'chamfer2')
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
const baseExtrusion = extrude(width, sketch001)
|
||||||
|
|> chamfer({
|
||||||
|
length: cornerChamferRad,
|
||||||
|
tags: ["cornerChamfer1", "cornerChamfer2", getOppositeEdge("cornerChamfer1", %), getOppositeEdge("cornerChamfer2", %)],
|
||||||
|
}, %)
|
||||||
|
|> chamfer({
|
||||||
|
length: chamferRad,
|
||||||
|
tags: [getPreviousAdjacentEdge("chamfer1", %), getPreviousAdjacentEdge("chamfer2", %)]
|
||||||
|
}, %)
|
||||||
|
|> chamfer({
|
||||||
|
length: chamferRad + thickness,
|
||||||
|
tags: [getNextAdjacentEdge("chamfer1", %), getNextAdjacentEdge("chamfer2", %)],
|
||||||
|
}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/chamfers_referencing_other_chamfers.png",
|
||||||
|
&result,
|
||||||
|
1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_engine_error_source_range_on_last_command() {
|
||||||
|
let code = r#"const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([61.74, 206.13], %)
|
||||||
|
|> xLine(305.11, %, 'seg01')
|
||||||
|
|> yLine(-291.85, %)
|
||||||
|
|> xLine(-segLen('seg01', %), %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(40.14, %)
|
||||||
|
|> shell({
|
||||||
|
faces: ["seg01"],
|
||||||
|
thickness: 3.14,
|
||||||
|
}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code, UnitLength::Mm).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.err().unwrap().to_string(),
|
||||||
|
r#"engine: KclErrorDetails { source_ranges: [SourceRange([262, 320])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Invalid brep after shell operation\" }]" }"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
Reference in New Issue
Block a user