Compare commits

...

12 Commits

Author SHA1 Message Date
c45c2e27ba make sure the nix flake never breaks (#6273)
make sure teh nix flake never breaks

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-11 06:27:42 +00:00
5832890dbb Add CSG operations to the artifact graph (#6104)
* Add CSG operations to the artifact graph

* Add tool IDs for subtract

* Fix names to match modeling-cmds now that it's done

* Update output since adding CSG ops

* Update formatting

* Add extra solid ids to the graph

* Fix to not add duplicates to the graph
2025-04-11 05:16:29 +00:00
6f2e6d14b6 refactor: Change to use topLevelModule helper function (#6264) 2025-04-11 05:13:10 +00:00
0d899694b2 Remove experimental warning on whole-module imports (#6240) 2025-04-11 04:22:51 +00:00
7bd5e7365d Insert a newline between block comments and attributes (#6250)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2025-04-11 03:19:52 +00:00
a63e51e2ad Remove import function from std (#6241)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-11 03:11:59 +00:00
1b8eee86a1 Fix vscode lsp bugs (#6271)
* start of shit

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

* untitled test

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-04-11 12:53:05 +10:00
6d50278d34 bump kcl friends (#6272)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-10 19:01:30 -07:00
35844842de Bump modeling api & pull thru csg endpoints (#6245)
* csg-upts

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

* base

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

* do the id shit

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

* tried to run

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

* csg-upts

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

* use bens samples

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

* use bens samples

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

* gen std

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

* gen std

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>

* fix;

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-10 18:30:57 -07:00
843e772094 Franknoirot/adhoc/improve e2e (#6265)
* fix: increasing timeout to reduce failures..?

* fix: removing debugging code

* Remove unnecessary pixel color check

* Only close the command palette on disconnection events

This code closes the palette on *any* network event, including it
getting established. This made tests like "Create a few projects using
the default project name" unreliable.

Also adds a block comment about how we should do something more
sophisticated than bail out in the future.

* Show a toast to explain why the user just lost their command flow

* Remove unnecessary click in test. `fill` focuses already

* Oop don't spam this disconnection toast if the palette isn't open

Honoring exhausting hook dependency checks

* Skip circle snapshot test, it's being weird

* Disable the export button if engine connection is unavailable

* Refactor useDemoCode to hopefully be more reliable

* allow weak connections to receive demo code

* revert Refactor useDemoCode to hopefully be more reliable
Commit:
2625c14783 [2625c1478]

---------

Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Jace Browning <jacebrowning@gmail.com>
Co-authored-by: Andrew Varga <grizzly33@gmail.com>
2025-04-11 00:08:39 +00:00
9d0518dfeb Fix URL encoded names after rename to Zoo Design Studio (#6269) 2025-04-10 17:20:37 -04:00
d33d399c31 Repetitive structs removed for import file extensions (#6211)
* get rid of repetitive structs

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

fmt

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

get rid of more

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

add more

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

updates

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

fix

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

updates

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

await the shit

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

updates

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

put it at the root

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

* updates

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

* fix;es

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

* fixes

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

* fix

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

* kcl-language-server flake

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-04-10 13:57:12 -07:00
95 changed files with 7596 additions and 1993 deletions

View File

@ -378,7 +378,7 @@ jobs:
NOTES: ${{ needs.prepare-files.outputs.notes }}
PUB_DATE: ${{ github.event.repository.updated_at }}
WEBSITE_DIR: ${{ env.IS_NIGHTLY == 'true' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ env.IS_NIGHTLY == 'true' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
URL_CODED_NAME: ${{ env.IS_NIGHTLY == 'true' && 'Zoo%20Design%20Studio%20%28Nightly%29' || 'Zoo%20Design%20Studio' }}
run: |
RELEASE_DIR=https://${WEBSITE_DIR}
jq --null-input \

55
.github/workflows/nix.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Test Nix Flake
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
nix-flake-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix flake check for all platforms
run: |
nix flake check --all-systems
nix-build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix build . for x86_64-linux
run: nix build .
nix-build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: cachix/install-nix-action@v31
with:
nix_path: nixpkgs=channel:nixos-unstable
- name: nix build . for x86_64-darwin
run: nix build .

1
.gitignore vendored
View File

@ -26,6 +26,7 @@ yarn-error.log*
.idea
.vscode
.helix
result
# rust
rust/target

File diff suppressed because one or more lines are too long

View File

@ -77,6 +77,7 @@ layout: manual
* [`helix`](kcl/std-helix)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`intersect`](kcl/intersect)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)
* [`legAngX`](kcl/legAngX)
@ -123,6 +124,7 @@ layout: manual
* [`sqrt`](kcl/sqrt)
* [`startProfileAt`](kcl/startProfileAt)
* [`startSketchOn`](kcl/startSketchOn)
* [`subtract`](kcl/subtract)
* [`sweep`](kcl/sweep)
* [`tangentToEnd`](kcl/tangentToEnd)
* [`tangentialArc`](kcl/tangentialArc)
@ -131,6 +133,7 @@ layout: manual
* [`toDegrees`](kcl/toDegrees)
* [`toRadians`](kcl/toRadians)
* [`translate`](kcl/translate)
* [`union`](kcl/union)
* [`xLine`](kcl/xLine)
* [`yLine`](kcl/yLine)
* **std::math**

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -252,7 +252,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
| `type` |enum: `ImportedGeometry`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |

File diff suppressed because one or more lines are too long

View File

@ -1144,7 +1144,7 @@ sketch001 = startSketchOn(XZ)
)
test(
`Can use the import stdlib function on a local OBJ file`,
`Can import a local OBJ file`,
{ tag: '@electron' },
async ({ page, context }, testInfo) => {
test.fixme(orRunWhenFullSuiteEnabled())
@ -1194,7 +1194,7 @@ sketch001 = startSketchOn(XZ)
.toBeLessThan(15)
})
await test.step(`Write the import function line`, async () => {
await u.codeLocator.fill(`import('cube.obj')`)
await u.codeLocator.fill(`import 'cube.obj'\ncube`)
await page.waitForTimeout(800)
})
await test.step(`Reset the camera before checking`, async () => {

View File

@ -99,7 +99,6 @@ export class HomePageFixture {
createAndGoToProject = async (projectTitle = 'untitled') => {
await this.projectsLoaded()
await this.projectButtonNew.click()
await this.projectTextName.click()
await this.projectTextName.fill(projectTitle)
await this.projectButtonContinue.click()
}

View File

@ -233,7 +233,7 @@ export class SceneFixture {
settled = async (cmdBar: CmdBarFixture) => {
const u = await getUtils(this.page)
await expect(this.startEditSketchBtn).not.toBeDisabled()
await expect(this.startEditSketchBtn).not.toBeDisabled({ timeout: 15_000 })
await expect(this.startEditSketchBtn).toBeVisible()
await cmdBar.openCmdBar()

View File

@ -68,12 +68,10 @@ test.describe('edit with AI example snapshots', () => {
body1CapCoords.x,
body1CapCoords.y
)
const yellow: [number, number, number] = [179, 179, 131]
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
await test.step('wait for scene to load select body and check selection came through', async () => {
await clickBody1Cap()
await scene.expectPixelColor(yellow, body1CapCoords, 20)
await editor.expectState({
highlightedCode: '',
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],

View File

@ -588,6 +588,7 @@ test(
'Draft circle should look right',
{ tag: '@snapshot' },
async ({ page, context, cmdBar, scene }) => {
test.fixme(orRunWhenFullSuiteEnabled())
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio

View File

@ -0,0 +1,15 @@
import { createProject } from '@e2e/playwright/test-utils'
import { test } from '@e2e/playwright/zoo-test'
test.describe('Stress test', () => {
test('Create project and load stress test', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'stress-test-project'
// Create and load project
await createProject({ name: projectName, page })
await scene.settled(cmdBar)
})
})

77
flake.lock generated
View File

@ -1,6 +1,56 @@
{
"nodes": {
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1743800763,
"narHash": "sha256-YFKV+fxEpMgP5VsUcM6Il28lI0NlpM7+oB1XxbBAYCw=",
"owner": "nix-community",
"repo": "naersk",
"rev": "ed0232117731a4c19d3ee93aa0c382a8fe754b01",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1744157173,
"narHash": "sha256-bWSjxDwq7iVePrhmA7tY2dyMWHuNJo8knkO4y+q4ZkY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6a39c6e495eefabc935d8ddf66aa45d85b85fa3f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1744157173,
"narHash": "sha256-bWSjxDwq7iVePrhmA7tY2dyMWHuNJo8knkO4y+q4ZkY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6a39c6e495eefabc935d8ddf66aa45d85b85fa3f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1736320768,
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
@ -16,38 +66,23 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1728538411,
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1736476219,
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
"lastModified": 1744338850,
"narHash": "sha256-pwMIVmsb8fjjT92n5XFDqCsplcX70qVMMT7NulumPXs=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
"rev": "5e64aecc018e6f775572609e7d7485fdba6985a7",
"type": "github"
},
"original": {

136
flake.nix
View File

@ -1,84 +1,96 @@
{
description = "modeling-app development environment";
description = "zoo.dev modeling-app";
# Flake inputs
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix
rust-overlay.url = "github:oxalica/rust-overlay";
naersk.url = "github:nix-community/naersk";
};
# Flake outputs
outputs = { self, nixpkgs, rust-overlay }:
let
# Overlays enable you to customize the Nixpkgs attribute set
overlays = [
# Makes a `rust-bin` attribute available in Nixpkgs
(import rust-overlay)
# Provides a `rustToolchain` attribute for Nixpkgs that we can use to
# create a Rust environment
(self: super: {
rustToolchain = super. rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
extensions = [ "rustfmt" "llvm-tools-preview" "rust-src" ];
outputs = {
self,
nixpkgs,
rust-overlay,
naersk,
}: let
overlays = [
(import rust-overlay)
(self: super: {
rustToolchain = super.rust-bin.stable.latest.default.override {
targets = ["wasm32-unknown-unknown"];
extensions = ["rustfmt" "llvm-tools-preview" "rust-src"];
};
})
];
allSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = f:
nixpkgs.lib.genAttrs allSystems (system:
f {
pkgs = import nixpkgs {
inherit overlays system;
};
})
(self: super: {
cargo-llvm-cov = super.cargo-llvm-cov.overrideAttrs(oa: {
doCheck = false; doInstallCheck = false;
});
})
];
# Systems supported
allSystems = [
"x86_64-linux" # 64-bit Intel/AMD Linux
"aarch64-linux" # 64-bit ARM Linux
"x86_64-darwin" # 64-bit Intel macOS
"aarch64-darwin" # 64-bit ARM macOS
];
# Helper to provide system-specific attributes
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
pkgs = import nixpkgs { inherit overlays system; config.allowBroken = true; };
});
in
{
# Development environment output
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
# The Nix packages provided in the environment
packages = (with pkgs; [
# The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt,
# rustdoc, rustfmt, and other tools.
system = system;
});
in {
devShells = forAllSystems ({pkgs, ...}: {
default = pkgs.mkShell {
packages =
(with pkgs; [
rustToolchain
cargo-llvm-cov
cargo-nextest
just
postgresql.lib
openssl
pkg-config
nodejs_22
yarn
electron
playwright-driver.browsers
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
wasm-pack
python3Full
])
++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
libiconv
darwin.apple_sdk.frameworks.Security
]);
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
NODE_ENV = "development";
};
});
};
TARGET_CC = "${pkgs.stdenv.cc}/bin/${pkgs.stdenv.cc.targetPrefix}cc";
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = "${pkgs.electron}/bin/";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";
PLAYWRIGHT_BROWSERS_PATH = "${pkgs.playwright-driver.browsers}";
NODE_ENV = "development";
};
});
packages = forAllSystems ({
pkgs,
system,
}: let
naersk-lib = pkgs.callPackage naersk {
cargo = pkgs.rustToolchain;
rustc = pkgs.rustToolchain;
};
in {
kcl-language-server = naersk-lib.buildPackage {
pname = "kcl-language-server";
version = "0.1.0";
release = true;
src = ./rust;
cargoBuildOptions = opt: opt ++ ["-p" "kcl-language-server"];
buildInputs = [pkgs.openssl pkgs.pkg-config];
};
default = self.packages.${system}.kcl-language-server;
});
};
}

View File

@ -389,6 +389,13 @@ export class LanguageServerPlugin implements PluginValue {
}
if (insertText && insertTextFormat === 2) {
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
// This is not standard from the lsp so we add it here.
if (insertText.endsWith(')')) {
// We have a function its safe to insert the ${} at the end.
insertText = insertText + '${}'
}
return snippetCompletion(insertText, completion)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

24
rust/Cargo.lock generated
View File

@ -1780,7 +1780,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"clap",
@ -1791,7 +1791,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"Inflector",
"anyhow",
@ -1810,7 +1810,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"proc-macro2",
"quote",
@ -1819,7 +1819,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.58"
version = "0.2.60"
dependencies = [
"anyhow",
"clap",
@ -1840,7 +1840,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"clap",
@ -1860,7 +1860,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.58"
version = "0.2.60"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1928,7 +1928,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.58"
version = "0.3.60"
dependencies = [
"anyhow",
"kcl-lib",
@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1956,7 +1956,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"anyhow",
"async-trait",
@ -1970,7 +1970,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.58"
version = "0.1.60"
dependencies = [
"bson",
"console_error_panic_hook",
@ -2033,9 +2033,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.110"
version = "0.2.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdfd16800a12a2eaefff53958bd871875c246e669274269f7caefc25d19641ad"
checksum = "fa1c927569925425a1b03711617c384a30cb7554394e8a6a01266910b22421de"
dependencies = [
"anyhow",
"chrono",

View File

@ -36,7 +36,7 @@ dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.7.0"
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.110", features = ["ts-rs", "websocket"] }
kittycad-modeling-cmds = { version = "0.2.113", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"
pyo3 = { version = "0.24.0" }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.58"
version = "0.1.60"
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.58"
version = "0.1.60"
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.58"
version = "0.1.60"
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.58"
version = "0.1.60"
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.58"
version = "0.2.60"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -9,7 +9,10 @@ export async function createClient(
serverOptions: lc.ServerOptions
): Promise<lc.LanguageClient> {
const clientOptions: lc.LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'kcl' }],
documentSelector: [
{ scheme: 'file', language: 'kcl' },
{ scheme: 'untitled', language: 'kcl' },
],
initializationOptions,
traceOutputChannel,
outputChannel,

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.58"
version = "0.2.60"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -103,7 +103,7 @@ tokio-tungstenite = { version = "0.24.0", features = [
tower-lsp = { workspace = true, features = ["proposed", "default"] }
[features]
default = ["engine"]
default = ["cli", "engine"]
cli = ["dep:clap", "kittycad/clap"]
dhat-heap = ["dep:dhat"]
# For the lsp server, when run with stdout for rpc we want to disable println.

View File

@ -444,12 +444,11 @@ impl FnData {
}
}
#[allow(clippy::literal_string_with_formatting_args)]
pub(super) fn to_autocomplete_snippet(&self) -> String {
if self.name == "loft" {
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
return "loft([${0:sketch000}, ${1:sketch001}])".to_owned();
} else if self.name == "hole" {
return "hole(${0:holeSketch}, ${1:%})${}".to_owned();
return "hole(${0:holeSketch}, ${1:%})".to_owned();
}
let mut args = Vec::new();
let mut index = 0;
@ -459,9 +458,7 @@ impl FnData {
args.push(arg_str);
}
}
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
format!("{}({})${{}}", self.preferred_name, args.join(", "))
format!("{}({})", self.preferred_name, args.join(", "))
}
fn to_signature_help(&self) -> SignatureHelp {

View File

@ -498,12 +498,17 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
})
}
#[allow(clippy::literal_string_with_formatting_args)]
fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
} else if self.name() == "union" {
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "subtract" {
return Ok("subtract([${0:extrude001}], tools = [${1:extrude002}])".to_string());
} else if self.name() == "intersect" {
return Ok("intersect([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "hole" {
return Ok("hole(${0:holeSketch}, ${1:%})${}".to_string());
return Ok("hole(${0:holeSketch}, ${1:%})".to_string());
}
let in_keyword_fn = self.keyword_arguments();
let mut args = Vec::new();
@ -514,9 +519,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
args.push(arg_str);
}
}
// We end with ${} so you can jump to the end of the snippet.
// After the last argument.
Ok(format!("{}({})${{}}", self.name(), args.join(", ")))
Ok(format!("{}({})", self.name(), args.join(", ")))
}
fn to_signature_help(&self) -> SignatureHelp {
@ -890,29 +893,26 @@ mod tests {
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_line() {
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
let snippet = line_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])${}"#);
assert_eq!(snippet, r#"line(${0:%}, end = [${1:3.14}, ${2:3.14}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_extrude() {
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})${}"#);
assert_eq!(snippet, r#"extrude(${0:%}, length = ${1:3.14})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_fillet() {
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])${}"#
r#"fillet(${0:%}, radius = ${1:3.14}, tags = [${2:"tag_or_edge_fn"}])"#
);
}
@ -920,18 +920,17 @@ mod tests {
fn get_autocomplete_snippet_start_sketch_on() {
let start_sketch_on_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::StartSketchOn);
let snippet = start_sketch_on_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})${}"#);
assert_eq!(snippet, r#"startSketchOn(${0:"XY"})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_circular_3d() {
// We test this one specifically because it has ints and floats and strings.
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})${}"#
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})"#
);
}
@ -942,11 +941,10 @@ mod tests {
panic!();
};
let snippet = revolve_fn.to_autocomplete_snippet();
assert_eq!(snippet, r#"revolve(axis = ${0:X})${}"#);
assert_eq!(snippet, r#"revolve(axis = ${0:X})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_circle() {
let data = kcl_doc::walk_prelude();
let DocData::Fn(circle_fn) = data.into_iter().find(|d| d.name() == "circle").unwrap() else {
@ -955,12 +953,11 @@ mod tests {
let snippet = circle_fn.to_autocomplete_snippet();
assert_eq!(
snippet,
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})${}"#
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_arc() {
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
@ -970,7 +967,7 @@ mod tests {
angleStart = ${0:3.14},
angleEnd = ${1:3.14},
radius = ${2:3.14},
}, ${3:%})${}"#
}, ${3:%})"#
);
}
@ -978,17 +975,16 @@ mod tests {
fn get_autocomplete_snippet_map() {
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
let snippet = map_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"map(${0:[0..9]})${}"#);
assert_eq!(snippet, r#"map(${0:[0..9]})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_pattern_linear_2d() {
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])${}"#
r#"patternLinear2d(${0:%}, instances = ${1:10}, distance = ${2:3.14}, axis = [${3:3.14}, ${4:3.14}])"#
);
}
@ -998,36 +994,32 @@ mod tests {
let snippet = appearance_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})${}"#
r#"appearance(${0:%}, color = ${1:"#.to_owned() + "\"#" + r#"ff0000"})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_loft() {
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
let snippet = loft_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])${}"#);
assert_eq!(snippet, r#"loft([${0:sketch000}, ${1:sketch001}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_sweep() {
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})${}"#);
assert_eq!(snippet, r#"sweep(${0:%}, path = ${1:sketch000})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_hole() {
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
let snippet = hole_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})${}"#);
assert_eq!(snippet, r#"hole(${0:holeSketch}, ${1:%})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_helix() {
let data = kcl_doc::walk_prelude();
let DocData::Fn(helix_fn) = data.into_iter().find(|d| d.name() == "helix").unwrap() else {
@ -1036,36 +1028,32 @@ mod tests {
let snippet = helix_fn.to_autocomplete_snippet();
assert_eq!(
snippet,
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})${}"#
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_union() {
let union_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Union);
let snippet = union_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"union(${0:%})${}"#);
assert_eq!(snippet, r#"union([${0:extrude001}, ${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_subtract() {
let subtract_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Subtract);
let snippet = subtract_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"subtract(${0:%}, tools = ${1:%})${}"#);
assert_eq!(snippet, r#"subtract([${0:extrude001}], tools = [${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_intersect() {
let intersect_fn: Box<dyn StdLibFn> = Box::new(crate::std::csg::Intersect);
let snippet = intersect_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"intersect(${0:%})${}"#);
assert_eq!(snippet, r#"intersect([${0:extrude001}, ${1:extrude002}])"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_get_common_edge() {
let get_common_edge_fn: Box<dyn StdLibFn> = Box::new(crate::std::edge::GetCommonEdge);
let snippet = get_common_edge_fn.to_autocomplete_snippet().unwrap();
@ -1073,40 +1061,34 @@ mod tests {
snippet,
r#"getCommonEdge(faces = [{
value = ${0:"string"},
}])${}"#
}])"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_scale() {
let scale_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Scale);
let snippet = scale_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
);
assert_eq!(snippet, r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_translate() {
let translate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Translate);
let snippet = translate_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})"#
);
}
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_rotate() {
let rotate_fn: Box<dyn StdLibFn> = Box::new(crate::std::transform::Rotate);
let snippet = rotate_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})${}"#
r#"rotate(${0:%}, roll = ${1:3.14}, pitch = ${2:3.14}, yaw = ${3:3.14})"#
);
}

View File

@ -20,7 +20,6 @@ pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) const NO_PRELUDE: &str = "no_std";
pub(super) const IMPORT_FORMAT: &str = "format";
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
pub(super) const IMPORT_COORDS: &str = "coords";
pub(super) const IMPORT_COORDS_VALUES: [(&str, &System); 3] =
[("zoo", KITTYCAD), ("opengl", OPENGL), ("vulkan", VULKAN)];

View File

@ -115,6 +115,30 @@ impl CodeRef {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub struct CompositeSolid {
pub id: ArtifactId,
pub sub_type: CompositeSolidSubType,
/// Constituent solids of the composite solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub solid_ids: Vec<ArtifactId>,
/// Tool solids used for asymmetric operations like subtract.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_ids: Vec<ArtifactId>,
pub code_ref: CodeRef,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
pub enum CompositeSolidSubType {
Intersect,
Subtract,
Union,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
#[ts(export_to = "Artifact.ts")]
#[serde(rename_all = "camelCase")]
@ -318,6 +342,7 @@ pub struct Helix {
#[ts(export_to = "Artifact.ts")]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Artifact {
CompositeSolid(CompositeSolid),
Plane(Plane),
Path(Path),
Segment(Segment),
@ -336,6 +361,7 @@ pub enum Artifact {
impl Artifact {
pub(crate) fn id(&self) -> ArtifactId {
match self {
Artifact::CompositeSolid(a) => a.id,
Artifact::Plane(a) => a.id,
Artifact::Path(a) => a.id,
Artifact::Segment(a) => a.id,
@ -355,6 +381,7 @@ impl Artifact {
#[expect(dead_code)]
pub(crate) fn code_ref(&self) -> Option<&CodeRef> {
match self {
Artifact::CompositeSolid(a) => Some(&a.code_ref),
Artifact::Plane(a) => Some(&a.code_ref),
Artifact::Path(a) => Some(&a.code_ref),
Artifact::Segment(a) => Some(&a.code_ref),
@ -375,6 +402,7 @@ impl Artifact {
/// type, return the new artifact which should be used as a replacement.
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
match self {
Artifact::CompositeSolid(a) => a.merge(new),
Artifact::Plane(a) => a.merge(new),
Artifact::Path(a) => a.merge(new),
Artifact::Segment(a) => a.merge(new),
@ -392,6 +420,18 @@ impl Artifact {
}
}
impl CompositeSolid {
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
let Artifact::CompositeSolid(new) = new else {
return Some(new);
};
merge_ids(&mut self.solid_ids, new.solid_ids);
merge_ids(&mut self.tool_ids, new.tool_ids);
None
}
}
impl Plane {
fn merge(&mut self, new: Artifact) -> Option<Artifact> {
let Artifact::Plane(new) = new else {
@ -1047,6 +1087,85 @@ fn artifacts_to_update(
// the helix here, but it's not useful right now.
return Ok(return_arr);
}
ModelingCmd::BooleanIntersection(_) | ModelingCmd::BooleanSubtract(_) | ModelingCmd::BooleanUnion(_) => {
let (sub_type, solid_ids, tool_ids) = match cmd {
ModelingCmd::BooleanIntersection(intersection) => {
let solid_ids = intersection
.solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
(CompositeSolidSubType::Intersect, solid_ids, Vec::new())
}
ModelingCmd::BooleanSubtract(subtract) => {
let solid_ids = subtract
.target_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
let tool_ids = subtract
.tool_ids
.iter()
.copied()
.map(ArtifactId::new)
.collect::<Vec<_>>();
(CompositeSolidSubType::Subtract, solid_ids, tool_ids)
}
ModelingCmd::BooleanUnion(union) => {
let solid_ids = union.solid_ids.iter().copied().map(ArtifactId::new).collect::<Vec<_>>();
(CompositeSolidSubType::Union, solid_ids, Vec::new())
}
_ => unreachable!(),
};
let mut new_solid_ids = vec![id];
match response {
OkModelingCmdResponse::BooleanIntersection(intersection) => intersection
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanSubtract(subtract) => subtract
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
OkModelingCmdResponse::BooleanUnion(union) => union
.extra_solid_ids
.iter()
.copied()
.map(ArtifactId::new)
.for_each(|id| new_solid_ids.push(id)),
_ => {}
}
let return_arr = new_solid_ids
.into_iter()
// Extra solid IDs may include the command's ID. Make sure we
// don't create a duplicate.
.filter(|solid_id| *solid_id != id)
.map(|solid_id| {
Artifact::CompositeSolid(CompositeSolid {
id: solid_id,
sub_type,
solid_ids: solid_ids.clone(),
tool_ids: tool_ids.clone(),
code_ref: CodeRef {
range,
path_to_node: path_to_node.clone(),
},
})
})
.collect::<Vec<_>>();
// TODO: Should we add the reverse graph edges?
return Ok(return_arr);
}
_ => {}
}

View File

@ -67,6 +67,11 @@ impl Artifact {
/// the graph. This should be disjoint with `child_ids`.
pub(crate) fn back_edges(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(a) => {
let mut ids = a.solid_ids.clone();
ids.extend(a.tool_ids.iter());
ids
}
Artifact::Plane(_) => Vec::new(),
Artifact::Path(a) => vec![a.plane_id],
Artifact::Segment(a) => vec![a.path_id],
@ -87,6 +92,11 @@ impl Artifact {
/// the graph.
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
match self {
Artifact::CompositeSolid(_) => {
// Note: Don't include these since they're parents: solid_ids,
// tool_ids.
Vec::new()
}
Artifact::Plane(a) => a.path_ids.clone(),
Artifact::Path(a) => {
// Note: Don't include these since they're parents: plane_id.
@ -213,6 +223,7 @@ impl ArtifactGraph {
let id = artifact.id();
let grouped = match artifact {
Artifact::CompositeSolid(_) => false,
Artifact::Plane(_) => false,
Artifact::Path(_) => {
groups.entry(id).or_insert_with(Vec::new).push(id);
@ -278,6 +289,15 @@ impl ArtifactGraph {
}
match artifact {
Artifact::CompositeSolid(composite_solid) => {
writeln!(
output,
"{prefix}{}[\"CompositeSolid {:?}<br>{:?}\"]",
id,
composite_solid.sub_type,
code_ref_display(&composite_solid.code_ref)
)?;
}
Artifact::Plane(plane) => {
writeln!(
output,

View File

@ -939,25 +939,40 @@ impl Node<BinaryExpression> {
if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_union(vec![*left.clone(), *right.clone()], exec_state, args).await?;
let result = crate::std::csg::inner_union(
vec![*left.clone(), *right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::Sub {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_subtract(vec![*left.clone()], vec![*right.clone()], exec_state, args)
.await?;
let result = crate::std::csg::inner_subtract(
vec![*left.clone()],
vec![*right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::And {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_intersect(vec![*left.clone(), *right.clone()], exec_state, args).await?;
let result = crate::std::csg::inner_intersect(
vec![*left.clone(), *right.clone()],
Default::default(),
exec_state,
args,
)
.await?;
return Ok(result.into());
}
}

View File

@ -173,7 +173,7 @@ pub(super) fn format_from_annotations(
KclError::Semantic(KclErrorDetails {
message: format!(
"Unknown format for import, expected one of: {}",
annotations::IMPORT_FORMAT_VALUES.join(", ")
crate::IMPORT_FILE_EXTENSIONS.join(", ")
),
source_ranges: vec![p.as_source_range()],
})

View File

@ -11,9 +11,7 @@ pub use cache::{bust_cache, clear_mem_cache};
pub use cad_op::Operation;
pub use geometry::*;
pub use id_generator::IdGenerator;
pub(crate) use import::{
import_foreign, send_to_engine as send_import_to_engine, PreImportedGeometry, ZOO_COORD_SYSTEM,
};
pub(crate) use import::PreImportedGeometry;
use indexmap::IndexMap;
pub use kcl_value::{KclObjectFields, KclValue};
use kcmc::{

View File

@ -131,11 +131,36 @@ pub mod pretty {
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
}
#[cfg(feature = "cli")]
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(flatten)]

View File

@ -3418,3 +3418,148 @@ async fn kcl_test_kcl_lsp_multi_file_error() {
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_on_hover_untitled_file_scheme() {
let server = kcl_lsp_server(true).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "untitled:Untitled-1".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"startSketchOn(XY)
foo = 42
foo
fn bar(x: string): string {
return x
}
bar("an arg")
startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [0, 10])
"#
.to_string(),
},
})
.await;
// Std lib call
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("startSketchOn"));
assert!(value.contains(": SketchSurface"));
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
}
_ => unreachable!(),
}
// Variable use
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 2, character: 1 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("foo: number = 42"));
}
_ => unreachable!(),
}
// User-defined function call.
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 8, character: 1 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("bar(x: string): string"));
}
_ => unreachable!(),
}
// Variable inside a function
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 5, character: 9 },
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("x: string"));
}
_ => unreachable!(),
}
// std function KwArg
let hover = server
.hover(tower_lsp::lsp_types::HoverParams {
text_document_position_params: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "untitled:Untitled-1".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position {
line: 12,
character: 11,
},
},
work_done_progress_params: Default::default(),
})
.await
.unwrap();
match hover.unwrap().contents {
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
assert!(value.contains("end?: [number]"));
assert!(value.contains("How far away (along the X and Y axes) should this line go?"));
}
_ => unreachable!(),
}
server.executor_ctx().await.clone().unwrap().close().await;
}

View File

@ -35,7 +35,7 @@ use crate::{
token::{Token, TokenSlice, TokenType},
PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
},
SourceRange,
SourceRange, IMPORT_FILE_EXTENSIONS,
};
thread_local! {
@ -1803,11 +1803,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
end = alias.end;
*selector_alias = Some(alias);
}
ParseContext::warn(CompilationError::err(
SourceRange::new(start, path.end, path.module_id),
"Importing a whole module is experimental, likely to be buggy, and likely to change",
));
}
let path_string = match path.inner.value {
@ -1843,8 +1838,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
))
}
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
/// Validates the path string in an `import` statement.
///
/// `var_name` is `true` if the path will be used as a variable name.
@ -1909,12 +1902,11 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
ImportPath::Std { path: segments }
} else if path_string.contains('.') {
// TODO should allow other extensions if there is a format attribute.
let extn = &path_string[path_string.rfind('.').unwrap() + 1..];
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
let extn = std::path::Path::new(&path_string).extension().unwrap_or_default();
if !IMPORT_FILE_EXTENSIONS.contains(&extn.to_string_lossy().to_string()) {
ParseContext::warn(CompilationError::err(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
))
}
ImportPath::Foreign { path: path_string }
@ -1922,7 +1914,7 @@ fn validate_path_string(path_string: String, var_name: bool, path_range: SourceR
return Err(ErrMode::Cut(
CompilationError::fatal(
path_range,
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", IMPORT_FILE_EXTENSIONS.join(", ")),
)
.into(),
));
@ -4498,21 +4490,9 @@ export fn cos(num: number(rad)): number(_) {}"#;
#[test]
fn warn_import() {
let some_program_string = r#"import "foo.kcl""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.obj""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.sldprt""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 1, "{errs:#?}");
let some_program_string = r#"import "foo.bad""#;
let (_, errs) = assert_no_err(some_program_string);
assert_eq!(errs.len(), 2, "{errs:#?}");
assert_eq!(errs.len(), 1, "{errs:#?}");
}
#[test]

View File

@ -664,10 +664,6 @@ impl Args {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) fn get_sketch_data_and_optional_tag(
&self,
) -> Result<(super::sketch::SketchData, Option<FaceTag>), KclError> {
@ -1077,35 +1073,6 @@ macro_rules! let_field_of {
};
}
impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, typ "format");
match typ {
"fbx" => Some(Self::Fbx {}),
"gltf" => Some(Self::Gltf {}),
"sldprt" => Some(Self::Sldprt {}),
"step" => Some(Self::Step {}),
"stl" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Stl { coords, units })
}
"obj" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Obj { coords, units })
}
"ply" => {
let_field_of!(obj, coords?);
let_field_of!(obj, units);
Some(Self::Ply { coords, units })
}
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;

View File

@ -2,6 +2,13 @@
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
use kittycad_modeling_cmds::{
self as kcmc,
ok_response::OkModelingCmdResponse,
output::{BooleanIntersection, BooleanSubtract, BooleanUnion},
websocket::OkWebSocketResponseData,
};
use crate::{
errors::{KclError, KclErrorDetails},
@ -9,10 +16,13 @@ use crate::{
std::Args,
};
use super::DEFAULT_TOLERANCE;
/// Union two or more solids into a single solid.
pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids: Vec<Solid> =
args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::Union(vec![RuntimeType::solids()]), exec_state)?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
@ -21,7 +31,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
}));
}
let solids = inner_union(solids, exec_state, args).await?;
let solids = inner_union(solids, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -30,18 +40,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// ```no_run
/// // Union two cubes using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// unionedPart = union([part001, part002])
/// ```
@ -51,18 +62,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// unionedPart = part001 + part002
@ -73,18 +85,19 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([20, 10])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: union([part001, part002])
/// // Programmers will understand `|` as a union operation, but mechanical engineers
@ -96,31 +109,64 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to union."},
tolerance = {docs = "The tolerance to use for the union operation."},
}
}]
pub(crate) async fn inner_union(
solids: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids.
args.flush_batch_for_solids(exec_state, &solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanUnion {
solid_ids: solids.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the union operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}
/// Intersect returns the shared volume between multiple solids, preserving only
/// overlapping regions.
pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
@ -129,7 +175,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
}));
}
let solids = inner_intersect(solids, exec_state, args).await?;
let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -144,18 +190,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// ```no_run
/// // Intersect two cubes using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([8, 8])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// intersectedPart = intersect([part001, part002])
/// ```
@ -165,18 +212,19 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = cube([8, 8])
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: intersect([part001, part002])
/// intersectedPart = part001 & part002
@ -186,25 +234,57 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to intersect."},
tolerance = {docs = "The tolerance to use for the intersection operation."},
}
}]
pub(crate) async fn inner_intersect(
solids: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids.
args.flush_batch_for_solids(exec_state, &solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanIntersection {
solid_ids: solids.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the intersection operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}
/// Subtract removes tool solids from base solids, leaving the remaining material.
@ -212,7 +292,23 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
let solids: Vec<Solid> = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
let tools: Vec<Solid> = args.get_kw_arg_typed("tools", &RuntimeType::solids(), exec_state)?;
let solids = inner_subtract(solids, tools, exec_state, args).await?;
if solids.len() > 1 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "Only one solid is allowed for a subtract operation, currently.".to_string(),
source_ranges: vec![args.source_range],
}));
}
if tools.len() > 1 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "Only one tool is allowed for a subtract operation, currently.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let tolerance = args.get_kw_arg_opt("tolerance")?;
let solids = inner_subtract(solids, tools, tolerance, exec_state, args).await?;
Ok(solids.into())
}
@ -227,20 +323,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
/// ```no_run
/// // Subtract a cylinder from a cube using the stdlib functions.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = startSketchOn('XY')
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 10)
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// subtractedPart = subtract([part001], tools=[part002])
/// ```
@ -250,20 +345,19 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
/// // NOTE: This will not work when using codemods through the UI.
/// // Codemods will generate the stdlib function call instead.
///
/// fn cube(center) {
/// fn cube(center, size) {
/// return startSketchOn('XY')
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
/// |> line(endAbsolute = [center[0] - 10, center[1] + 10])
/// |> startProfileAt([center[0] - size, center[1] - size], %)
/// |> line(endAbsolute = [center[0] + size, center[1] - size])
/// |> line(endAbsolute = [center[0] + size, center[1] + size])
/// |> line(endAbsolute = [center[0] - size, center[1] + size])
/// |> close()
/// |> extrude(length = 10)
/// }
///
/// part001 = cube([0, 0])
/// part002 = startSketchOn('XY')
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 10)
/// part001 = cube([0, 0], 10)
/// part002 = cube([7, 3], 5)
/// |> translate(z = 1)
///
/// // This is the equivalent of: subtract([part001], tools=[part002])
/// subtractedPart = part001 - part002
@ -273,26 +367,59 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
deprecated = true,
args = {
solids = {docs = "The solids to use as the base to subtract from."},
tools = {docs = "The solids to subtract."},
tolerance = {docs = "The tolerance to use for the subtraction operation."},
}
}]
pub(crate) async fn inner_subtract(
solids: Vec<Solid>,
tools: Vec<Solid>,
tolerance: Option<f64>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Vec<Solid>, KclError> {
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
return Ok(new_solids);
}
// Flush the fillets for the solids and the tools.
let combined_solids = solids.iter().chain(tools.iter()).cloned().collect::<Vec<Solid>>();
args.flush_batch_for_solids(exec_state, &combined_solids).await?;
// TODO: call the engine union operation.
// TODO: figure out all the shit after for the faces etc.
let result = args
.send_modeling_cmd(
solid_out_id,
ModelingCmd::from(mcmd::BooleanSubtract {
target_ids: solids.iter().map(|s| s.id).collect(),
tool_ids: tools.iter().map(|s| s.id).collect(),
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
}),
)
.await?;
// For now just return the first solid.
// Til we have a proper implementation.
Ok(vec![solids[0].clone()])
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the subtract operation.".to_string(),
source_ranges: vec![args.source_range],
}));
};
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
new_solids.push(solid.clone());
}
Ok(new_solids)
}

View File

@ -1,181 +0,0 @@
//! Standard library functions involved in importing files.
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{coord::System, format::InputFormat3d, units::UnitLength};
use kittycad_modeling_cmds as kcmc;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{import_foreign, send_import_to_engine, ExecState, ImportedGeometry, KclValue, ZOO_COORD_SYSTEM},
std::Args,
};
/// Import format specifier
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
#[serde(tag = "format")]
pub enum ImportFormat {
/// Autodesk Filmbox (FBX) format
#[serde(rename = "fbx")]
Fbx {},
/// Binary glTF 2.0. We refer to this as glTF since that is how our customers refer to
/// it, but this can also import binary glTF (glb).
#[serde(rename = "gltf")]
Gltf {},
/// Wavefront OBJ format.
#[serde(rename = "obj")]
Obj {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
/// The PLY Polygon File Format.
#[serde(rename = "ply")]
Ply {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
/// SolidWorks part (SLDPRT) format.
#[serde(rename = "sldprt")]
Sldprt {},
/// ISO 10303-21 (STEP) format.
#[serde(rename = "step")]
Step {},
/// *ST**ereo**L**ithography format.
#[serde(rename = "stl")]
Stl {
/// Co-ordinate system of input data.
/// Defaults to the [KittyCAD co-ordinate system.
coords: Option<System>,
/// The units of the input data. This is very important for correct scaling and when
/// calculating physics properties like mass, etc.
/// Defaults to millimeters.
units: UnitLength,
},
}
impl From<ImportFormat> for InputFormat3d {
fn from(format: ImportFormat) -> Self {
match format {
ImportFormat::Fbx {} => InputFormat3d::Fbx(Default::default()),
ImportFormat::Gltf {} => InputFormat3d::Gltf(Default::default()),
ImportFormat::Obj { coords, units } => InputFormat3d::Obj(kcmc::format::obj::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
ImportFormat::Ply { coords, units } => InputFormat3d::Ply(kcmc::format::ply::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
ImportFormat::Sldprt {} => InputFormat3d::Sldprt(kcmc::format::sldprt::import::Options {
split_closed_faces: false,
}),
ImportFormat::Step {} => InputFormat3d::Step(kcmc::format::step::import::Options {
split_closed_faces: false,
}),
ImportFormat::Stl { coords, units } => InputFormat3d::Stl(kcmc::format::stl::import::Options {
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
units,
}),
}
}
}
/// Import a CAD file.
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
/// Otherwise you can specify the unit by passing in the options parameter.
/// If you import a gltf file, we will try to find the bin file and import it as well.
///
/// Import paths are relative to the current project directory. This only works in the desktop app
/// not in browser.
pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
let imported_geometry = inner_import(file_path, options, exec_state, args).await?;
Ok(KclValue::ImportedGeometry(imported_geometry))
}
/// Import a CAD file.
///
/// **DEPRECATED** Prefer to use import statements.
///
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
/// unit of measurement is millimeters. Alternatively you may specify the unit
/// by passing your desired measurement unit in the options parameter. When
/// importing a GLTF file, the bin file will be imported as well. Import paths
/// are relative to the current project directory.
///
/// Note: The import command currently only works when using the native
/// Design Studio.
///
/// ```no_run
/// model = import("tests/inputs/cube.obj")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.gltf")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.sldprt")
/// ```
///
/// ```no_run
/// model = import("tests/inputs/cube.step")
/// ```
///
/// ```no_run
/// import height, buildSketch from 'common.kcl'
///
/// plane = 'XZ'
/// margin = 2
/// s1 = buildSketch(plane, [0, 0])
/// s2 = buildSketch(plane, [0, height() + margin])
/// ```
#[stdlib {
name = "import",
feature_tree_operation = true,
deprecated = true,
tags = [],
}]
async fn inner_import(
file_path: String,
options: Option<ImportFormat>,
exec_state: &mut ExecState,
args: Args,
) -> Result<ImportedGeometry, KclError> {
if file_path.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "No file path was provided.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let format = options.map(InputFormat3d::from);
send_import_to_engine(
import_foreign(
std::path::Path::new(&file_path),
format,
exec_state,
&args.ctx,
args.source_range,
)
.await?,
&args.ctx,
)
.await
}

View File

@ -12,7 +12,6 @@ pub mod edge;
pub mod extrude;
pub mod fillet;
pub mod helix;
pub mod import;
pub mod loft;
pub mod math;
pub mod mirror;
@ -111,7 +110,6 @@ lazy_static! {
Box::new(crate::std::sweep::Sweep),
Box::new(crate::std::loft::Loft),
Box::new(crate::std::planes::OffsetPlane),
Box::new(crate::std::import::Import),
Box::new(crate::std::math::Acos),
Box::new(crate::std::math::Asin),
Box::new(crate::std::math::Atan),

View File

@ -1,8 +1,5 @@
use std::fmt::Write;
#[cfg(feature = "cli")]
use clap::ValueEnum;
use crate::parsing::{
ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
@ -195,7 +192,7 @@ impl Node<Annotation> {
result.push_str(&indentation);
result.push_str(comment);
}
if !comment.ends_with("*/") && !result.ends_with("\n\n") && result != "\n" {
if !result.ends_with("\n\n") && result != "\n" {
result.push('\n');
}
}
@ -867,29 +864,6 @@ impl Parameter {
}
}
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
/// Collect all the kcl (and other relevant) files in a directory, recursively.
#[cfg(not(target_arch = "wasm32"))]
#[async_recursion::async_recursion]
@ -909,7 +883,7 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
files.extend(walk_dir(&path).await?);
} else if path
.extension()
.is_some_and(|ext| RELEVANT_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
.is_some_and(|ext| crate::RELEVANT_FILE_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
{
files.push(path);
}
@ -1048,6 +1022,20 @@ bar = 0
assert_eq!(output, input);
}
#[test]
fn recast_annotations_with_block_comment() {
let input = r#"/* Start comment
sdfsdfsdfs */
@settings(defaultLengthUnit = in)
foo = 42
"#;
let program = crate::parsing::top_level_parse(input).unwrap();
let output = program.recast(&Default::default(), 0);
assert_eq!(output, input);
}
#[test]
fn test_recast_if_else_if_same() {
let input = r#"b = if false {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -178,7 +178,7 @@ description: Artifact commands intersect_cubes.kcl
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 10.0,
"distance": 20.0,
"faces": null,
"opposite": "None"
}
@ -342,7 +342,7 @@ description: Artifact commands intersect_cubes.kcl
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": -2.0,
"x": 2.0,
"y": -2.0,
"z": 0.0
}
@ -364,7 +364,7 @@ description: Artifact commands intersect_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": 18.0,
"x": 12.0,
"y": -2.0,
"z": 0.0
},
@ -381,8 +381,8 @@ description: Artifact commands intersect_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": 18.0,
"y": 18.0,
"x": 12.0,
"y": 8.0,
"z": 0.0
},
"relative": false
@ -398,8 +398,8 @@ description: Artifact commands intersect_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": -2.0,
"y": 18.0,
"x": 2.0,
"y": 8.0,
"z": 0.0
},
"relative": false
@ -544,5 +544,41 @@ description: Artifact commands intersect_cubes.kcl
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_object_transform",
"object_id": "[uuid]",
"transforms": [
{
"translate": {
"property": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"set": false,
"is_local": true
},
"rotate_rpy": null,
"rotate_angle_axis": null,
"scale": null
}
]
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "boolean_intersection",
"solid_ids": [
"[uuid]",
"[uuid]"
],
"tolerance": 0.0000001
}
}
]

View File

@ -1,23 +1,23 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[52, 103, 0]"]
3["Segment<br>[111, 163, 0]"]
4["Segment<br>[171, 223, 0]"]
5["Segment<br>[231, 283, 0]"]
6["Segment<br>[291, 298, 0]"]
2["Path<br>[58, 113, 0]"]
3["Segment<br>[121, 177, 0]"]
4["Segment<br>[185, 241, 0]"]
5["Segment<br>[249, 305, 0]"]
6["Segment<br>[313, 320, 0]"]
7[Solid2d]
end
subgraph path24 [Path]
24["Path<br>[52, 103, 0]"]
25["Segment<br>[111, 163, 0]"]
26["Segment<br>[171, 223, 0]"]
27["Segment<br>[231, 283, 0]"]
28["Segment<br>[291, 298, 0]"]
24["Path<br>[58, 113, 0]"]
25["Segment<br>[121, 177, 0]"]
26["Segment<br>[185, 241, 0]"]
27["Segment<br>[249, 305, 0]"]
28["Segment<br>[313, 320, 0]"]
29[Solid2d]
end
1["Plane<br>[27, 44, 0]"]
8["Sweep Extrusion<br>[306, 326, 0]"]
1["Plane<br>[33, 50, 0]"]
8["Sweep Extrusion<br>[328, 354, 0]"]
9[Wall]
10[Wall]
11[Wall]
@ -32,8 +32,8 @@ flowchart LR
20["SweepEdge Adjacent"]
21["SweepEdge Opposite"]
22["SweepEdge Adjacent"]
23["Plane<br>[27, 44, 0]"]
30["Sweep Extrusion<br>[306, 326, 0]"]
23["Plane<br>[33, 50, 0]"]
30["Sweep Extrusion<br>[328, 354, 0]"]
31[Wall]
32[Wall]
33[Wall]
@ -48,6 +48,7 @@ flowchart LR
42["SweepEdge Adjacent"]
43["SweepEdge Opposite"]
44["SweepEdge Adjacent"]
45["CompositeSolid Intersect<br>[448, 477, 0]"]
1 --- 2
2 --- 3
2 --- 4
@ -114,4 +115,6 @@ flowchart LR
30 --- 42
30 --- 43
30 --- 44
2 <--x 45
24 <--x 45
```

View File

@ -101,16 +101,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -149,16 +153,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -246,16 +254,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -294,16 +306,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -386,16 +402,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -434,16 +454,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -526,16 +550,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -574,16 +602,20 @@ description: Result of parsing intersect_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -656,14 +688,38 @@ description: Result of parsing intersect_cubes.kcl
"arg": {
"commentStart": 0,
"end": 0,
"raw": "10",
"left": {
"commentStart": 0,
"end": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
"operator": "*",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "BinaryExpression",
"type": "BinaryExpression"
}
}
],
@ -719,6 +775,16 @@ description: Result of parsing intersect_cubes.kcl
"start": 0,
"type": "Identifier"
}
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
}
}
],
"start": 0,
@ -780,6 +846,18 @@ description: Result of parsing intersect_cubes.kcl
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
],
"callee": {
@ -825,61 +903,129 @@ description: Result of parsing intersect_cubes.kcl
"type": "Identifier"
},
"init": {
"arguments": [
"body": [
{
"commentStart": 0,
"elements": [
"arguments": [
{
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "7",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 7.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "3",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 3.0,
"suffix": "None"
}
}
],
"end": 0,
"raw": "8",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 8.0,
"suffix": "None"
}
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"commentStart": 0,
"end": 0,
"raw": "8",
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 8.0,
"value": 5.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
"type": "CallExpression",
"type": "CallExpression"
},
"path": [],
"start": 0,
"type": "Name"
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "z",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "translate",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpression",
"type": "CallExpression"
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "VariableDeclarator"

View File

@ -1,14 +1,15 @@
fn cube(center) {
fn cube(center, size) {
return startSketchOn(XY)
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> startProfileAt([center[0] - size, center[1] - size], %)
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
|> extrude(length = 2 * size)
}
part001 = cube([0, 0])
part002 = cube([8, 8])
part001 = cube([0, 0], 10)
part002 = cube([7, 3], 5)
|> translate(z = 1)
fullPart = intersect([part001, part002])

View File

@ -10,7 +10,7 @@ description: Operations executed intersect_cubes.kcl
"name": "cube",
"functionSourceRange": [
7,
328,
356,
0
],
"unlabeledArg": null,
@ -38,7 +38,7 @@ description: Operations executed intersect_cubes.kcl
"length": {
"value": {
"type": "Number",
"value": 10.0,
"value": 20.0,
"ty": {
"type": "Default",
"len": {
@ -75,7 +75,7 @@ description: Operations executed intersect_cubes.kcl
"name": "cube",
"functionSourceRange": [
7,
328,
356,
0
],
"unlabeledArg": null,

View File

@ -7,182 +7,365 @@ description: Variables in memory after executing intersect_cubes.kcl
"type": "Function"
},
"fullPart": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"type": "HomArray",
"value": [
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"sourceRange": []
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
}
}
]
},
"part001": {
"type": "Solid",
@ -354,7 +537,7 @@ description: Variables in memory after executing intersect_cubes.kcl
"type": "Mm"
}
},
"height": 10.0,
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
@ -408,12 +591,12 @@ description: Variables in memory after executing intersect_cubes.kcl
"sourceRange": []
},
"from": [
-2.0,
2.0,
-2.0
],
"tag": null,
"to": [
18.0,
12.0,
-2.0
],
"type": "ToPoint",
@ -427,13 +610,13 @@ description: Variables in memory after executing intersect_cubes.kcl
"sourceRange": []
},
"from": [
18.0,
12.0,
-2.0
],
"tag": null,
"to": [
18.0,
18.0
12.0,
8.0
],
"type": "ToPoint",
"units": {
@ -446,13 +629,13 @@ description: Variables in memory after executing intersect_cubes.kcl
"sourceRange": []
},
"from": [
18.0,
18.0
12.0,
8.0
],
"tag": null,
"to": [
-2.0,
18.0
2.0,
8.0
],
"type": "ToPoint",
"units": {
@ -465,12 +648,12 @@ description: Variables in memory after executing intersect_cubes.kcl
"sourceRange": []
},
"from": [
-2.0,
18.0
2.0,
8.0
],
"tag": null,
"to": [
-2.0,
2.0,
-2.0
],
"type": "ToPoint",
@ -510,11 +693,11 @@ description: Variables in memory after executing intersect_cubes.kcl
},
"start": {
"from": [
-2.0,
2.0,
-2.0
],
"to": [
-2.0,
2.0,
-2.0
],
"units": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -2,17 +2,18 @@
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing intersect_cubes.kcl
---
fn cube(center) {
fn cube(center, size) {
return startSketchOn(XY)
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> startProfileAt([center[0] - size, center[1] - size], %)
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
|> extrude(length = 2 * size)
}
part001 = cube([0, 0])
part002 = cube([8, 8])
part001 = cube([0, 0], 10)
part002 = cube([7, 3], 5)
|> translate(z = 1)
fullPart = intersect([part001, part002])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -342,8 +342,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 2.0,
"y": 0.0,
"x": 4.0,
"y": 2.0,
"z": 0.0
}
}
@ -364,8 +364,8 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"segment": {
"type": "arc",
"center": {
"x": 0.0,
"y": 0.0
"x": 2.0,
"y": 2.0
},
"radius": 2.0,
"start": {
@ -410,7 +410,7 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 10.0,
"distance": 5.0,
"faces": null,
"opposite": "None"
}
@ -458,5 +458,19 @@ description: Artifact commands subtract_cylinder_from_cube.kcl
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "boolean_subtract",
"target_ids": [
"[uuid]"
],
"tool_ids": [
"[uuid]"
],
"tolerance": 0.0000001
}
}
]

View File

@ -30,12 +30,13 @@ flowchart LR
21["SweepEdge Opposite"]
22["SweepEdge Adjacent"]
23["Plane<br>[363, 382, 0]"]
27["Sweep Extrusion<br>[429, 449, 0]"]
27["Sweep Extrusion<br>[429, 448, 0]"]
28[Wall]
29["Cap Start"]
30["Cap End"]
31["SweepEdge Opposite"]
32["SweepEdge Adjacent"]
33["CompositeSolid Subtract<br>[461, 497, 0]"]
1 --- 2
2 --- 3
2 --- 4
@ -81,4 +82,6 @@ flowchart LR
27 --- 30
27 --- 31
27 --- 32
2 <--x 33
24 <--x 33
```

View File

@ -876,24 +876,24 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
{
"commentStart": 0,
"end": 0,
"raw": "0",
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"value": 2.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"value": 2.0,
"suffix": "None"
}
}
@ -963,12 +963,12 @@ description: Result of parsing subtract_cylinder_from_cube.kcl
"arg": {
"commentStart": 0,
"end": 0,
"raw": "10",
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"value": 5.0,
"suffix": "None"
}
}

View File

@ -10,7 +10,7 @@ fn cube(center) {
part001 = cube([0, 0])
part002 = startSketchOn('XY')
|> circle(center = [0, 0], radius = 2)
|> extrude(length = 10)
|> circle(center = [2, 2], radius = 2)
|> extrude(length = 5)
fullPart = subtract([part001], tools=[part002])

View File

@ -88,7 +88,7 @@ description: Operations executed subtract_cylinder_from_cube.kcl
"length": {
"value": {
"type": "Number",
"value": 10.0,
"value": 5.0,
"ty": {
"type": "Default",
"len": {

View File

@ -7,182 +7,365 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
"type": "Function"
},
"fullPart": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"type": "HomArray",
"value": [
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"sourceRange": []
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
}
}
]
},
"part001": {
"type": "Solid",
@ -388,18 +571,18 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
},
"ccw": true,
"center": [
0.0,
0.0
2.0,
2.0
],
"from": [
2.0,
0.0
4.0,
2.0
],
"radius": 2.0,
"tag": null,
"to": [
2.0,
0.0
4.0,
2.0
],
"type": "Circle",
"units": {
@ -438,12 +621,12 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
},
"start": {
"from": [
2.0,
0.0
4.0,
2.0
],
"to": [
2.0,
0.0
4.0,
2.0
],
"units": {
"type": "Mm"
@ -460,7 +643,7 @@ description: Variables in memory after executing subtract_cylinder_from_cube.kcl
"type": "Mm"
}
},
"height": 10.0,
"height": 5.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -14,7 +14,7 @@ fn cube(center) {
part001 = cube([0, 0])
part002 = startSketchOn(XY)
|> circle(center = [0, 0], radius = 2)
|> extrude(length = 10)
|> circle(center = [2, 2], radius = 2)
|> extrude(length = 5)
fullPart = subtract([part001], tools = [part002])

View File

@ -178,7 +178,7 @@ description: Artifact commands union_cubes.kcl
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 10.0,
"distance": 20.0,
"faces": null,
"opposite": "None"
}
@ -342,8 +342,8 @@ description: Artifact commands union_cubes.kcl
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 10.0,
"y": 0.0,
"x": 2.0,
"y": -2.0,
"z": 0.0
}
}
@ -364,8 +364,8 @@ description: Artifact commands union_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": 30.0,
"y": 0.0,
"x": 12.0,
"y": -2.0,
"z": 0.0
},
"relative": false
@ -381,8 +381,8 @@ description: Artifact commands union_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": 30.0,
"y": 20.0,
"x": 12.0,
"y": 8.0,
"z": 0.0
},
"relative": false
@ -398,8 +398,8 @@ description: Artifact commands union_cubes.kcl
"segment": {
"type": "line",
"end": {
"x": 10.0,
"y": 20.0,
"x": 2.0,
"y": 8.0,
"z": 0.0
},
"relative": false
@ -544,5 +544,41 @@ description: Artifact commands union_cubes.kcl
"edge_id": "[uuid]",
"face_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "set_object_transform",
"object_id": "[uuid]",
"transforms": [
{
"translate": {
"property": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"set": false,
"is_local": true
},
"rotate_rpy": null,
"rotate_angle_axis": null,
"scale": null
}
]
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "boolean_union",
"solid_ids": [
"[uuid]",
"[uuid]"
],
"tolerance": 0.0000001
}
}
]

View File

@ -1,23 +1,23 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[52, 103, 0]"]
3["Segment<br>[111, 163, 0]"]
4["Segment<br>[171, 223, 0]"]
5["Segment<br>[231, 283, 0]"]
6["Segment<br>[291, 298, 0]"]
2["Path<br>[58, 113, 0]"]
3["Segment<br>[121, 177, 0]"]
4["Segment<br>[185, 241, 0]"]
5["Segment<br>[249, 305, 0]"]
6["Segment<br>[313, 320, 0]"]
7[Solid2d]
end
subgraph path24 [Path]
24["Path<br>[52, 103, 0]"]
25["Segment<br>[111, 163, 0]"]
26["Segment<br>[171, 223, 0]"]
27["Segment<br>[231, 283, 0]"]
28["Segment<br>[291, 298, 0]"]
24["Path<br>[58, 113, 0]"]
25["Segment<br>[121, 177, 0]"]
26["Segment<br>[185, 241, 0]"]
27["Segment<br>[249, 305, 0]"]
28["Segment<br>[313, 320, 0]"]
29[Solid2d]
end
1["Plane<br>[27, 44, 0]"]
8["Sweep Extrusion<br>[306, 326, 0]"]
1["Plane<br>[33, 50, 0]"]
8["Sweep Extrusion<br>[328, 354, 0]"]
9[Wall]
10[Wall]
11[Wall]
@ -32,8 +32,8 @@ flowchart LR
20["SweepEdge Adjacent"]
21["SweepEdge Opposite"]
22["SweepEdge Adjacent"]
23["Plane<br>[27, 44, 0]"]
30["Sweep Extrusion<br>[306, 326, 0]"]
23["Plane<br>[33, 50, 0]"]
30["Sweep Extrusion<br>[328, 354, 0]"]
31[Wall]
32[Wall]
33[Wall]
@ -48,6 +48,7 @@ flowchart LR
42["SweepEdge Adjacent"]
43["SweepEdge Opposite"]
44["SweepEdge Adjacent"]
45["CompositeSolid Union<br>[448, 473, 0]"]
1 --- 2
2 --- 3
2 --- 4
@ -114,4 +115,6 @@ flowchart LR
30 --- 42
30 --- 43
30 --- 44
2 <--x 45
24 <--x 45
```

View File

@ -101,16 +101,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -149,16 +153,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -246,16 +254,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -294,16 +306,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -386,16 +402,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -434,16 +454,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -526,16 +550,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "-",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -574,16 +602,20 @@ description: Result of parsing union_cubes.kcl
},
"operator": "+",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"raw": "10",
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "BinaryExpression",
@ -656,14 +688,38 @@ description: Result of parsing union_cubes.kcl
"arg": {
"commentStart": 0,
"end": 0,
"raw": "10",
"left": {
"commentStart": 0,
"end": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
"operator": "*",
"right": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
"type": "BinaryExpression",
"type": "BinaryExpression"
}
}
],
@ -719,6 +775,16 @@ description: Result of parsing union_cubes.kcl
"start": 0,
"type": "Identifier"
}
},
{
"type": "Parameter",
"identifier": {
"commentStart": 0,
"end": 0,
"name": "size",
"start": 0,
"type": "Identifier"
}
}
],
"start": 0,
@ -780,6 +846,18 @@ description: Result of parsing union_cubes.kcl
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
],
"callee": {
@ -825,61 +903,129 @@ description: Result of parsing union_cubes.kcl
"type": "Identifier"
},
"init": {
"arguments": [
"body": [
{
"commentStart": 0,
"elements": [
"arguments": [
{
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "7",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 7.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "3",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 3.0,
"suffix": "None"
}
}
],
"end": 0,
"raw": "20",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 20.0,
"suffix": "None"
}
"type": "ArrayExpression",
"type": "ArrayExpression"
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"value": 5.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
"type": "CallExpression",
"type": "CallExpression"
},
"path": [],
"start": 0,
"type": "Name"
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "z",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "translate",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpression",
"type": "CallExpression"
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "VariableDeclarator"

View File

@ -1,14 +1,15 @@
fn cube(center) {
fn cube(center, size) {
return startSketchOn(XY)
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> startProfileAt([center[0] - size, center[1] - size], %)
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
|> extrude(length = 2 * size)
}
part001 = cube([0, 0])
part002 = cube([20, 10])
part001 = cube([0, 0], 10)
part002 = cube([7, 3], 5)
|> translate(z = 1)
fullPart = union([part001, part002])

View File

@ -10,7 +10,7 @@ description: Operations executed union_cubes.kcl
"name": "cube",
"functionSourceRange": [
7,
328,
356,
0
],
"unlabeledArg": null,
@ -38,7 +38,7 @@ description: Operations executed union_cubes.kcl
"length": {
"value": {
"type": "Number",
"value": 10.0,
"value": 20.0,
"ty": {
"type": "Default",
"len": {
@ -75,7 +75,7 @@ description: Operations executed union_cubes.kcl
"name": "cube",
"functionSourceRange": [
7,
328,
356,
0
],
"unlabeledArg": null,

View File

@ -7,182 +7,365 @@ description: Variables in memory after executing union_cubes.kcl
"type": "Function"
},
"fullPart": {
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"type": "HomArray",
"value": [
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"sourceRange": []
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
{
"type": "Solid",
"value": {
"type": "Solid",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
},
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [],
"tag": null,
"type": "extrudePlane"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
-10.0
],
"tag": null,
"to": [
10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
-10.0
],
"tag": null,
"to": [
10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
10.0,
10.0
],
"tag": null,
"to": [
-10.0,
10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
},
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
},
"from": [
-10.0,
10.0
],
"tag": null,
"to": [
-10.0,
-10.0
],
"type": "ToPoint",
"units": {
"type": "Mm"
}
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"units": {
"type": "Mm"
}
},
"start": {
"from": [
-10.0,
-10.0
],
"to": [
-10.0,
-10.0
],
"units": {
"type": "Mm"
},
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": []
}
},
"artifactId": "[uuid]",
"originalId": "[uuid]",
"units": {
"type": "Mm"
}
},
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
"type": "Mm"
}
}
}
}
]
},
"part001": {
"type": "Solid",
@ -354,7 +537,7 @@ description: Variables in memory after executing union_cubes.kcl
"type": "Mm"
}
},
"height": 10.0,
"height": 20.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"units": {
@ -408,13 +591,13 @@ description: Variables in memory after executing union_cubes.kcl
"sourceRange": []
},
"from": [
10.0,
0.0
2.0,
-2.0
],
"tag": null,
"to": [
30.0,
0.0
12.0,
-2.0
],
"type": "ToPoint",
"units": {
@ -427,13 +610,13 @@ description: Variables in memory after executing union_cubes.kcl
"sourceRange": []
},
"from": [
30.0,
0.0
12.0,
-2.0
],
"tag": null,
"to": [
30.0,
20.0
12.0,
8.0
],
"type": "ToPoint",
"units": {
@ -446,13 +629,13 @@ description: Variables in memory after executing union_cubes.kcl
"sourceRange": []
},
"from": [
30.0,
20.0
12.0,
8.0
],
"tag": null,
"to": [
10.0,
20.0
2.0,
8.0
],
"type": "ToPoint",
"units": {
@ -465,13 +648,13 @@ description: Variables in memory after executing union_cubes.kcl
"sourceRange": []
},
"from": [
10.0,
20.0
2.0,
8.0
],
"tag": null,
"to": [
10.0,
0.0
2.0,
-2.0
],
"type": "ToPoint",
"units": {
@ -510,12 +693,12 @@ description: Variables in memory after executing union_cubes.kcl
},
"start": {
"from": [
10.0,
0.0
2.0,
-2.0
],
"to": [
10.0,
0.0
2.0,
-2.0
],
"units": {
"type": "Mm"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -2,17 +2,18 @@
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing union_cubes.kcl
---
fn cube(center) {
fn cube(center, size) {
return startSketchOn(XY)
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> startProfileAt([center[0] - size, center[1] - size], %)
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
|> extrude(length = 2 * size)
}
part001 = cube([0, 0])
part002 = cube([20, 10])
part001 = cube([0, 0], 10)
part002 = cube([7, 3], 5)
|> translate(z = 1)
fullPart = union([part001, part002])

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.58"
version = "0.1.60"
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.58"
version = "0.1.60"
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.58"
version = "0.1.60"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.83"

View File

@ -292,3 +292,22 @@ pub fn get_kcl_version() -> String {
kcl_lib::version().to_string()
}
/// Get the allowed import file extensions.
#[wasm_bindgen]
pub fn import_file_extensions() -> Result<Vec<String>, String> {
console_error_panic_hook::set_once();
Ok(kcl_lib::IMPORT_FILE_EXTENSIONS.iter().map(|s| s.to_string()).collect())
}
/// Get the allowed relevant file extensions (imports + kcl).
#[wasm_bindgen]
pub fn relevant_file_extensions() -> Result<Vec<String>, String> {
console_error_panic_hook::set_once();
Ok(kcl_lib::RELEVANT_FILE_EXTENSIONS
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>())
}

View File

@ -14,6 +14,7 @@ import {
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import toast from 'react-hot-toast'
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
@ -35,13 +36,23 @@ export const CommandBar = () => {
commandBarActor.send({ type: 'Close' })
}, [pathname])
/**
* if the engine connection is about to end, we don't want users
* to be able to perform commands that might require that connection,
* so we just close the command palette.
* TODO: instead, let each command control whether it is disabled, and
* don't just bail out
*/
useEffect(() => {
if (
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
!commandBarActor.getSnapshot().matches('Closed') &&
(immediateState.type === EngineConnectionStateType.Disconnecting ||
immediateState.type === EngineConnectionStateType.Disconnected)
) {
commandBarActor.send({ type: 'Close' })
toast.error('Exiting command flow because engine disconnected')
}
}, [immediateState])
}, [immediateState, commandBarActor])
// Hook up keyboard shortcuts
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {

View File

@ -4,6 +4,7 @@ import type { MouseEventHandler } from 'react'
import { useCallback, useContext, useEffect, useMemo } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useAppState } from '@src/AppState'
import { ActionIcon } from '@src/components/ActionIcon'
import type { CustomIconName } from '@src/components/CustomIcon'
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
@ -16,7 +17,10 @@ import { sidebarPanes } from '@src/components/ModelingSidebar/ModelingPanes'
import Tooltip from '@src/components/Tooltip'
import { DEV } from '@src/env'
import { useModelingContext } from '@src/hooks/useModelingContext'
import { useNetworkContext } from '@src/hooks/useNetworkContext'
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
import { useKclContext } from '@src/lang/KclProvider'
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
import { SIDEBAR_BUTTON_SUFFIX } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { useSettings } from '@src/machines/appMachine'
@ -52,6 +56,16 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
: 'pointer-events-auto '
const showDebugPanel = settings.app.showDebugPanel
const { overallState, immediateState } = useNetworkContext()
const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState()
const reliesOnEngine =
(overallState !== NetworkHealthState.Ok &&
overallState !== NetworkHealthState.Weak) ||
isExecuting ||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
!isStreamReady
const paneCallbackProps = useMemo(
() => ({
kclContext,
@ -93,6 +107,8 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
sidebarName: 'Export part',
icon: 'floppyDiskArrow',
keybinding: 'Ctrl + Shift + E',
disable: () =>
reliesOnEngine ? 'Need engine connection to export' : undefined,
action: () =>
commandBarActor.send({
type: 'Find and select command',

View File

@ -12,6 +12,7 @@ import {
getSweepFromSuspectedSweepSurface,
getWallCodeRef,
} from '@src/lang/std/artifactGraph'
import { isTopLevelModule } from '@src/lang/util'
import type { CallExpression, CallExpressionKw } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/wasm'
import type { DefaultPlaneStr } from '@src/lib/planes'
@ -190,8 +191,8 @@ export function useEngineConnectionSubscriptions() {
kclManager.artifactGraph
)
if (!err(extrusion)) {
const fileIndex = getModuleId(extrusion.codeRef.range)
if (fileIndex !== 0) {
if (!isTopLevelModule(extrusion.codeRef.range)) {
const fileIndex = getModuleId(extrusion.codeRef.range)
const importDetails =
kclManager.execState.filenames[fileIndex]
if (!importDetails) {

View File

@ -514,7 +514,9 @@ export function getCodeRefsByArtifactId(
artifactGraph: ArtifactGraph
): Array<CodeRef> | null {
const artifact = artifactGraph.get(id)
if (artifact?.type === 'solid2d') {
if (artifact?.type === 'compositeSolid') {
return [artifact.codeRef]
} else if (artifact?.type === 'solid2d') {
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
if (err(codeRef)) return null
return [codeRef]

View File

@ -71,6 +71,7 @@ export type {
ArtifactId,
Cap as CapArtifact,
CodeRef,
CompositeSolid as CompositeSolidArtifact,
EdgeCut,
Path as PathArtifact,
Plane as PlaneArtifact,

12
src/lang/wasmUtils.ts Normal file
View File

@ -0,0 +1,12 @@
import {
import_file_extensions,
relevant_file_extensions,
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
export function importFileExtensions(): string[] {
return import_file_extensions()
}
export function relevantFileExtensions(): string[] {
return relevant_file_extensions()
}

View File

@ -1,5 +1,4 @@
import type { Models } from '@kittycad/lib/dist/types/src'
import type { FileImportFormat_type } from '@kittycad/lib/dist/types/src/models'
import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd'
@ -36,29 +35,6 @@ export const PROJECT_IMAGE_NAME = `thumbnail.png`
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
/** The default name given to new kcl files in a project */
export const DEFAULT_FILE_NAME = 'Untitled'
/** The file endings that will appear in
* the file explorer if found in a project directory */
// TODO: make stp part of this enum as an alias to step
// TODO: make glb part of this enum as it is in fact supported
export type NativeFileType = 'kcl'
export type RelevantFileType =
| FileImportFormat_type
| NativeFileType
| 'stp'
| 'glb'
export const NATIVE_FILE_TYPE: NativeFileType = 'kcl'
export const RELEVANT_FILE_TYPES: RelevantFileType[] = [
'kcl',
'fbx',
'gltf',
'glb',
'obj',
'ply',
'sldprt',
'stp',
'step',
'stl',
] as const
/** The default name for a tutorial project */
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
/**

View File

@ -2,9 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
import { isRelevantFile, listProjects } from '@src/lib/desktop'
import { initPromise } from '@src/lang/wasm'
import { listProjects } from '@src/lib/desktop'
import type { DeepPartial } from '@src/lib/types'
beforeAll(async () => {
await initPromise
})
// Mock the electron window global
const mockElectron = {
readdir: vi.fn(),
@ -112,41 +117,6 @@ describe('desktop utilities', () => {
mockElectron.kittycad.mockResolvedValue({})
})
describe('isRelevantFile', () => {
it('finds supported extension files relevant', () => {
expect(isRelevantFile('part.kcl')).toEqual(true)
expect(isRelevantFile('part.fbx')).toEqual(true)
expect(isRelevantFile('part.gltf')).toEqual(true)
expect(isRelevantFile('part.glb')).toEqual(true)
expect(isRelevantFile('part.obj')).toEqual(true)
expect(isRelevantFile('part.ply')).toEqual(true)
expect(isRelevantFile('part.sldprt')).toEqual(true)
expect(isRelevantFile('part.stp')).toEqual(true)
expect(isRelevantFile('part.step')).toEqual(true)
expect(isRelevantFile('part.stl')).toEqual(true)
})
// TODO: we should be lowercasing the extension here to check. .sldprt or .SLDPRT should be supported
// But the api doesn't allow it today, so revisit this and the tests once this is done
it('finds (now) supported uppercase extension files *not* relevant', () => {
expect(isRelevantFile('part.KCL')).toEqual(false)
expect(isRelevantFile('part.FBX')).toEqual(false)
expect(isRelevantFile('part.GLTF')).toEqual(false)
expect(isRelevantFile('part.GLB')).toEqual(false)
expect(isRelevantFile('part.OBJ')).toEqual(false)
expect(isRelevantFile('part.PLY')).toEqual(false)
expect(isRelevantFile('part.SLDPRT')).toEqual(false)
expect(isRelevantFile('part.STP')).toEqual(false)
expect(isRelevantFile('part.STEP')).toEqual(false)
expect(isRelevantFile('part.STL')).toEqual(false)
})
it("doesn't find .docx or .SLDASM relevant", () => {
expect(isRelevantFile('paper.docx')).toEqual(false)
expect(isRelevantFile('assembly.SLDASM')).toEqual(false)
})
})
describe('listProjects', () => {
it('does not list .git directories', async () => {
const projects = await listProjects(mockConfig)

View File

@ -10,13 +10,13 @@ import {
parseAppSettings,
parseProjectSettings,
} from '@src/lang/wasm'
import { relevantFileExtensions } from '@src/lang/wasmUtils'
import {
DEFAULT_DEFAULT_LENGTH_UNIT,
PROJECT_ENTRYPOINT,
PROJECT_FOLDER,
PROJECT_IMAGE_NAME,
PROJECT_SETTINGS_FILE_NAME,
RELEVANT_FILE_TYPES,
SETTINGS_FILE_NAME,
TELEMETRY_FILE_NAME,
TELEMETRY_RAW_FILE_NAME,
@ -201,15 +201,13 @@ export async function listProjects(
return projects
}
// TODO: we should be lowercasing the extension here to check. .sldprt or .SLDPRT should be supported
// But the api doesn't allow it today, so revisit this and the tests once this is done
export const isRelevantFile = (filename: string): boolean =>
RELEVANT_FILE_TYPES.some((ext) => filename.endsWith('.' + ext))
const collectAllFilesRecursiveFrom = async (
path: string,
canReadWritePath: boolean
) => {
const RELEVANT_FILE_EXTENSIONS = relevantFileExtensions()
const isRelevantFile = (filename: string): boolean =>
RELEVANT_FILE_EXTENSIONS.some((ext) => filename.endsWith('.' + ext))
// Make sure the filesystem object exists.
try {
await window.electron.stat(path)

View File

@ -3,8 +3,13 @@ import os from 'os'
import path from 'path'
import { v4 as uuidv4 } from 'uuid'
import { initPromise } from '@src/lang/wasm'
import getCurrentProjectFile from '@src/lib/getCurrentProjectFile'
beforeAll(async () => {
await initPromise
})
describe('getCurrentProjectFile', () => {
test('with explicit open file with space (URL encoded)', async () => {
const name = `kittycad-modeling-projects-${uuidv4()}`

View File

@ -1,17 +1,12 @@
import {
importFileExtensions,
relevantFileExtensions,
} from '@src/lang/wasmUtils'
import type { Stats } from 'fs'
import * as fs from 'fs/promises'
import * as path from 'path'
import {
NATIVE_FILE_TYPE,
PROJECT_ENTRYPOINT,
RELEVANT_FILE_TYPES,
type RelevantFileType,
} from '@src/lib/constants'
const shouldWrapExtension = (extension: string) =>
RELEVANT_FILE_TYPES.includes(extension as RelevantFileType) &&
extension !== NATIVE_FILE_TYPE
import { PROJECT_ENTRYPOINT } from '@src/lib/constants'
/// Get the current project file from the path.
/// This is used for double-clicking on a file in the file explorer,
@ -19,6 +14,12 @@ const shouldWrapExtension = (extension: string) =>
export default async function getCurrentProjectFile(
pathString: string
): Promise<string | Error> {
// Extract the values into an array
const allFileImportFormats: string[] = importFileExtensions()
const relevantExtensions: string[] = relevantFileExtensions()
const shouldWrapExtension = (extension: string) =>
allFileImportFormats.includes(extension)
// Fix for "." path, which is the current directory.
let sourcePath = pathString === '.' ? process.cwd() : pathString
@ -71,12 +72,9 @@ export default async function getCurrentProjectFile(
// Check if the extension on what we are trying to open is a relevant file type.
const extension = path.extname(sourcePath).slice(1).toLowerCase()
if (
!RELEVANT_FILE_TYPES.includes(extension as RelevantFileType) &&
extension !== 'toml'
) {
if (!relevantExtensions.includes(extension) && extension !== 'toml') {
return new Error(
`File type (${extension}) cannot be opened with this app: '${sourcePath}', try opening one of the following file types: ${RELEVANT_FILE_TYPES.join(
`File type (${extension}) cannot be opened with this app: '${sourcePath}', try opening one of the following file types: ${relevantExtensions.join(
', '
)}`
)

View File

@ -15,6 +15,7 @@ import type {
format_number as FormatNumber,
get_kcl_version as GetKclVersion,
get_tangential_arc_to_info as GetTangentialArcToInfo,
import_file_extensions as ImportFileExtensions,
is_kcl_empty_or_only_settings as IsKclEmptyOrOnlySettings,
is_points_ccw as IsPointsCcw,
kcl_lint as KclLint,
@ -23,6 +24,7 @@ import type {
parse_project_settings as ParseProjectSettings,
parse_wasm as ParseWasm,
recast_wasm as RecastWasm,
relevant_file_extensions as RelevantFileExtensions,
serialize_configuration as SerializeConfiguration,
serialize_project_configuration as SerializeProjectConfiguration,
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
@ -111,3 +113,9 @@ export const serialize_project_configuration: typeof SerializeProjectConfigurati
(...args) => {
return getModule().serialize_project_configuration(...args)
}
export const import_file_extensions: typeof ImportFileExtensions = () => {
return getModule().import_file_extensions()
}
export const relevant_file_extensions: typeof RelevantFileExtensions = () => {
return getModule().relevant_file_extensions()
}

View File

@ -45,7 +45,8 @@ export function useDemoCode() {
}
// Don't run if the network isn't healthy or the connection isn't established
if (
overallState !== NetworkHealthState.Ok ||
overallState === NetworkHealthState.Disconnected ||
overallState === NetworkHealthState.Issue ||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
) {
return