Compare commits

...

9 Commits

Author SHA1 Message Date
d3a4fd8b55 Raw dog parse for python bindings (#6970)
* raw dog parse python

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

* add tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-15 15:39:28 +00:00
2be7107cca External: Fix: symbol replace text box dark mode contrast (#6827) (#6927)
* fix: symbol replace text box dark mode contrast (#6827)

* fix: symbol replace text box dark mode contrast (#6827)

* correct fix for rename popup white text on white background #6827

* correct fix for rename popup white text on white background #6827

* added comments explaining the change

---------

Co-authored-by: rajgandhi1 <rajgandhi9952@gmail.com>
2025-05-15 12:57:39 +00:00
94f194a984 new sweep default (#6965)
snaps

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-15 11:50:55 +00:00
4fe880a970 Revert "fixes"
This reverts commit 8f5fbfc273.
2025-05-15 04:28:20 -07:00
8f5fbfc273 fixes
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-15 04:27:59 -07:00
e660f52bb0 Restore automated snapshot commits (#6960)
* Restore automated snapshot commits

* Update snapshots

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-15 03:28:42 +00:00
d74fdd9369 Color picker goober works w single quotes (#6957)
* lsp stuffs

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-15 12:37:14 +10:00
334145f0be fix sketch on face of union (#6949)
* fix sketch on face of union

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

* rotate the model

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 23:25:12 +00:00
c24073b6ae Sensible parser error when using keyword as arg label (#6948)
Closes <https://github.com/KittyCAD/modeling-app/issues/6924>
2025-05-14 22:29:31 +00:00
44 changed files with 8033 additions and 96 deletions

View File

@ -40,7 +40,7 @@ jobs:
- name: Install dependencies
run: npm install
- name: Download Wasm Cache
- name: Download Wasm cache
id: download-wasm
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
@ -52,7 +52,7 @@ jobs:
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
- name: Build Wasm condition
id: wasm
run: |
set -euox pipefail
@ -70,7 +70,7 @@ jobs:
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
- name: Install Rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
@ -81,7 +81,7 @@ jobs:
with:
tool: wasm-pack
- name: Rust Cache
- name: Use Rust cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
@ -117,7 +117,7 @@ jobs:
- uses: actions/download-artifact@v4
name: prepared-wasm
- name: Copy prepared wasm
- name: Copy prepared Wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -133,20 +133,17 @@ jobs:
id: deps-install
run: npm install
- name: Cache Playwright Browsers
- name: Cache browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers
- name: Install browsers
run: npm run playwright install --with-deps
- name: build web
run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots
- name: Capture snapshots
uses: nick-fields/retry@v3.0.2
with:
shell: bash
@ -170,7 +167,7 @@ jobs:
retention-days: 30
overwrite: true
- name: Check for changes
- name: Check diff
if: ${{ github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
@ -181,9 +178,8 @@ jobs:
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes
if: ${{ false && steps.git-check.outputs.modified == 'true' }}
- name: Commit changes
if: ${{ steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -193,7 +189,7 @@ jobs:
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
git commit --message "Update snapshots" || true
git push
git push origin ${{ github.head_ref }}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -251786,7 +251786,7 @@
}
},
"required": false,
"description": "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to sketchPlane.",
"description": "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve.",
"labelRequired": true
},
{
@ -256720,7 +256720,7 @@
false
],
[
"// Create a spring by sweeping around a helix path.\n\n// Create a helix around the Z axis.\nhelixPath = helix(\n angleStart = 0,\n ccw = true,\n revolutions = 4,\n length = 10,\n radius = 5,\n axis = Z,\n)\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn(YZ)\n |> circle(center = [0, 0], radius = 1)\n |> sweep(path = helixPath)",
"// Create a spring by sweeping around a helix path.\n\n// Create a helix around the Z axis.\nhelixPath = helix(\n angleStart = 0,\n ccw = true,\n revolutions = 4,\n length = 10,\n radius = 5,\n axis = Z,\n)\n\n// Create a spring by sweeping around the helix path.\nspringSketch = startSketchOn(YZ)\n |> circle(center = [0, 0], radius = 1)\n |> sweep(path = helixPath, relativeTo = \"sketchPlane\")",
false
],
[

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,3 @@
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
@ -9,6 +8,7 @@ import {
settingsToToml,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
test.beforeEach(async ({ page, context }) => {
// Make the user avatar image always 404
@ -873,6 +873,50 @@ sweepSketch = startSketchOn(XY)
mask: lowerRightMasks(page),
})
})
test('code color goober works with single quotes', async ({
page,
context,
scene,
cmdBar,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`// Create a pipe using a sweep.
// Create a path for the sweep.
sweepPath = startSketchOn(XZ)
|> startProfile(at = [0.05, 0.05])
|> line(end = [0, 7])
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
sweepSketch = startSketchOn(XY)
|> startProfile(at = [2, 0])
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|> sweep(path = sweepPath)
|> appearance(
color = '#bb00ff',
metalness = 90,
roughness = 90
)
`
)
})
await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart()
await scene.settled(cmdBar)
await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100,
mask: lowerRightMasks(page),
})
})
test('code color goober opening window', async ({
page,

34
rust/Cargo.lock generated
View File

@ -535,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -963,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1746,7 +1746,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1815,7 +1815,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"anyhow",
"clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"Inflector",
"anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"proc-macro2",
"quote",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.70"
version = "0.2.71"
dependencies = [
"anyhow",
"clap",
@ -1875,7 +1875,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"anyhow",
"clap",
@ -1895,7 +1895,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.70"
version = "0.2.71"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1971,7 +1971,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.70"
version = "0.3.71"
dependencies = [
"anyhow",
"kcl-lib",
@ -1986,7 +1986,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1999,7 +1999,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"anyhow",
"async-trait",
@ -2013,7 +2013,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.70"
version = "0.1.71"
dependencies = [
"anyhow",
"bson",
@ -2987,7 +2987,7 @@ dependencies = [
"once_cell",
"socket2",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3306,7 +3306,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3900,7 +3900,7 @@ dependencies = [
"getrandom 0.3.1",
"once_cell",
"rustix",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4753,7 +4753,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.70"
version = "0.1.71"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.70"
version = "0.1.71"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.70"
version = "0.1.71"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.70"
version = "0.2.71"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

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

View File

@ -4220,8 +4220,8 @@ sketch001 = startSketchOn(XY)
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
@ -4272,8 +4272,8 @@ sketch001 = startSketchOn(XY)
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
@ -4291,8 +4291,8 @@ sketch001 = startSketchOn(XY)
uri: "file:///test.kcl".try_into().unwrap(),
},
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,

View File

@ -438,8 +438,15 @@ impl Node<Program> {
let add_color = |literal: &Node<Literal>| {
// Check if the string is a color.
if let Some(c) = literal.value.is_color() {
let source_range = literal.as_source_range();
// We subtract 1 from either side because of the "'s in the literal.
let fixed_source_range = SourceRange::new(
source_range.start() + 1,
source_range.end() - 1,
source_range.module_id(),
);
let color = ColorInformation {
range: literal.as_source_range().to_lsp_range(code),
range: fixed_source_range.to_lsp_range(code),
color: tower_lsp::lsp_types::Color {
red: c.r,
green: c.g,
@ -498,7 +505,11 @@ impl Node<Program> {
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
match node {
crate::walk::Node::Literal(literal) => {
if literal.start == pos_start && literal.end == pos_end && literal.value.is_color().is_some() {
// Account for the quotes in the literal.
if (literal.start + 1) == pos_start
&& (literal.end - 1) == pos_end
&& literal.value.is_color().is_some()
{
found.replace(true);
return Ok(true);
}

View File

@ -2729,6 +2729,17 @@ fn ty(i: &mut TokenSlice) -> PResult<Token> {
keyword(i, "type")
}
fn any_keyword(i: &mut TokenSlice) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword => Ok(token),
_ => Err(CompilationError::fatal(
token.as_source_range(),
"expected some reserved keyword".to_owned(),
)),
})
.parse_next(i)
}
fn keyword(i: &mut TokenSlice, expected: &str) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword if token.value == expected => Ok(token),
@ -3143,12 +3154,14 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
NonCode(Node<NonCodeNode>),
LabeledArg(LabeledArg),
UnlabeledArg(Expr),
Keyword(Token),
}
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
let args: Vec<_> = repeat(
0..,
alt((
terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
expression.map(ArgPlace::UnlabeledArg),
)),
@ -3164,6 +3177,18 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
ArgPlace::LabeledArg(x) => {
args.push(x);
}
ArgPlace::Keyword(kw) => {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(kw.clone()),
format!(
"`{}` is not the name of an argument (it's a reserved keyword)",
kw.value
),
)
.into(),
));
}
ArgPlace::UnlabeledArg(arg) => {
let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
if followed_by_equals {
@ -5055,6 +5080,30 @@ bar = 1
}
}
#[test]
fn test_sensible_error_when_using_keyword_as_arg_label() {
for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = match fn_call_kw.parse(tokens.as_slice()) {
Err(e) => e,
Ok(ast) => {
eprintln!("{ast:#?}");
panic!("Expected this to error but it didn't");
}
};
let cause = err.inner().cause.as_ref().unwrap();
assert_eq!(
cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
"failed test {i}: {program}"
);
assert_eq!(
cause.source_range.start(),
program.find("fn").unwrap(),
"failed test {i}: {program}"
);
}
}
#[test]
fn test_sensible_error_when_missing_rhs_of_obj_property() {
for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {

View File

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

View File

@ -14,7 +14,7 @@ use super::{args::TyF64, DEFAULT_TOLERANCE};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, KclValue, Solid},
std::Args,
std::{patterns::GeometryTrait, Args},
};
/// Union two or more solids into a single solid.
@ -123,7 +123,7 @@ pub(crate) async fn inner_union(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -155,7 +155,7 @@ pub(crate) async fn inner_union(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}
@ -249,7 +249,7 @@ pub(crate) async fn inner_intersect(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -281,7 +281,7 @@ pub(crate) async fn inner_intersect(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}
@ -385,7 +385,7 @@ pub(crate) async fn inner_subtract(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -419,7 +419,7 @@ pub(crate) async fn inner_subtract(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}

View File

@ -598,7 +598,7 @@ fn array_to_point2d(
.map(|val| val.as_point2d().unwrap())
}
trait GeometryTrait: Clone {
pub trait GeometryTrait: Clone {
type Set: Into<Vec<Self>> + Clone;
fn id(&self) -> Uuid;
fn original_id(&self) -> Uuid;
@ -608,6 +608,7 @@ trait GeometryTrait: Clone {
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError>;
#[allow(async_fn_in_trait)]
async fn flush_batch(args: &Args, exec_state: &mut ExecState, set: &Self::Set) -> Result<(), KclError>;
}
@ -641,6 +642,8 @@ impl GeometryTrait for Solid {
type Set = Vec<Solid>;
fn set_id(&mut self, id: Uuid) {
self.id = id;
// We need this for in extrude.rs when you sketch on face.
self.sketch.id = id;
}
fn id(&self) -> Uuid {

View File

@ -112,7 +112,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// // Create a spring by sweeping around the helix path.
/// springSketch = startSketchOn(YZ)
/// |> circle( center = [0, 0], radius = 1)
/// |> sweep(path = helixPath)
/// |> sweep(path = helixPath, relativeTo = "sketchPlane")
/// ```
///
/// ```no_run
@ -167,7 +167,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
path = { docs = "The path to sweep the sketch along" },
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
tolerance = { docs = "Tolerance for this operation" },
relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to sketchPlane."},
relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve."},
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the sweep" },
},
@ -191,14 +191,13 @@ async fn inner_sweep(
};
let relative_to = match relative_to.as_deref() {
Some("sketchPlane") => RelativeTo::SketchPlane,
Some("trajectoryCurve") => RelativeTo::TrajectoryCurve,
Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
Some(_) => {
return Err(KclError::Syntax(crate::errors::KclErrorDetails {
source_ranges: vec![args.source_range],
message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
}))
}
None => RelativeTo::default(),
};
let mut solids = Vec::new();

View File

@ -83,7 +83,7 @@ export END = 'end'
/// // Create a spring by sweeping around the helix path.
/// springSketch = startSketchOn(YZ)
/// |> circle( center = [0, 0], radius = 0.5)
/// |> sweep(path = helixPath)
/// |> sweep(path = helixPath, relativeTo = "sketchPlane")
/// ```
///
/// ```
@ -104,7 +104,7 @@ export END = 'end'
/// // Create a spring by sweeping around the helix path.
/// springSketch = startSketchOn(XY)
/// |> circle( center = [0, 0], radius = 0.5 )
/// |> sweep(path = helixPath)
/// |> sweep(path = helixPath, relativeTo = "sketchPlane")
/// ```
///
/// ```
@ -124,7 +124,7 @@ export END = 'end'
/// // Create a spring by sweeping around the helix path.
/// springSketch = startSketchOn(XY)
/// |> circle( center = [0, 0], radius = 1 )
/// |> sweep(path = helixPath)
/// |> sweep(path = helixPath, relativeTo = "sketchPlane")
/// ```
///
/// ```
@ -413,7 +413,7 @@ export fn offsetPlane(
/// // Create a spring by sweeping around the helix path.
/// sweepedSpring = clone(springSketch)
/// |> translate(x=100)
/// |> sweep(path = helixPath)
/// |> sweep(path = helixPath, relativeTo = "sketchPlane")
/// ```
///
/// ```kcl

View File

@ -5122,7 +5122,7 @@ description: Artifact commands bench.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -5134,7 +5134,7 @@ description: Artifact commands bench.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
}
]

View File

@ -906,7 +906,7 @@ description: Artifact commands cold-plate.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

View File

@ -5576,7 +5576,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -6111,7 +6111,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -9469,7 +9469,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -9601,7 +9601,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -10120,7 +10120,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -10252,7 +10252,7 @@ description: Artifact commands cpu-cooler.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

View File

@ -1598,7 +1598,7 @@ description: Artifact commands exhaust-manifold.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -1610,7 +1610,7 @@ description: Artifact commands exhaust-manifold.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -1622,7 +1622,7 @@ description: Artifact commands exhaust-manifold.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{
@ -1634,7 +1634,7 @@ description: Artifact commands exhaust-manifold.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

View File

@ -4491,7 +4491,7 @@ description: Artifact commands utility-sink.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
@settings(defaultLengthUnit = in)
// Define parameters
trussSupportAngle = 15
height = 120
thickness = 4
sketch001 = startSketchOn(YZ)
profile001 = startProfile(sketch001, at = [60, 0])
|> xLine(length = -120, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = 25, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -25, endAbsoluteX = 60)
|> close()
profile002 = startProfile(sketch001, at = [60-thickness, thickness])
|> xLine(endAbsolute = thickness/2)
|> yLine(endAbsolute = segEndY(tag001)-thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = -25)
|> close(%)
profile003 = startProfile(sketch001, at = [-60+thickness, thickness])
|> xLine(endAbsolute = -thickness/2)
|> yLine(endAbsolute = segEndY(tag001)-thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = 205)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
body001 = extrude(profile001, length = 2)
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness/2-1, 14])
|> angledLine(angle = 30, length = 25)
|> angledLine(angle = -25, length = 5)
|> angledLine(angle = 210, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
profile007 = startProfile(sketch002, at = [-thickness/2+1, 14])
|> angledLine(angle = 150, length = 25)
|> angledLine(angle = 205, length = 5)
|> angledLine(angle = -30, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
newSketch = body001 + profile006 + profile007
leg001Sketch = startSketchOn(newSketch, face = bottomFace)
legProfile001 = startProfile(leg001Sketch, at = [-60, 0])
|> xLine(%, length = 4)
|> yLine(%, length = 2)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = 48)
|> rotate(axis = [0, 0, 1.0], angle = -90)

View File

@ -0,0 +1,264 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed sketch_on_face_union.kcl
---
[
{
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"labeledArgs": {
"tool": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
"name": "subtract2d",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"tool": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
"name": "subtract2d",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"type": "KclStdLibCall",
"name": "offsetPlane",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"offset": {
"value": {
"type": "Number",
"value": 0.1,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 1.8,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 1.8,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"face": {
"value": {
"type": "TagIdentifier",
"value": "bottomFace",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Solid",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 48.0,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
}
]

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,62 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing sketch_on_face_union.kcl
---
@settings(defaultLengthUnit = in)
// Define parameters
trussSupportAngle = 15
height = 120
thickness = 4
sketch001 = startSketchOn(YZ)
profile001 = startProfile(sketch001, at = [60, 0])
|> xLine(length = -120, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = 25, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -25, endAbsoluteX = 60)
|> close()
profile002 = startProfile(sketch001, at = [60 - thickness, thickness])
|> xLine(endAbsolute = thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = -25)
|> close(%)
profile003 = startProfile(sketch001, at = [-60 + thickness, thickness])
|> xLine(endAbsolute = -thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = 205)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
body001 = extrude(profile001, length = 2)
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness / 2 - 1, 14])
|> angledLine(angle = 30, length = 25)
|> angledLine(angle = -25, length = 5)
|> angledLine(angle = 210, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
profile007 = startProfile(sketch002, at = [-thickness / 2 + 1, 14])
|> angledLine(angle = 150, length = 25)
|> angledLine(angle = 205, length = 5)
|> angledLine(angle = -30, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
newSketch = body001 + profile006 + profile007
leg001Sketch = startSketchOn(newSketch, face = bottomFace)
legProfile001 = startProfile(leg001Sketch, at = [-60, 0])
|> xLine(%, length = 4)
|> yLine(%, length = 2)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = 48)
|> rotate(axis = [0, 0, 1.0], angle = -90)

View File

@ -418,7 +418,7 @@ description: Artifact commands subtract_regression03.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

View File

@ -395,7 +395,7 @@ description: Artifact commands subtract_regression05.kcl
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
"relative_to": "trajectory_curve"
}
},
{

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-python-bindings"
version = "0.3.70"
version = "0.3.71"
edition = "2021"
repository = "https://github.com/kittycad/modeling-app"
exclude = ["tests/*", "files/*", "venv/*"]

View File

@ -227,6 +227,31 @@ async fn new_context_state(current_file: Option<std::path::PathBuf>) -> Result<(
Ok((ctx, state))
}
/// Parse the kcl code from a file path.
#[pyfunction]
async fn parse(path: String) -> PyResult<()> {
tokio()
.spawn(async move {
let (code, path) = get_code_and_file_path(&path)
.await
.map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?;
let _program = kcl_lib::Program::parse_no_errs(&code)
.map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?;
Ok(())
})
.await
.map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?
}
/// Parse the kcl code.
#[pyfunction]
fn parse_code(code: String) -> PyResult<()> {
let _program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?;
Ok(())
}
/// Execute the kcl code from a file path.
#[pyfunction]
async fn execute(path: String) -> PyResult<()> {
@ -534,6 +559,8 @@ fn kcl(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Discovered>()?;
// Add our functions to the module.
m.add_function(wrap_pyfunction!(parse, m)?)?;
m.add_function(wrap_pyfunction!(parse_code, m)?)?;
m.add_function(wrap_pyfunction!(execute, m)?)?;
m.add_function(wrap_pyfunction!(execute_code, m)?)?;
m.add_function(wrap_pyfunction!(execute_and_snapshot, m)?)?;

View File

@ -39,6 +39,33 @@ async def test_kcl_execute():
await kcl.execute(lego_file)
@pytest.mark.asyncio
async def test_kcl_parse_with_exception():
# Read from a file.
try:
await kcl.parse(os.path.join(files_dir, "parse_file_error"))
except Exception as e:
assert e is not None
assert len(str(e)) > 0
assert "lksjndflsskjfnak;jfna##" in str(e)
@pytest.mark.asyncio
async def test_kcl_parse():
# Read from a file.
await kcl.parse(lego_file)
@pytest.mark.asyncio
async def test_kcl_parse_code():
# Read from a file.
with open(lego_file, "r") as f:
code = str(f.read())
assert code is not None
assert len(code) > 0
kcl.parse_code(code)
@pytest.mark.asyncio
async def test_kcl_execute_code():
# Read from a file.
@ -97,9 +124,7 @@ async def test_kcl_execute_and_snapshot():
@pytest.mark.asyncio
async def test_kcl_execute_and_snapshot_dir():
# Read from a file.
image_bytes = await kcl.execute_and_snapshot(
car_wheel_dir, kcl.ImageFormat.Jpeg
)
image_bytes = await kcl.execute_and_snapshot(car_wheel_dir, kcl.ImageFormat.Jpeg)
assert image_bytes is not None
assert len(image_bytes) > 0
@ -129,10 +154,12 @@ def test_kcl_format():
assert formatted_code is not None
assert len(formatted_code) > 0
@pytest.mark.asyncio
async def test_kcl_format_dir():
await kcl.format_dir(car_wheel_dir)
def test_kcl_lint():
# Read from a file.
with open(os.path.join(files_dir, "box_with_linter_errors.kcl"), "r") as f:

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-to-core"
description = "Utility methods to convert kcl to engine core executable tests"
version = "0.1.70"
version = "0.1.71"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-wasm-lib"
version = "0.1.70"
version = "0.1.71"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.83"

View File

@ -111,7 +111,7 @@ function discoverColorsInKCL(
}
export function parseColorLiteral(colorLiteral: string): ColorData | null {
const literal = colorLiteral.replace(/"/g, '')
const literal = colorLiteral.replace(/"/g, '').replace(/'/g, '')
const match = hexRegex.exec(literal)
if (!match) {
return null

View File

@ -212,6 +212,12 @@ code {
z-index: 99999999999 !important;
}
.cm-rename-popup input {
/* use black text on white background in both light and dark mode */
color: black !important;
background: white !important;
}
@keyframes blink {
0%,
100% {