Compare commits
8 Commits
nested_dir
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
0573801381 | |||
140e8b56fb | |||
4d203b0c0c | |||
04d6fcf0c4 | |||
30b2a765fc | |||
8ddbb488d6 | |||
db82a54f27 | |||
077d1cfcef |
2
.github/ci-cd-scripts/upload-results.sh
vendored
2
.github/ci-cd-scripts/upload-results.sh
vendored
@ -6,7 +6,6 @@ if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
|
||||
fi
|
||||
|
||||
project="https://github.com/KittyCAD/modeling-app"
|
||||
suite="${CI_SUITE:-unit}"
|
||||
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
|
||||
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||
|
||||
@ -14,7 +13,6 @@ echo "Uploading batch results"
|
||||
curl --silent --request POST \
|
||||
--header "X-API-Key: ${TAB_API_KEY}" \
|
||||
--form "project=${project}" \
|
||||
--form "suite=${suite}" \
|
||||
--form "branch=${branch}" \
|
||||
--form "commit=${commit}" \
|
||||
--form "tests=@test-results/junit.xml" \
|
||||
|
1
.github/workflows/cargo-test.yml
vendored
1
.github/workflows/cargo-test.yml
vendored
@ -193,7 +193,6 @@ jobs:
|
||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CI_SUITE: unit:kcl
|
||||
run-internal-kcl-samples:
|
||||
name: cargo test (internal-kcl-samples)
|
||||
runs-on:
|
||||
|
2
.github/workflows/e2e-tests.yml
vendored
2
.github/workflows/e2e-tests.yml
vendored
@ -156,7 +156,6 @@ jobs:
|
||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CI_SUITE: snapshots
|
||||
TARGET: web
|
||||
|
||||
- name: Update snapshots
|
||||
@ -168,7 +167,6 @@ jobs:
|
||||
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
|
||||
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
CI_SUITE: snapshots
|
||||
TARGET: web
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
@ -105,19 +105,14 @@ export class CmdBarFixture {
|
||||
expectState = async (expected: CmdBarSerialised) => {
|
||||
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
|
||||
}
|
||||
/**
|
||||
* This method is used to progress the command bar to the next step, defaulting to clicking the next button.
|
||||
* Optionally, with the `shouldUseKeyboard` parameter, it will hit `Enter` to progress.
|
||||
* * TODO: This method assumes the user has a valid input to the current stage,
|
||||
/** The method will use buttons OR press enter randomly to progress the cmdbar,
|
||||
* this could have unexpected results depending on what's focused
|
||||
*
|
||||
* TODO: This method assumes the user has a valid input to the current stage,
|
||||
* and assumes we are past the `pickCommand` step.
|
||||
*/
|
||||
progressCmdBar = async (shouldUseKeyboard = false) => {
|
||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
||||
await this.page.waitForTimeout(2000)
|
||||
if (shouldUseKeyboard) {
|
||||
await this.page.keyboard.press('Enter')
|
||||
return
|
||||
}
|
||||
|
||||
const arrowButton = this.page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
|
@ -61,7 +61,6 @@ class MyAPIReporter implements Reporter {
|
||||
const payload = {
|
||||
// Required information
|
||||
project: 'https://github.com/KittyCAD/modeling-app',
|
||||
suite: process.env.CI_SUITE || 'e2e',
|
||||
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
||||
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
||||
test: test.titlePath().slice(2).join(' › '),
|
||||
|
@ -1855,11 +1855,7 @@ sketch002 = startSketchOn(XZ)
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
// Confirm we can submit from the review step with just `Enter`
|
||||
await cmdBar.progressCmdBar(true)
|
||||
await cmdBar.expectState({
|
||||
stage: 'commandBarClosed',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
@ -1999,7 +1995,7 @@ profile001 = ${circleCode}`
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar(true)
|
||||
await cmdBar.progressCmdBar()
|
||||
await editor.expectEditor.toContain(sweepDeclaration)
|
||||
})
|
||||
|
||||
|
@ -281,14 +281,7 @@ impl ExecutorContext {
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
exec_state.mod_local.module_exports.push(var_name);
|
||||
} else {
|
||||
exec_state.err(CompilationError::err(
|
||||
variable_declaration.as_source_range(),
|
||||
"Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
|
||||
));
|
||||
}
|
||||
exec_state.mod_local.module_exports.push(var_name);
|
||||
}
|
||||
// Variable declaration can be the return value of a module.
|
||||
last_expr = matches!(body_type, BodyType::Root).then_some(value);
|
||||
|
@ -996,27 +996,6 @@ mod import_cycle1 {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod import_only_at_top_level {
|
||||
const TEST_NAME: &str = "import_only_at_top_level";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod import_function_not_sketch {
|
||||
const TEST_NAME: &str = "import_function_not_sketch";
|
||||
|
||||
@ -1185,27 +1164,6 @@ mod import_foreign {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod export_var_only_at_top_level {
|
||||
const TEST_NAME: &str = "export_var_only_at_top_level";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, false).await
|
||||
}
|
||||
}
|
||||
mod assembly_non_default_units {
|
||||
const TEST_NAME: &str = "assembly_non_default_units";
|
||||
|
||||
@ -3360,24 +3318,3 @@ mod nested_windows_main_kcl {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod nested_assembly {
|
||||
const TEST_NAME: &str = "nested_assembly";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact commands export_only_at_top_level.kcl
|
||||
---
|
||||
[]
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart export_only_at_top_level.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -1,3 +0,0 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -1,148 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of parsing export_only_at_top_level.kcl
|
||||
---
|
||||
{
|
||||
"Ok": {
|
||||
"body": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"declaration": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"id": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "main",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"body": {
|
||||
"body": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"declaration": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"id": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "x",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "2",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 2.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
},
|
||||
"start": 0,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 0,
|
||||
"kind": "const",
|
||||
"start": 0,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"visibility": "export"
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "0",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 0.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "ReturnStatement",
|
||||
"type": "ReturnStatement"
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"params": [],
|
||||
"start": 0,
|
||||
"type": "FunctionExpression",
|
||||
"type": "FunctionExpression"
|
||||
},
|
||||
"start": 0,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 0,
|
||||
"kind": "fn",
|
||||
"start": 0,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
},
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"expression": {
|
||||
"callee": {
|
||||
"abs_path": false,
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "main",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 0,
|
||||
"type": "Name"
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "CallExpressionKw",
|
||||
"type": "CallExpressionKw",
|
||||
"unlabeled": null
|
||||
},
|
||||
"start": 0,
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement"
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"0": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "NonCodeNode",
|
||||
"value": {
|
||||
"type": "newLine"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"startNodes": []
|
||||
},
|
||||
"start": 0
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Error from executing export_only_at_top_level.kcl
|
||||
---
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Exports are only supported at the top-level of a file. Remove
|
||||
│ `export` or move it to the top-level.
|
||||
╭─[2:3]
|
||||
1 │ fn main() {
|
||||
2 │ export x = 2
|
||||
· ──────┬─────
|
||||
· ╰── main
|
||||
3 │ return 0
|
||||
╰────
|
@ -1,6 +0,0 @@
|
||||
fn main() {
|
||||
export x = 2
|
||||
return 0
|
||||
}
|
||||
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Operations executed export_only_at_top_level.kcl
|
||||
---
|
||||
[]
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing export_only_at_top_level.kcl
|
||||
---
|
||||
fn main() {
|
||||
export x = 2
|
||||
return 0
|
||||
}
|
||||
|
||||
main()
|
@ -1,32 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact commands import_only_at_top_level.kcl
|
||||
---
|
||||
[
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "edge_lines_visible",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart import_only_at_top_level.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -1,3 +0,0 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
@ -1,129 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of parsing import_only_at_top_level.kcl
|
||||
---
|
||||
{
|
||||
"Ok": {
|
||||
"body": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"declaration": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"id": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "main",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"init": {
|
||||
"body": {
|
||||
"body": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"path": {
|
||||
"type": "Kcl",
|
||||
"filename": "empty.kcl"
|
||||
},
|
||||
"selector": {
|
||||
"type": "None",
|
||||
"alias": null
|
||||
},
|
||||
"start": 0,
|
||||
"type": "ImportStatement",
|
||||
"type": "ImportStatement"
|
||||
},
|
||||
{
|
||||
"argument": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"raw": "0",
|
||||
"start": 0,
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"value": {
|
||||
"value": 0.0,
|
||||
"suffix": "None"
|
||||
}
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "ReturnStatement",
|
||||
"type": "ReturnStatement"
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"params": [],
|
||||
"start": 0,
|
||||
"type": "FunctionExpression",
|
||||
"type": "FunctionExpression"
|
||||
},
|
||||
"start": 0,
|
||||
"type": "VariableDeclarator"
|
||||
},
|
||||
"end": 0,
|
||||
"kind": "fn",
|
||||
"start": 0,
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration"
|
||||
},
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"expression": {
|
||||
"callee": {
|
||||
"abs_path": false,
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "main",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 0,
|
||||
"type": "Name"
|
||||
},
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "CallExpressionKw",
|
||||
"type": "CallExpressionKw",
|
||||
"unlabeled": null
|
||||
},
|
||||
"start": 0,
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement"
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"0": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "NonCodeNode",
|
||||
"value": {
|
||||
"type": "newLine"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"startNodes": []
|
||||
},
|
||||
"start": 0
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Error from executing import_only_at_top_level.kcl
|
||||
---
|
||||
KCL Semantic error
|
||||
|
||||
× semantic: Imports are only supported at the top-level of a file.
|
||||
╭─[2:3]
|
||||
1 │ fn main() {
|
||||
2 │ import "empty.kcl"
|
||||
· ─────────┬────────
|
||||
· ╰── tests/import_only_at_top_level/input.kcl
|
||||
3 │ return 0
|
||||
╰────
|
||||
╭─[6:1]
|
||||
5 │
|
||||
6 │ main()
|
||||
· ───┬──
|
||||
· ╰── tests/import_only_at_top_level/input.kcl
|
||||
╰────
|
||||
╰─▶ KCL Semantic error
|
||||
|
||||
× semantic: Imports are only supported at the top-level of a file.
|
||||
╭─[2:3]
|
||||
1 │ fn main() {
|
||||
2 │ import "empty.kcl"
|
||||
· ─────────┬────────
|
||||
· ╰── tests/import_only_at_top_level/input.kcl
|
||||
3 │ return 0
|
||||
╰────
|
@ -1,6 +0,0 @@
|
||||
fn main() {
|
||||
import "empty.kcl"
|
||||
return 0
|
||||
}
|
||||
|
||||
main()
|
@ -1,32 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Operations executed import_only_at_top_level.kcl
|
||||
---
|
||||
[
|
||||
{
|
||||
"type": "GroupBegin",
|
||||
"group": {
|
||||
"type": "ModuleInstance",
|
||||
"name": "empty.kcl",
|
||||
"moduleId": 0
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "GroupBegin",
|
||||
"group": {
|
||||
"type": "FunctionCall",
|
||||
"name": "main",
|
||||
"functionSourceRange": [],
|
||||
"unlabeledArg": null,
|
||||
"labeledArgs": {}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "GroupEnd"
|
||||
},
|
||||
{
|
||||
"type": "GroupEnd"
|
||||
}
|
||||
]
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing import_only_at_top_level.kcl
|
||||
---
|
||||
fn main() {
|
||||
import "empty.kcl"
|
||||
return 0
|
||||
}
|
||||
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing tests/import_only_at_top_level/empty.kcl
|
||||
---
|
||||
|
@ -1,184 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact commands nested_main_kcl.kcl
|
||||
---
|
||||
[
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "edge_lines_visible",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_visible",
|
||||
"object_id": "[uuid]",
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "make_plane",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"x_axis": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"y_axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"size": 60.0,
|
||||
"clobber": false,
|
||||
"hide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "close_path",
|
||||
"path_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "enable_sketch_mode",
|
||||
"entity_id": "[uuid]",
|
||||
"ortho": false,
|
||||
"animated": false,
|
||||
"adjust_camera": false,
|
||||
"planar_normal": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "extend_path",
|
||||
"path": "[uuid]",
|
||||
"segment": {
|
||||
"type": "arc",
|
||||
"center": {
|
||||
"x": 15.0,
|
||||
"y": 0.0
|
||||
},
|
||||
"radius": 5.0,
|
||||
"start": {
|
||||
"unit": "degrees",
|
||||
"value": 0.0
|
||||
},
|
||||
"end": {
|
||||
"unit": "degrees",
|
||||
"value": 360.0
|
||||
},
|
||||
"relative": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "move_path_pen",
|
||||
"path": "[uuid]",
|
||||
"to": {
|
||||
"x": 20.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "sketch_mode_disable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "start_path"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "object_bring_to_front",
|
||||
"object_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "revolve",
|
||||
"target": "[uuid]",
|
||||
"origin": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"axis": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"axis_is_2d": true,
|
||||
"angle": {
|
||||
"unit": "degrees",
|
||||
"value": 360.0
|
||||
},
|
||||
"tolerance": 0.0000001,
|
||||
"opposite": "None"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "solid3d_get_adjacency_info",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [],
|
||||
"command": {
|
||||
"type": "solid3d_get_extrusion_face_info",
|
||||
"object_id": "[uuid]",
|
||||
"edge_id": "[uuid]"
|
||||
}
|
||||
}
|
||||
]
|
@ -1,6 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart nested_main_kcl.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -1,23 +0,0 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[43, 81, 1]"]
|
||||
3["Segment<br>[43, 81, 1]"]
|
||||
4[Solid2d]
|
||||
end
|
||||
1["Plane<br>[18, 35, 1]"]
|
||||
5["Sweep Revolve<br>[89, 142, 1]"]
|
||||
6[Wall]
|
||||
%% face_code_ref=Missing NodePath
|
||||
7["SweepEdge Adjacent"]
|
||||
1 --- 2
|
||||
2 --- 3
|
||||
2 --- 4
|
||||
2 ---- 5
|
||||
5 <--x 3
|
||||
3 --- 6
|
||||
3 --- 7
|
||||
5 --- 6
|
||||
5 --- 7
|
||||
6 --- 7
|
||||
```
|
@ -1,73 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of parsing nested_main_kcl.kcl
|
||||
---
|
||||
{
|
||||
"Ok": {
|
||||
"body": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"path": {
|
||||
"type": "Kcl",
|
||||
"filename": "nested/foo/bar/main.kcl"
|
||||
},
|
||||
"selector": {
|
||||
"type": "None",
|
||||
"alias": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "bar",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
}
|
||||
},
|
||||
"start": 0,
|
||||
"type": "ImportStatement",
|
||||
"type": "ImportStatement"
|
||||
},
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"expression": {
|
||||
"abs_path": false,
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": {
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"name": "bar",
|
||||
"start": 0,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 0,
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"start": 0,
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement"
|
||||
}
|
||||
],
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {
|
||||
"0": [
|
||||
{
|
||||
"commentStart": 0,
|
||||
"end": 0,
|
||||
"start": 0,
|
||||
"type": "NonCodeNode",
|
||||
"value": {
|
||||
"type": "newLine"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"startNodes": []
|
||||
},
|
||||
"start": 0
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import "nested/foo/bar/main.kcl" as bar
|
||||
|
||||
bar
|
@ -1,7 +0,0 @@
|
||||
// A donut shape.
|
||||
startSketchOn(XY)
|
||||
|> circle( center = [15, 0], radius = 5 )
|
||||
|> revolve(
|
||||
angle = 360,
|
||||
axis = Y,
|
||||
)
|
@ -1,3 +0,0 @@
|
||||
import "imported.kcl" as imported
|
||||
|
||||
imported
|
@ -1,18 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Operations executed nested_main_kcl.kcl
|
||||
---
|
||||
[
|
||||
{
|
||||
"type": "GroupBegin",
|
||||
"group": {
|
||||
"type": "ModuleInstance",
|
||||
"name": "main.kcl",
|
||||
"moduleId": 0
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "GroupEnd"
|
||||
}
|
||||
]
|
@ -1,10 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Variables in memory after executing nested_main_kcl.kcl
|
||||
---
|
||||
{
|
||||
"bar": {
|
||||
"type": "Module",
|
||||
"value": 1
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 150 KiB |
@ -1,7 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing nested_main_kcl.kcl
|
||||
---
|
||||
import "nested/foo/bar/main.kcl" as bar
|
||||
|
||||
bar
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing tests/nested_assembly/nested/foo/bar/imported.kcl
|
||||
---
|
||||
// A donut shape.
|
||||
startSketchOn(XY)
|
||||
|> circle(center = [15, 0], radius = 5)
|
||||
|> revolve(angle = 360, axis = Y)
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Result of unparsing tests/nested_assembly/nested/foo/bar/main.kcl
|
||||
---
|
||||
import "imported.kcl" as imported
|
||||
|
||||
imported
|
@ -1,5 +1,4 @@
|
||||
import type React from 'react'
|
||||
import { useMemo, useEffect, useRef, useState } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
import { ActionButton } from '@src/components/ActionButton'
|
||||
@ -122,7 +121,6 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
||||
data-is-current-arg={
|
||||
argName === currentArgument?.name ? 'true' : 'false'
|
||||
}
|
||||
type="button"
|
||||
disabled={!isReviewing && currentArgument?.name === argName}
|
||||
onClick={() => {
|
||||
commandBarActor.send({
|
||||
@ -246,20 +244,13 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
||||
|
||||
type ButtonProps = { bgClassName?: string; iconClassName?: string }
|
||||
function ReviewingButton({ bgClassName, iconClassName }: ButtonProps) {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
useEffect(() => {
|
||||
if (buttonRef.current) {
|
||||
buttonRef.current.focus()
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<ActionButton
|
||||
Element="button"
|
||||
ref={buttonRef}
|
||||
autoFocus
|
||||
type="submit"
|
||||
form="review-form"
|
||||
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current"
|
||||
tabIndex={0}
|
||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||
data-testid="command-bar-submit"
|
||||
iconStart={{
|
||||
icon: 'checkmark',
|
||||
@ -278,8 +269,7 @@ function GatheringArgsButton({ bgClassName, iconClassName }: ButtonProps) {
|
||||
Element="button"
|
||||
type="submit"
|
||||
form="arg-form"
|
||||
className="w-fit !p-0 rounded-sm hover:shadow focus:outline-current"
|
||||
tabIndex={0}
|
||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||
data-testid="command-bar-continue"
|
||||
iconStart={{
|
||||
icon: 'arrowRight',
|
||||
|
@ -12,8 +12,12 @@ import { useLoaderData } from 'react-router-dom'
|
||||
import type { Actor, ContextFrom, Prop, SnapshotFrom, StateFrom } from 'xstate'
|
||||
import { assign, fromPromise } from 'xstate'
|
||||
|
||||
import type { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
|
||||
import type {
|
||||
OutputFormat3d,
|
||||
Point3d,
|
||||
} from '@rust/kcl-lib/bindings/ModelingCmd'
|
||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||
import type { Plane } from '@rust/kcl-lib/bindings/Plane'
|
||||
|
||||
import { useAppState } from '@src/AppState'
|
||||
import { letEngineAnimateAndSyncCamAfter } from '@src/clientSideScene/CameraControls'
|
||||
@ -34,16 +38,26 @@ import useModelingMachineCommands from '@src/hooks/useStateMachineCommands'
|
||||
import { useKclContext } from '@src/lang/KclProvider'
|
||||
import { updateModelingState } from '@src/lang/modelingWorkflows'
|
||||
import {
|
||||
insertNamedConstant,
|
||||
replaceValueAtNodePath,
|
||||
sketchOnExtrudedFace,
|
||||
sketchOnOffsetPlane,
|
||||
splitPipedProfile,
|
||||
startSketchOnDefault,
|
||||
} from '@src/lang/modifyAst'
|
||||
import {
|
||||
artifactIsPlaneWithPaths,
|
||||
doesSketchPipeNeedSplitting,
|
||||
getNodeFromPath,
|
||||
isCursorInFunctionDefinition,
|
||||
traverse,
|
||||
} from '@src/lang/queryAst'
|
||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||
import {
|
||||
getFaceCodeRef,
|
||||
getPathsFromArtifact,
|
||||
getPlaneFromArtifact,
|
||||
} from '@src/lang/std/artifactGraph'
|
||||
import {
|
||||
EngineConnectionStateType,
|
||||
EngineConnectionEvents,
|
||||
@ -52,6 +66,7 @@ import { err, reportRejection, trap, reject } from '@src/lib/trap'
|
||||
import { isNonNullable, platform, uuidv4 } from '@src/lib/utils'
|
||||
import { promptToEditFlow } from '@src/lib/promptToEdit'
|
||||
import type { FileMeta } from '@src/lib/types'
|
||||
import { kclEditorActor } from '@src/machines/kclEditorMachine'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
import { useToken, useSettings } from '@src/lib/singletons'
|
||||
import type { IndexLoaderData } from '@src/lib/types'
|
||||
@ -83,13 +98,23 @@ import {
|
||||
} from '@src/lib/singletons'
|
||||
import type { MachineManager } from '@src/components/MachineManagerProvider'
|
||||
import { MachineManagerContext } from '@src/components/MachineManagerProvider'
|
||||
import { updateSelections } from '@src/lib/selections'
|
||||
import { updateSketchDetailsNodePaths } from '@src/lang/util'
|
||||
import {
|
||||
handleSelectionBatch,
|
||||
updateSelections,
|
||||
type Selections,
|
||||
} from '@src/lib/selections'
|
||||
import {
|
||||
crossProduct,
|
||||
isCursorInSketchCommandRange,
|
||||
updateSketchDetailsNodePaths,
|
||||
} from '@src/lang/util'
|
||||
import {
|
||||
modelingMachineCommandConfig,
|
||||
type ModelingCommandSchema,
|
||||
} from '@src/lib/commandBarConfigs/modelingCommandConfig'
|
||||
import type {
|
||||
KclValue,
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
@ -295,6 +320,229 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
}
|
||||
}),
|
||||
'Set selection': assign(
|
||||
({ context: { selectionRanges, sketchDetails }, event }) => {
|
||||
// this was needed for ts after adding 'Set selection' action to on done modal events
|
||||
const setSelections =
|
||||
('data' in event &&
|
||||
event.data &&
|
||||
'selectionType' in event.data &&
|
||||
event.data) ||
|
||||
('output' in event &&
|
||||
event.output &&
|
||||
'selectionType' in event.output &&
|
||||
event.output) ||
|
||||
null
|
||||
if (!setSelections) return {}
|
||||
|
||||
let selections: Selections = {
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
if (setSelections.selectionType === 'singleCodeCursor') {
|
||||
if (!setSelections.selection && editorManager.isShiftDown) {
|
||||
// if the user is holding shift, but they didn't select anything
|
||||
// don't nuke their other selections (frustrating to have one bad click ruin your
|
||||
// whole selection)
|
||||
selections = {
|
||||
graphSelections: selectionRanges.graphSelections,
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
}
|
||||
} else if (
|
||||
!setSelections.selection &&
|
||||
!editorManager.isShiftDown
|
||||
) {
|
||||
selections = {
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (
|
||||
setSelections.selection &&
|
||||
!editorManager.isShiftDown
|
||||
) {
|
||||
selections = {
|
||||
graphSelections: [setSelections.selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||
// selecting and deselecting multiple objects
|
||||
|
||||
/**
|
||||
* There are two scenarios:
|
||||
* 1. General case:
|
||||
* When selecting and deselecting edges,
|
||||
* faces or segment (during sketch edit)
|
||||
* we use its artifact ID to identify the selection
|
||||
* 2. Initial sketch setup:
|
||||
* The artifact is not yet created
|
||||
* so we use the codeRef.range
|
||||
*/
|
||||
|
||||
let updatedSelections: typeof selectionRanges.graphSelections
|
||||
|
||||
// 1. General case: Artifact exists, use its ID
|
||||
if (setSelections.selection.artifact?.id) {
|
||||
// check if already selected
|
||||
const alreadySelected = selectionRanges.graphSelections.some(
|
||||
(selection) =>
|
||||
selection.artifact?.id ===
|
||||
setSelections.selection?.artifact?.id
|
||||
)
|
||||
if (
|
||||
alreadySelected &&
|
||||
setSelections.selection?.artifact?.id
|
||||
) {
|
||||
// remove it
|
||||
updatedSelections = selectionRanges.graphSelections.filter(
|
||||
(selection) =>
|
||||
selection.artifact?.id !==
|
||||
setSelections.selection?.artifact?.id
|
||||
)
|
||||
} else {
|
||||
// add it
|
||||
updatedSelections = [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
]
|
||||
}
|
||||
} else {
|
||||
// 2. Initial sketch setup: Artifact not yet created – use codeRef.range
|
||||
const selectionRange = JSON.stringify(
|
||||
setSelections.selection?.codeRef?.range
|
||||
)
|
||||
|
||||
// check if already selected
|
||||
const alreadySelected = selectionRanges.graphSelections.some(
|
||||
(selection) => {
|
||||
const existingRange = JSON.stringify(
|
||||
selection.codeRef?.range
|
||||
)
|
||||
return existingRange === selectionRange
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
alreadySelected &&
|
||||
setSelections.selection?.codeRef?.range
|
||||
) {
|
||||
// remove it
|
||||
updatedSelections = selectionRanges.graphSelections.filter(
|
||||
(selection) =>
|
||||
JSON.stringify(selection.codeRef?.range) !==
|
||||
selectionRange
|
||||
)
|
||||
} else {
|
||||
// add it
|
||||
updatedSelections = [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
selections = {
|
||||
graphSelections: updatedSelections,
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
engineEvents,
|
||||
codeMirrorSelection,
|
||||
updateSceneObjectColors,
|
||||
} = handleSelectionBatch({
|
||||
selections,
|
||||
})
|
||||
if (codeMirrorSelection) {
|
||||
kclEditorActor.send({
|
||||
type: 'setLastSelectionEvent',
|
||||
data: {
|
||||
codeMirrorSelection,
|
||||
scrollIntoView: setSelections.scrollIntoView ?? false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// If there are engine commands that need sent off, send them
|
||||
// TODO: This should be handled outside of an action as its own
|
||||
// actor, so that the system state is more controlled.
|
||||
engineEvents &&
|
||||
engineEvents.forEach((event) => {
|
||||
engineCommandManager
|
||||
.sendSceneCommand(event)
|
||||
.catch(reportRejection)
|
||||
})
|
||||
updateSceneObjectColors()
|
||||
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
setSelections.selectionType === 'axisSelection' ||
|
||||
setSelections.selectionType === 'defaultPlaneSelection'
|
||||
) {
|
||||
if (editorManager.isShiftDown) {
|
||||
selections = {
|
||||
graphSelections: selectionRanges.graphSelections,
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
} else {
|
||||
selections = {
|
||||
graphSelections: [],
|
||||
otherSelections: [setSelections.selection],
|
||||
}
|
||||
}
|
||||
return {
|
||||
selectionRanges: selections,
|
||||
}
|
||||
}
|
||||
|
||||
if (setSelections.selectionType === 'completeSelection') {
|
||||
const codeMirrorSelection = editorManager.createEditorSelection(
|
||||
setSelections.selection
|
||||
)
|
||||
kclEditorActor.send({
|
||||
type: 'setLastSelectionEvent',
|
||||
data: {
|
||||
codeMirrorSelection,
|
||||
scrollIntoView: false,
|
||||
},
|
||||
})
|
||||
if (!sketchDetails)
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
}
|
||||
return {
|
||||
selectionRanges: setSelections.selection,
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchEntryNodePath:
|
||||
setSelections.updatedSketchEntryNodePath ||
|
||||
sketchDetails?.sketchEntryNodePath ||
|
||||
[],
|
||||
sketchNodePaths:
|
||||
setSelections.updatedSketchNodePaths ||
|
||||
sketchDetails?.sketchNodePaths ||
|
||||
[],
|
||||
planeNodePath:
|
||||
setSelections.updatedPlaneNodePath ||
|
||||
sketchDetails?.planeNodePath ||
|
||||
[],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
),
|
||||
},
|
||||
guards: {
|
||||
'has valid selection for deletion': ({
|
||||
@ -304,6 +552,35 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.graphSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'is-error-free': () => {
|
||||
return kclManager.errors.length === 0 && !kclManager.hasErrors()
|
||||
},
|
||||
'Selection is on face': ({ context: { selectionRanges }, event }) => {
|
||||
if (event.type !== 'Enter sketch') return false
|
||||
if (event.data?.forceNewSketch) return false
|
||||
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
||||
return true
|
||||
} else if (selectionRanges.graphSelections[0]?.artifact) {
|
||||
// See if the selection is "close enough" to be coerced to the plane later
|
||||
const maybePlane = getPlaneFromArtifact(
|
||||
selectionRanges.graphSelections[0].artifact,
|
||||
kclManager.artifactGraph
|
||||
)
|
||||
return !err(maybePlane)
|
||||
}
|
||||
if (
|
||||
isCursorInFunctionDefinition(
|
||||
kclManager.ast,
|
||||
selectionRanges.graphSelections[0]
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return !!isCursorInSketchCommandRange(
|
||||
kclManager.artifactGraph,
|
||||
selectionRanges
|
||||
)
|
||||
},
|
||||
'Has exportable geometry': () =>
|
||||
!kclManager.hasErrors() && kclManager.ast.body.length > 0,
|
||||
},
|
||||
@ -573,6 +850,123 @@ export const ModelingMachineProvider = ({
|
||||
animateTargetId: input.planeId,
|
||||
}
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
async ({ input: { selectionRanges } }) => {
|
||||
const artifact = selectionRanges.graphSelections[0].artifact
|
||||
const plane = getPlaneFromArtifact(
|
||||
artifact,
|
||||
kclManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plane
|
||||
// but still works if the user selected a plane/face by defaulting to the first path
|
||||
const mainPath =
|
||||
artifact?.type === 'segment' || artifact?.type === 'solid2d'
|
||||
? artifact?.pathId
|
||||
: plane?.pathIds[0]
|
||||
let sketch: KclValue | null = null
|
||||
let planeVar: Plane | null = null
|
||||
|
||||
for (const variable of Object.values(
|
||||
kclManager.execState.variables
|
||||
)) {
|
||||
// find programMemory that matches path artifact
|
||||
if (
|
||||
variable?.type === 'Sketch' &&
|
||||
variable.value.artifactId === mainPath
|
||||
) {
|
||||
sketch = variable
|
||||
break
|
||||
}
|
||||
if (
|
||||
// if the variable is an sweep, check if the underlying sketch matches the artifact
|
||||
variable?.type === 'Solid' &&
|
||||
variable.value.sketch.on.type === 'plane' &&
|
||||
variable.value.sketch.artifactId === mainPath
|
||||
) {
|
||||
sketch = {
|
||||
type: 'Sketch',
|
||||
value: variable.value.sketch,
|
||||
}
|
||||
break
|
||||
}
|
||||
if (
|
||||
variable?.type === 'Plane' &&
|
||||
plane.id === variable.value.id
|
||||
) {
|
||||
planeVar = variable.value
|
||||
}
|
||||
}
|
||||
|
||||
if (!sketch || sketch.type !== 'Sketch') {
|
||||
if (artifact?.type !== 'plane')
|
||||
return Promise.reject(new Error('No sketch'))
|
||||
const planeCodeRef = getFaceCodeRef(artifact)
|
||||
if (planeVar && planeCodeRef) {
|
||||
const toTuple = (point: Point3d): [number, number, number] => [
|
||||
point.x,
|
||||
point.y,
|
||||
point.z,
|
||||
]
|
||||
const planPath = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
planeCodeRef.range
|
||||
)
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
engineCommandManager,
|
||||
artifact.id
|
||||
)
|
||||
const normal = crossProduct(planeVar.xAxis, planeVar.yAxis)
|
||||
return {
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: planPath,
|
||||
sketchNodePaths: [],
|
||||
zAxis: toTuple(normal),
|
||||
yAxis: toTuple(planeVar.yAxis),
|
||||
origin: toTuple(planeVar.origin),
|
||||
}
|
||||
}
|
||||
return Promise.reject(new Error('No sketch'))
|
||||
}
|
||||
const info = await sceneEntitiesManager.getSketchOrientationDetails(
|
||||
sketch.value
|
||||
)
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
engineCommandManager,
|
||||
info?.sketchDetails?.faceId || ''
|
||||
)
|
||||
|
||||
const sketchArtifact = kclManager.artifactGraph.get(mainPath)
|
||||
if (sketchArtifact?.type !== 'path')
|
||||
return Promise.reject(new Error('No sketch artifact'))
|
||||
const sketchPaths = getPathsFromArtifact({
|
||||
artifact: kclManager.artifactGraph.get(plane.id),
|
||||
sketchPathToNode: sketchArtifact?.codeRef?.pathToNode,
|
||||
artifactGraph: kclManager.artifactGraph,
|
||||
ast: kclManager.ast,
|
||||
})
|
||||
if (err(sketchPaths)) return Promise.reject(sketchPaths)
|
||||
let codeRef = getFaceCodeRef(plane)
|
||||
if (!codeRef) return Promise.reject(new Error('No plane codeRef'))
|
||||
// codeRef.pathToNode is not always populated correctly
|
||||
const planeNodePath = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
codeRef.range
|
||||
)
|
||||
return {
|
||||
sketchEntryNodePath: sketchArtifact.codeRef.pathToNode || [],
|
||||
sketchNodePaths: sketchPaths,
|
||||
planeNodePath,
|
||||
zAxis: info.sketchDetails.zAxis || null,
|
||||
yAxis: info.sketchDetails.yAxis || null,
|
||||
origin: info.sketchDetails.origin.map(
|
||||
(a) => a / sceneInfra._baseUnitMultiplier
|
||||
) as [number, number, number],
|
||||
animateTargetId: info?.sketchDetails?.faceId || '',
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
'Get horizontal info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
@ -977,6 +1371,130 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}
|
||||
),
|
||||
'Apply named value constraint': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails, data } }) => {
|
||||
if (!sketchDetails) {
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
}
|
||||
if (!data) {
|
||||
return Promise.reject(new Error('No data from command flow'))
|
||||
}
|
||||
let pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
let parsed = pResult.program
|
||||
|
||||
let result: {
|
||||
modifiedAst: Node<Program>
|
||||
pathToReplaced: PathToNode | null
|
||||
exprInsertIndex: number
|
||||
} = {
|
||||
modifiedAst: parsed,
|
||||
pathToReplaced: null,
|
||||
exprInsertIndex: -1,
|
||||
}
|
||||
// If the user provided a constant name,
|
||||
// we need to insert the named constant
|
||||
// and then replace the node with the constant's name.
|
||||
if ('variableName' in data.namedValue) {
|
||||
const astAfterReplacement = replaceValueAtNodePath({
|
||||
ast: parsed,
|
||||
pathToNode: data.currentValue.pathToNode,
|
||||
newExpressionString: data.namedValue.variableName,
|
||||
})
|
||||
if (trap(astAfterReplacement)) {
|
||||
return Promise.reject(astAfterReplacement)
|
||||
}
|
||||
const parseResultAfterInsertion = parse(
|
||||
recast(
|
||||
insertNamedConstant({
|
||||
node: astAfterReplacement.modifiedAst,
|
||||
newExpression: data.namedValue,
|
||||
})
|
||||
)
|
||||
)
|
||||
result.exprInsertIndex = data.namedValue.insertIndex
|
||||
|
||||
if (
|
||||
trap(parseResultAfterInsertion) ||
|
||||
!resultIsOk(parseResultAfterInsertion)
|
||||
)
|
||||
return Promise.reject(parseResultAfterInsertion)
|
||||
result = {
|
||||
modifiedAst: parseResultAfterInsertion.program,
|
||||
pathToReplaced: astAfterReplacement.pathToReplaced,
|
||||
exprInsertIndex: result.exprInsertIndex,
|
||||
}
|
||||
} else if ('valueText' in data.namedValue) {
|
||||
// If they didn't provide a constant name,
|
||||
// just replace the node with the value.
|
||||
const astAfterReplacement = replaceValueAtNodePath({
|
||||
ast: parsed,
|
||||
pathToNode: data.currentValue.pathToNode,
|
||||
newExpressionString: data.namedValue.valueText,
|
||||
})
|
||||
if (trap(astAfterReplacement)) {
|
||||
return Promise.reject(astAfterReplacement)
|
||||
}
|
||||
// The `replacer` function returns a pathToNode that assumes
|
||||
// an identifier is also being inserted into the AST, creating an off-by-one error.
|
||||
// This corrects that error, but TODO we should fix this upstream
|
||||
// to avoid this kind of error in the future.
|
||||
astAfterReplacement.pathToReplaced[1][0] =
|
||||
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
|
||||
result = astAfterReplacement
|
||||
}
|
||||
|
||||
pResult = parse(recast(result.modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(new Error('Unexpected compilation error'))
|
||||
parsed = pResult.program
|
||||
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
if (!result.pathToReplaced)
|
||||
return Promise.reject(new Error('No path to replaced node'))
|
||||
const {
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
} = updateSketchDetailsNodePaths({
|
||||
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
exprInsertIndex: result.exprInsertIndex,
|
||||
})
|
||||
|
||||
const updatedAst =
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
parsed,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||
updatedAst.newAst
|
||||
)
|
||||
|
||||
const selection = updateSelections(
|
||||
{ 0: result.pathToReplaced },
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
'set-up-draft-circle': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
@ -1090,6 +1608,38 @@ export const ModelingMachineProvider = ({
|
||||
return result
|
||||
}
|
||||
),
|
||||
'setup-client-side-sketch-segments': fromPromise(
|
||||
async ({ input: { sketchDetails, selectionRanges } }) => {
|
||||
if (!sketchDetails) return
|
||||
if (!sketchDetails.sketchEntryNodePath?.length) return
|
||||
sceneInfra.resetMouseListeners()
|
||||
await sceneEntitiesManager.setupSketch({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
selectionRanges,
|
||||
})
|
||||
sceneInfra.resetMouseListeners()
|
||||
|
||||
sceneEntitiesManager.setupSketchIdleCallbacks({
|
||||
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
|
||||
forward: sketchDetails.zAxis,
|
||||
up: sketchDetails.yAxis,
|
||||
position: sketchDetails.origin,
|
||||
sketchNodePaths: sketchDetails.sketchNodePaths,
|
||||
planeNodePath: sketchDetails.planeNodePath,
|
||||
// We will want to pass sketchTools here
|
||||
// to add their interactions
|
||||
})
|
||||
|
||||
// We will want to update the context with sketchTools.
|
||||
// They'll be used for their .destroy() in tearDownSketch
|
||||
return undefined
|
||||
}
|
||||
),
|
||||
'split-sketch-pipe-if-needed': fromPromise(
|
||||
async ({ input: { sketchDetails } }) => {
|
||||
if (!sketchDetails) return reject('No sketch details')
|
||||
|
@ -14,8 +14,13 @@ import { IS_ML_EXPERIMENTAL, PROJECT_ENTRYPOINT } from '@src/lib/constants'
|
||||
import toast from 'react-hot-toast'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
import { relevantFileExtensions } from '@src/lang/wasmUtils'
|
||||
import { getStringAfterLastSeparator, webSafePathSplit } from '@src/lib/paths'
|
||||
import {
|
||||
getStringAfterLastSeparator,
|
||||
joinOSPaths,
|
||||
webSafePathSplit,
|
||||
} from '@src/lib/paths'
|
||||
import { FILE_EXT } from '@src/lib/constants'
|
||||
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
|
||||
|
||||
function onSubmitKCLSampleCreation({
|
||||
sample,
|
||||
@ -87,6 +92,26 @@ function onSubmitKCLSampleCreation({
|
||||
},
|
||||
})
|
||||
} else {
|
||||
/**
|
||||
* When adding assemblies to an existing project create the assembly into a unique sub directory
|
||||
*/
|
||||
if (!isProjectNew) {
|
||||
requestedFiles.forEach((requestedFile) => {
|
||||
const subDirectoryName = projectPathPart
|
||||
const firstLevelDirectories = getAllSubDirectoriesAtProjectRoot({
|
||||
projectFolderName: requestedFile.requestedProjectName,
|
||||
})
|
||||
const uniqueSubDirectoryName = getUniqueProjectName(
|
||||
subDirectoryName,
|
||||
firstLevelDirectories
|
||||
)
|
||||
requestedFile.requestedProjectName = joinOSPaths(
|
||||
requestedFile.requestedProjectName,
|
||||
uniqueSubDirectoryName
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk create the assembly and navigate to the project
|
||||
*/
|
||||
@ -278,10 +303,9 @@ export function createApplicationCommands({
|
||||
return value
|
||||
},
|
||||
options: ({ argumentsToSubmit }) => {
|
||||
const samples =
|
||||
isDesktop() && argumentsToSubmit.method !== 'existingProject'
|
||||
? everyKclSample
|
||||
: kclSamplesManifestWithNoMultipleFiles
|
||||
const samples = isDesktop()
|
||||
? everyKclSample
|
||||
: kclSamplesManifestWithNoMultipleFiles
|
||||
return samples.map((sample) => {
|
||||
return {
|
||||
value: sample.pathFromProjectDirectoryToFirstFile,
|
||||
@ -296,17 +320,10 @@ export function createApplicationCommands({
|
||||
skip: true,
|
||||
options: ({ argumentsToSubmit }, _) => {
|
||||
if (isDesktop() && typeof argumentsToSubmit.sample === 'string') {
|
||||
const kclSample = findKclSample(argumentsToSubmit.sample)
|
||||
if (kclSample && kclSample.files.length > 1) {
|
||||
return [
|
||||
{ name: 'New project', value: 'newProject', isCurrent: true },
|
||||
]
|
||||
} else {
|
||||
return [
|
||||
{ name: 'New project', value: 'newProject', isCurrent: true },
|
||||
{ name: 'Existing project', value: 'existingProject' },
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ name: 'New project', value: 'newProject', isCurrent: true },
|
||||
{ name: 'Existing project', value: 'existingProject' },
|
||||
]
|
||||
} else {
|
||||
return [{ name: 'Overwrite', value: 'existingProject' }]
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import type { Selections } from '@src/lib/selections'
|
||||
import { codeManager, kclManager } from '@src/lib/singletons'
|
||||
import { err } from '@src/lib/trap'
|
||||
import type { SketchTool, modelingMachine } from '@src/machines/modelingMachine'
|
||||
import { isDesktop } from '../isDesktop'
|
||||
|
||||
type OutputFormat = Models['OutputFormat3d_type']
|
||||
type OutputTypeKey = OutputFormat['type']
|
||||
@ -159,6 +160,10 @@ export type ModelingCommandSchema = {
|
||||
nodeToEdit?: PathToNode
|
||||
color: string
|
||||
}
|
||||
Insert: {
|
||||
path: string
|
||||
localName: string
|
||||
}
|
||||
Translate: {
|
||||
nodeToEdit?: PathToNode
|
||||
selection: Selections
|
||||
@ -1011,6 +1016,74 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
// Add more fields
|
||||
},
|
||||
},
|
||||
Insert: {
|
||||
description: 'Insert from a file in the current project directory',
|
||||
icon: 'import',
|
||||
hide: 'web',
|
||||
needsReview: true,
|
||||
args: {
|
||||
path: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
// options
|
||||
validation: async ({ data }) => {
|
||||
const importExists = kclManager.ast.body.find(
|
||||
(n) =>
|
||||
n.type === 'ImportStatement' &&
|
||||
((n.path.type === 'Kcl' && n.path.filename === data.path) ||
|
||||
(n.path.type === 'Foreign' && n.path.path === data.path))
|
||||
)
|
||||
if (importExists) {
|
||||
return 'This file is already imported, use the Clone command instead.'
|
||||
// TODO: see if we can transition to the clone command, see #6515
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
localName: {
|
||||
inputType: 'string',
|
||||
required: true,
|
||||
defaultValue: (context: CommandBarContext) => {
|
||||
if (!context.argumentsToSubmit['path']) {
|
||||
return
|
||||
}
|
||||
|
||||
const path = context.argumentsToSubmit['path'] as string
|
||||
return getPathFilenameInVariableCase(path)
|
||||
},
|
||||
validation: async ({ data }) => {
|
||||
const variableExists = kclManager.variables[data.localName]
|
||||
if (variableExists) {
|
||||
return 'This variable name is already in use.'
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
},
|
||||
// onSubmit: (data) => {
|
||||
// if (!data) {
|
||||
// return new Error('No input provided')
|
||||
// }
|
||||
|
||||
// const ast = kclManager.ast
|
||||
// const { path, localName } = data
|
||||
// const { modifiedAst, pathToNode } = addModuleImport({
|
||||
// ast,
|
||||
// path,
|
||||
// localName,
|
||||
// })
|
||||
// updateModelingState(
|
||||
// modifiedAst,
|
||||
// EXECUTION_TYPE_REAL,
|
||||
// { kclManager, editorManager, codeManager },
|
||||
// {
|
||||
// focusPath: [pathToNode],
|
||||
// }
|
||||
// ).catch(reportRejection)
|
||||
// },
|
||||
},
|
||||
Translate: {
|
||||
description: 'Set translation on solid or sketch.',
|
||||
icon: 'move',
|
||||
|
@ -89,76 +89,76 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Insert',
|
||||
description: 'Insert from a file in the current project directory',
|
||||
icon: 'import',
|
||||
groupId: 'code',
|
||||
hide: 'web',
|
||||
needsReview: true,
|
||||
args: {
|
||||
path: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
options: commandProps.specialPropsForInsertCommand.providedOptions,
|
||||
validation: async ({ data }) => {
|
||||
const importExists = kclManager.ast.body.find(
|
||||
(n) =>
|
||||
n.type === 'ImportStatement' &&
|
||||
((n.path.type === 'Kcl' && n.path.filename === data.path) ||
|
||||
(n.path.type === 'Foreign' && n.path.path === data.path))
|
||||
)
|
||||
if (importExists) {
|
||||
return 'This file is already imported, use the Clone command instead.'
|
||||
// TODO: see if we can transition to the clone command, see #6515
|
||||
}
|
||||
// {
|
||||
// name: 'Insert',
|
||||
// description: 'Insert from a file in the current project directory',
|
||||
// icon: 'import',
|
||||
// groupId: 'code',
|
||||
// hide: 'web',
|
||||
// needsReview: true,
|
||||
// args: {
|
||||
// path: {
|
||||
// inputType: 'options',
|
||||
// required: true,
|
||||
// options: commandProps.specialPropsForInsertCommand.providedOptions,
|
||||
// validation: async ({ data }) => {
|
||||
// const importExists = kclManager.ast.body.find(
|
||||
// (n) =>
|
||||
// n.type === 'ImportStatement' &&
|
||||
// ((n.path.type === 'Kcl' && n.path.filename === data.path) ||
|
||||
// (n.path.type === 'Foreign' && n.path.path === data.path))
|
||||
// )
|
||||
// if (importExists) {
|
||||
// return 'This file is already imported, use the Clone command instead.'
|
||||
// // TODO: see if we can transition to the clone command, see #6515
|
||||
// }
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
localName: {
|
||||
inputType: 'string',
|
||||
required: true,
|
||||
defaultValue: (context: CommandBarContext) => {
|
||||
if (!context.argumentsToSubmit['path']) {
|
||||
return
|
||||
}
|
||||
// return true
|
||||
// },
|
||||
// },
|
||||
// localName: {
|
||||
// inputType: 'string',
|
||||
// required: true,
|
||||
// defaultValue: (context: CommandBarContext) => {
|
||||
// if (!context.argumentsToSubmit['path']) {
|
||||
// return
|
||||
// }
|
||||
|
||||
const path = context.argumentsToSubmit['path'] as string
|
||||
return getPathFilenameInVariableCase(path)
|
||||
},
|
||||
validation: async ({ data }) => {
|
||||
const variableExists = kclManager.variables[data.localName]
|
||||
if (variableExists) {
|
||||
return 'This variable name is already in use.'
|
||||
}
|
||||
// const path = context.argumentsToSubmit['path'] as string
|
||||
// return getPathFilenameInVariableCase(path)
|
||||
// },
|
||||
// validation: async ({ data }) => {
|
||||
// const variableExists = kclManager.variables[data.localName]
|
||||
// if (variableExists) {
|
||||
// return 'This variable name is already in use.'
|
||||
// }
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
},
|
||||
onSubmit: (data) => {
|
||||
if (!data) {
|
||||
return new Error('No input provided')
|
||||
}
|
||||
// return true
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// onSubmit: (data) => {
|
||||
// if (!data) {
|
||||
// return new Error('No input provided')
|
||||
// }
|
||||
|
||||
const ast = kclManager.ast
|
||||
const { path, localName } = data
|
||||
const { modifiedAst, pathToNode } = addModuleImport({
|
||||
ast,
|
||||
path,
|
||||
localName,
|
||||
})
|
||||
updateModelingState(
|
||||
modifiedAst,
|
||||
EXECUTION_TYPE_REAL,
|
||||
{ kclManager, editorManager, codeManager },
|
||||
{
|
||||
focusPath: [pathToNode],
|
||||
}
|
||||
).catch(reportRejection)
|
||||
},
|
||||
},
|
||||
// const ast = kclManager.ast
|
||||
// const { path, localName } = data
|
||||
// const { modifiedAst, pathToNode } = addModuleImport({
|
||||
// ast,
|
||||
// path,
|
||||
// localName,
|
||||
// })
|
||||
// updateModelingState(
|
||||
// modifiedAst,
|
||||
// EXECUTION_TYPE_REAL,
|
||||
// { kclManager, editorManager, codeManager },
|
||||
// {
|
||||
// focusPath: [pathToNode],
|
||||
// }
|
||||
// ).catch(reportRejection)
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: 'format-code',
|
||||
displayName: 'Format Code',
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { DEFAULT_DEFAULT_LENGTH_UNIT } from '@src/lib/constants'
|
||||
import { isPlaywright } from '@src/lib/isPlaywright'
|
||||
import {
|
||||
engineCommandManager,
|
||||
kclManager,
|
||||
sceneInfra,
|
||||
settingsActor,
|
||||
} from '@src/lib/singletons'
|
||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
|
||||
import {
|
||||
engineStreamZoomToFit,
|
||||
engineViewIsometricWithoutGeometryPresent,
|
||||
@ -27,15 +22,6 @@ export async function resetCameraPosition() {
|
||||
if (isPlaywright()) {
|
||||
await engineStreamZoomToFit({ engineCommandManager, padding })
|
||||
} else {
|
||||
// Get user camera projection
|
||||
const cameraProjection =
|
||||
settingsActor.getSnapshot().context.modeling.cameraProjection.current
|
||||
|
||||
// We need to keep the users projection setting when resetting their camera
|
||||
if (cameraProjection === 'perspective') {
|
||||
await sceneInfra.camControls.usePerspectiveCamera()
|
||||
}
|
||||
|
||||
// If the scene is empty you cannot use view_isometric, it will not move the camera
|
||||
if (kclManager.isAstBodyEmpty(kclManager.ast)) {
|
||||
await engineViewIsometricWithoutGeometryPresent({
|
||||
@ -43,7 +29,6 @@ export async function resetCameraPosition() {
|
||||
unit:
|
||||
kclManager.fileSettings.defaultLengthUnit ||
|
||||
DEFAULT_DEFAULT_LENGTH_UNIT,
|
||||
cameraProjection,
|
||||
})
|
||||
} else {
|
||||
await engineViewIsometricWithGeometryPresent({
|
||||
|
@ -12,7 +12,6 @@ import type {
|
||||
CameraViewState_type,
|
||||
UnitLength_type,
|
||||
} from '@kittycad/lib/dist/types/src/models'
|
||||
import type { CameraProjectionType } from '@rust/kcl-lib/bindings/CameraProjectionType'
|
||||
|
||||
export const uuidv4 = v4
|
||||
|
||||
@ -623,11 +622,9 @@ export async function engineViewIsometricWithGeometryPresent({
|
||||
export async function engineViewIsometricWithoutGeometryPresent({
|
||||
engineCommandManager,
|
||||
unit,
|
||||
cameraProjection,
|
||||
}: {
|
||||
engineCommandManager: EngineCommandManager
|
||||
unit?: UnitLength_type
|
||||
cameraProjection: CameraProjectionType
|
||||
}) {
|
||||
// If you load an empty scene with any file unit it will have an eye offset of this
|
||||
const MAGIC_ENGINE_EYE_OFFSET = 1378.0057
|
||||
@ -647,8 +644,8 @@ export async function engineViewIsometricWithoutGeometryPresent({
|
||||
eye_offset: MAGIC_ENGINE_EYE_OFFSET,
|
||||
fov_y: 45,
|
||||
ortho_scale_factor: 1.4063792,
|
||||
is_ortho: cameraProjection !== 'perspective',
|
||||
ortho_scale_enabled: cameraProjection !== 'perspective',
|
||||
is_ortho: true,
|
||||
ortho_scale_enabled: true,
|
||||
world_coord_system: 'right_handed_up_z',
|
||||
}
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
|
@ -1,245 +0,0 @@
|
||||
import {
|
||||
modelingMachine,
|
||||
modelingMachineDefaultContext,
|
||||
} from '@src/machines/modelingMachine'
|
||||
import { createActor } from 'xstate'
|
||||
import { vi } from 'vitest'
|
||||
import { assertParse, type CallExpressionKw } from '@src/lang/wasm'
|
||||
import { initPromise } from '@src/lang/wasmUtils'
|
||||
import {
|
||||
codeManager,
|
||||
engineCommandManager,
|
||||
kclManager,
|
||||
} from '@src/lib/singletons'
|
||||
import { VITE_KC_DEV_TOKEN } from '@src/env'
|
||||
import { line } from '@src/lang/std/sketch'
|
||||
import { getNodeFromPath } from '@src/lang/queryAst'
|
||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||
import { err } from '@src/lib/trap'
|
||||
import {
|
||||
createIdentifier,
|
||||
createLiteral,
|
||||
createVariableDeclaration,
|
||||
} from '@src/lang/create'
|
||||
|
||||
// Store original method to restore in afterAll
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
|
||||
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
||||
await new Promise((resolve) => {
|
||||
engineCommandManager.start({
|
||||
token: VITE_KC_DEV_TOKEN,
|
||||
width: 256,
|
||||
height: 256,
|
||||
setMediaStream: () => {},
|
||||
setIsStreamReady: () => {},
|
||||
callbackOnEngineLiteConnect: () => {
|
||||
resolve(true)
|
||||
},
|
||||
})
|
||||
})
|
||||
}, 30_000)
|
||||
|
||||
afterAll(() => {
|
||||
// Restore the original method
|
||||
|
||||
engineCommandManager.tearDown()
|
||||
})
|
||||
|
||||
// Define mock implementations that will be referenced in vi.mock calls
|
||||
vi.mock('@src/components/SetHorVertDistanceModal', () => ({
|
||||
createInfoModal: vi.fn(() => ({
|
||||
open: vi.fn().mockResolvedValue({
|
||||
value: '10',
|
||||
segName: 'test',
|
||||
valueNode: {},
|
||||
newVariableInsertIndex: 0,
|
||||
sign: 1,
|
||||
}),
|
||||
})),
|
||||
GetInfoModal: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@src/components/SetAngleLengthModal', () => ({
|
||||
createSetAngleLengthModal: vi.fn(() => ({
|
||||
open: vi.fn().mockResolvedValue({
|
||||
value: '45',
|
||||
segName: 'test',
|
||||
valueNode: {},
|
||||
newVariableInsertIndex: 0,
|
||||
sign: 1,
|
||||
}),
|
||||
})),
|
||||
SetAngleLengthModal: vi.fn(),
|
||||
}))
|
||||
|
||||
// Add this function before the test cases
|
||||
// Utility function to wait for a condition to be met
|
||||
const waitForCondition = async (
|
||||
condition: () => boolean,
|
||||
timeout = 5000,
|
||||
interval = 100
|
||||
) => {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
try {
|
||||
if (condition()) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors, keep polling
|
||||
}
|
||||
|
||||
// Wait for the next interval
|
||||
await new Promise((resolve) => setTimeout(resolve, interval))
|
||||
}
|
||||
|
||||
// Last attempt before failing
|
||||
return condition()
|
||||
}
|
||||
|
||||
describe('modelingMachine - XState', () => {
|
||||
describe('when initialized', () => {
|
||||
it('should start in the idle state', () => {
|
||||
const actor = createActor(modelingMachine, {
|
||||
input: modelingMachineDefaultContext,
|
||||
}).start()
|
||||
const state = actor.getSnapshot().value
|
||||
|
||||
// The machine should start in the idle state
|
||||
expect(state).toEqual({ idle: 'hidePlanes' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('when in sketch mode', () => {
|
||||
it('should transition to sketch state when entering sketch mode', async () => {
|
||||
const code = `sketch001 = startSketchOn(XZ)
|
||||
profile001 = startProfile(sketch001, at = [2263.04, -2721.2])
|
||||
|> line(end = [16.27, 73.81])
|
||||
|> line(end = [75.72, 18.41])
|
||||
`
|
||||
|
||||
const ast = assertParse(code)
|
||||
|
||||
await kclManager.executeAst({ ast })
|
||||
|
||||
expect(kclManager.errors).toEqual([])
|
||||
|
||||
const indexOfInterest = code.indexOf('[16.27, 73.81]')
|
||||
|
||||
// segment artifact with that source range
|
||||
const artifact = [...kclManager.artifactGraph].find(
|
||||
([_, artifact]) =>
|
||||
artifact?.type === 'segment' &&
|
||||
artifact.codeRef.range[0] <= indexOfInterest &&
|
||||
indexOfInterest <= artifact.codeRef.range[1]
|
||||
)?.[1]
|
||||
if (!artifact || !('codeRef' in artifact)) {
|
||||
throw new Error('Artifact not found or invalid artifact structure')
|
||||
}
|
||||
|
||||
const actor = createActor(modelingMachine, {
|
||||
input: modelingMachineDefaultContext,
|
||||
}).start()
|
||||
|
||||
// Send event to transition to sketch mode
|
||||
actor.send({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'mirrorCodeMirrorSelections',
|
||||
selection: {
|
||||
graphSelections: [
|
||||
{
|
||||
artifact: artifact,
|
||||
codeRef: artifact.codeRef,
|
||||
},
|
||||
],
|
||||
otherSelections: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
actor.send({ type: 'Enter sketch' })
|
||||
|
||||
// Check that we're in the sketch state
|
||||
let state = actor.getSnapshot()
|
||||
expect(state.value).toBe('animating to existing sketch')
|
||||
|
||||
// wait for it to transition
|
||||
await waitForCondition(() => {
|
||||
const snapshot = actor.getSnapshot()
|
||||
return snapshot.value !== 'animating to existing sketch'
|
||||
}, 5000)
|
||||
|
||||
// After the condition is met, do the actual assertion
|
||||
expect(actor.getSnapshot().value).toEqual({
|
||||
Sketch: { SketchIdle: 'scene drawn' },
|
||||
})
|
||||
|
||||
const getConstraintInfo = line.getConstraintInfo
|
||||
const callExp = getNodeFromPath<Node<CallExpressionKw>>(
|
||||
kclManager.ast,
|
||||
artifact.codeRef.pathToNode,
|
||||
'CallExpressionKw'
|
||||
)
|
||||
if (err(callExp)) {
|
||||
throw new Error('Failed to get CallExpressionKw node')
|
||||
}
|
||||
const constraintInfo = getConstraintInfo(
|
||||
callExp.node,
|
||||
codeManager.code,
|
||||
artifact.codeRef.pathToNode
|
||||
)
|
||||
const first = constraintInfo[0]
|
||||
|
||||
// Now that we're in sketchIdle state, test the "Constrain with named value" event
|
||||
actor.send({
|
||||
type: 'Constrain with named value',
|
||||
data: {
|
||||
currentValue: {
|
||||
valueText: first.value,
|
||||
pathToNode: first.pathToNode,
|
||||
variableName: 'test_variable',
|
||||
},
|
||||
// Use type assertion to mock the complex type
|
||||
namedValue: {
|
||||
valueText: '20',
|
||||
variableName: 'test_variable',
|
||||
insertIndex: 0,
|
||||
valueCalculated: '20',
|
||||
variableDeclarationAst: createVariableDeclaration(
|
||||
'test_variable',
|
||||
createLiteral('20')
|
||||
),
|
||||
variableIdentifierAst: createIdentifier('test_variable') as any,
|
||||
valueAst: createLiteral('20'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Wait for the state to change in response to the constraint
|
||||
await waitForCondition(() => {
|
||||
const snapshot = actor.getSnapshot()
|
||||
// Check if we've transitioned to a different state
|
||||
return (
|
||||
JSON.stringify(snapshot.value) !==
|
||||
JSON.stringify({
|
||||
Sketch: { SketchIdle: 'set up segments' },
|
||||
})
|
||||
)
|
||||
}, 5000)
|
||||
|
||||
await waitForCondition(() => {
|
||||
const snapshot = actor.getSnapshot()
|
||||
// Check if we've transitioned to a different state
|
||||
return (
|
||||
JSON.stringify(snapshot.value) !==
|
||||
JSON.stringify({ Sketch: 'Converting to named value' })
|
||||
)
|
||||
}, 5000)
|
||||
expect(codeManager.code).toContain('line(end = [test_variable,')
|
||||
}, 10_000)
|
||||
})
|
||||
})
|
File diff suppressed because one or more lines are too long
@ -1,4 +1,6 @@
|
||||
import type { FileEntry } from '@src/lib/project'
|
||||
import { systemIOActor } from '@src/lib/singletons'
|
||||
import { isArray } from '@src/lib/utils'
|
||||
|
||||
export const folderSnapshot = () => {
|
||||
const { folders } = systemIOActor.getSnapshot().context
|
||||
@ -9,3 +11,48 @@ export const defaultProjectFolderNameSnapshot = () => {
|
||||
const { defaultProjectFolderName } = systemIOActor.getSnapshot().context
|
||||
return defaultProjectFolderName
|
||||
}
|
||||
|
||||
/**
|
||||
* From the application project directory go down to a project folder and list all the folders at that directory level
|
||||
* application project directory: /home/documents/zoo-modeling-app-projects/
|
||||
*
|
||||
* /home/documents/zoo-modeling-app-projects/car-door/
|
||||
* ├── handle
|
||||
* ├── main.kcl
|
||||
* └── window
|
||||
*
|
||||
* The two folders are handle and window
|
||||
*
|
||||
* @param {Object} params
|
||||
* @param {string} params.projectFolderName - The name with no path information.
|
||||
* @returns {FileEntry[]} An array of subdirectory names found at the root level of the specified project folder.
|
||||
*/
|
||||
export const getAllSubDirectoriesAtProjectRoot = ({
|
||||
projectFolderName,
|
||||
}: { projectFolderName: string }): FileEntry[] => {
|
||||
const subDirectories: FileEntry[] = []
|
||||
const { folders } = systemIOActor.getSnapshot().context
|
||||
|
||||
const projectFolder = folders.find((folder) => {
|
||||
return folder.name === projectFolderName
|
||||
})
|
||||
|
||||
// Find the subdirectories
|
||||
if (projectFolder) {
|
||||
// 1st level
|
||||
const children = projectFolder.children
|
||||
if (children) {
|
||||
children.forEach((childFileOrDirectory) => {
|
||||
// 2nd level
|
||||
const secondLevelChild = childFileOrDirectory.children
|
||||
// if secondLevelChild is null then it is a file
|
||||
if (secondLevelChild && isArray(secondLevelChild)) {
|
||||
// this is a directory!
|
||||
subDirectories.push(childFileOrDirectory)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return subDirectories
|
||||
}
|
||||
|
Reference in New Issue
Block a user