Compare commits
1 Commits
kurt-contr
...
jtran/plus
Author | SHA1 | Date | |
---|---|---|---|
4de0b57ea4 |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
18
package-lock.json
generated
@ -43,7 +43,6 @@
|
|||||||
"bson": "^6.10.3",
|
"bson": "^6.10.3",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"culori": "^4.0.2",
|
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
@ -93,7 +92,6 @@
|
|||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/culori": "^4.0.0",
|
|
||||||
"@types/diff": "^7.0.2",
|
"@types/diff": "^7.0.2",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/hammerjs": "^2.0.46",
|
"@types/hammerjs": "^2.0.46",
|
||||||
@ -7394,13 +7392,6 @@
|
|||||||
"integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
|
"integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/culori": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/culori/-/culori-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-aFljQwjb++sl6TAyEXeHTiK/fk9epZOQ+nMmadjnAvzZFIvNoQ0x8XQYfcOaRTBwmDUPUlghhZCJ66MTcqQAsg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||||
@ -11832,15 +11823,6 @@
|
|||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/culori": {
|
|
||||||
"version": "4.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/culori/-/culori-4.0.2.tgz",
|
|
||||||
"integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
"bson": "^6.10.3",
|
"bson": "^6.10.3",
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"culori": "^4.0.2",
|
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"electron-updater": "^6.6.2",
|
"electron-updater": "^6.6.2",
|
||||||
@ -171,7 +170,6 @@
|
|||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^15.0.7",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@types/culori": "^4.0.0",
|
|
||||||
"@types/diff": "^7.0.2",
|
"@types/diff": "^7.0.2",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/hammerjs": "^2.0.46",
|
"@types/hammerjs": "^2.0.46",
|
||||||
|
@ -1086,6 +1086,32 @@ impl Node<MemberExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn concat(left: &[KclValue], left_el_ty: &RuntimeType, right: &[KclValue], right_el_ty: &RuntimeType) -> KclValue {
|
||||||
|
if left.is_empty() {
|
||||||
|
return KclValue::HomArray {
|
||||||
|
value: right.to_vec(),
|
||||||
|
ty: right_el_ty.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if right.is_empty() {
|
||||||
|
return KclValue::HomArray {
|
||||||
|
value: left.to_vec(),
|
||||||
|
ty: left_el_ty.clone(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let mut new = left.to_vec();
|
||||||
|
new.extend_from_slice(right);
|
||||||
|
// Propagate the element type if we can.
|
||||||
|
let ty = if right_el_ty.subtype(left_el_ty) {
|
||||||
|
left_el_ty.clone()
|
||||||
|
} else if left_el_ty.subtype(right_el_ty) {
|
||||||
|
right_el_ty.clone()
|
||||||
|
} else {
|
||||||
|
RuntimeType::any()
|
||||||
|
};
|
||||||
|
KclValue::HomArray { value: new, ty }
|
||||||
|
}
|
||||||
|
|
||||||
impl Node<BinaryExpression> {
|
impl Node<BinaryExpression> {
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
@ -1104,6 +1130,50 @@ impl Node<BinaryExpression> {
|
|||||||
meta,
|
meta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Array plus is concatenation.
|
||||||
|
match (&left_value, &right_value) {
|
||||||
|
(
|
||||||
|
KclValue::HomArray {
|
||||||
|
value: left,
|
||||||
|
ty: left_el_ty,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
KclValue::HomArray {
|
||||||
|
value: right,
|
||||||
|
ty: right_el_ty,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
return Ok(concat(left, left_el_ty, right, right_el_ty));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
KclValue::HomArray {
|
||||||
|
value: left,
|
||||||
|
ty: left_el_ty,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
// Any single value can be coerced to an array.
|
||||||
|
let right = vec![right_value.clone()];
|
||||||
|
let right_el_ty = RuntimeType::any();
|
||||||
|
return Ok(concat(left, left_el_ty, &right, &right_el_ty));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
_,
|
||||||
|
KclValue::HomArray {
|
||||||
|
value: right,
|
||||||
|
ty: right_el_ty,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
// Any single value can be coerced to an array.
|
||||||
|
let left = vec![left_value.clone()];
|
||||||
|
let left_el_ty = RuntimeType::any();
|
||||||
|
return Ok(concat(&left, &left_el_ty, right, right_el_ty));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then check if we have solids.
|
// Then check if we have solids.
|
||||||
|
@ -246,7 +246,7 @@ impl RuntimeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subtype with no coercion, including refining numeric types.
|
// Subtype with no coercion, including refining numeric types.
|
||||||
fn subtype(&self, sup: &RuntimeType) -> bool {
|
pub(super) fn subtype(&self, sup: &RuntimeType) -> bool {
|
||||||
use RuntimeType::*;
|
use RuntimeType::*;
|
||||||
|
|
||||||
match (self, sup) {
|
match (self, sup) {
|
||||||
|
@ -14,7 +14,7 @@ description: Result of parsing add_arrays.kcl
|
|||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
"end": 0,
|
"end": 0,
|
||||||
"moduleId": 0,
|
"moduleId": 0,
|
||||||
"name": "answer",
|
"name": "a",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
@ -96,6 +96,170 @@ description: Result of parsing add_arrays.kcl
|
|||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclaration",
|
"type": "VariableDeclaration",
|
||||||
"type": "VariableDeclaration"
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"name": "b",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"left": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 1.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
"moduleId": 0,
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "2",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 2.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"name": "c",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"left": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"moduleId": 0,
|
||||||
|
"operator": "+",
|
||||||
|
"right": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "1",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 1.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"raw": "2",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 2.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "BinaryExpression",
|
||||||
|
"type": "BinaryExpression"
|
||||||
|
},
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"moduleId": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"commentStart": 0,
|
"commentStart": 0,
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
source: kcl-lib/src/simulation_tests.rs
|
|
||||||
description: Error from executing add_arrays.kcl
|
|
||||||
---
|
|
||||||
KCL Semantic error
|
|
||||||
|
|
||||||
× semantic: Expected a number, but found an array of `number`, `number`
|
|
||||||
╭────
|
|
||||||
1 │ answer = [0, 1] + [2]
|
|
||||||
· ───┬──
|
|
||||||
· ╰── tests/add_arrays/input.kcl
|
|
||||||
╰────
|
|
@ -1 +1,3 @@
|
|||||||
answer = [0, 1] + [2]
|
a = [0, 1] + [2]
|
||||||
|
b = [0, 1] + 2
|
||||||
|
c = 0 + [1, 2]
|
||||||
|
138
rust/kcl-lib/tests/add_arrays/program_memory.snap
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Variables in memory after executing add_arrays.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"a": {
|
||||||
|
"type": "HomArray",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 1.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 2.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"type": "HomArray",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 1.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 2.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"type": "HomArray",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 1.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Number",
|
||||||
|
"value": 2.0,
|
||||||
|
"ty": {
|
||||||
|
"type": "Default",
|
||||||
|
"len": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "Degrees"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -2,4 +2,6 @@
|
|||||||
source: kcl-lib/src/simulation_tests.rs
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
description: Result of unparsing add_arrays.kcl
|
description: Result of unparsing add_arrays.kcl
|
||||||
---
|
---
|
||||||
answer = [0, 1] + [2]
|
a = [0, 1] + [2]
|
||||||
|
b = [0, 1] + 2
|
||||||
|
c = 0 + [1, 2]
|
||||||
|
@ -324,8 +324,6 @@ export class SceneEntities {
|
|||||||
group: segment,
|
group: segment,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
sceneInfra: this.sceneInfra,
|
sceneInfra: this.sceneInfra,
|
||||||
// Note: AST and code not available in onCamChange, so constraints won't be checked here
|
|
||||||
// This is primarily for scaling changes
|
|
||||||
})
|
})
|
||||||
callBack && !err(callBack) && callbacks.push(callBack)
|
callBack && !err(callBack) && callbacks.push(callBack)
|
||||||
if (segment.name === PROFILE_START) {
|
if (segment.name === PROFILE_START) {
|
||||||
@ -731,8 +729,6 @@ export class SceneEntities {
|
|||||||
scale,
|
scale,
|
||||||
theme: this.sceneInfra._theme,
|
theme: this.sceneInfra._theme,
|
||||||
isDraft: false,
|
isDraft: false,
|
||||||
ast: maybeModdedAst,
|
|
||||||
code: this.codeManager.code,
|
|
||||||
})
|
})
|
||||||
_profileStart.layers.set(SKETCH_LAYER)
|
_profileStart.layers.set(SKETCH_LAYER)
|
||||||
_profileStart.traverse((child) => {
|
_profileStart.traverse((child) => {
|
||||||
@ -870,8 +866,6 @@ export class SceneEntities {
|
|||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra: this.sceneInfra,
|
sceneInfra: this.sceneInfra,
|
||||||
selection,
|
selection,
|
||||||
ast: maybeModdedAst,
|
|
||||||
code: this.codeManager.code,
|
|
||||||
})
|
})
|
||||||
if (err(result)) return
|
if (err(result)) return
|
||||||
const { group: _group, updateOverlaysCallback } = result
|
const { group: _group, updateOverlaysCallback } = result
|
||||||
@ -3258,8 +3252,6 @@ export class SceneEntities {
|
|||||||
scale: factor,
|
scale: factor,
|
||||||
prevSegment: sgPaths[index - 1],
|
prevSegment: sgPaths[index - 1],
|
||||||
sceneInfra: this.sceneInfra,
|
sceneInfra: this.sceneInfra,
|
||||||
ast: modifiedAst,
|
|
||||||
code: this.codeManager.code,
|
|
||||||
})
|
})
|
||||||
if (callBack && !err(callBack)) return callBack
|
if (callBack && !err(callBack)) return callBack
|
||||||
|
|
||||||
|
@ -76,17 +76,12 @@ import {
|
|||||||
} from '@src/clientSideScene/sceneUtils'
|
} from '@src/clientSideScene/sceneUtils'
|
||||||
import { angleLengthInfo } from '@src/components/Toolbar/angleLengthInfo'
|
import { angleLengthInfo } from '@src/components/Toolbar/angleLengthInfo'
|
||||||
import type { Coords2d } from '@src/lang/std/sketch'
|
import type { Coords2d } from '@src/lang/std/sketch'
|
||||||
import { getConstraintInfoKw } from '@src/lang/std/sketch'
|
|
||||||
import type { SegmentInputs } from '@src/lang/std/stdTypes'
|
import type { SegmentInputs } from '@src/lang/std/stdTypes'
|
||||||
import type { PathToNode, Program } from '@src/lang/wasm'
|
import type { PathToNode } from '@src/lang/wasm'
|
||||||
import { getTangentialArcToInfo } from '@src/lang/wasm'
|
import { getTangentialArcToInfo } from '@src/lang/wasm'
|
||||||
import { getNodeFromPath } from '@src/lang/queryAst'
|
|
||||||
import type { Selections } from '@src/lib/selections'
|
import type { Selections } from '@src/lib/selections'
|
||||||
import type { Themes } from '@src/lib/theme'
|
import type { Themes } from '@src/lib/theme'
|
||||||
import {
|
import { getThemeColorForThreeJs } from '@src/lib/theme'
|
||||||
getThemeColorForThreeJs,
|
|
||||||
getPrimaryColorForThreeJs,
|
|
||||||
} from '@src/lib/theme'
|
|
||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils'
|
import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils'
|
||||||
import { getTangentPointFromPreviousArc } from '@src/lib/utils2d'
|
import { getTangentPointFromPreviousArc } from '@src/lib/utils2d'
|
||||||
@ -100,7 +95,6 @@ import toast from 'react-hot-toast'
|
|||||||
import { ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants'
|
import { ARG_INTERIOR_ABSOLUTE } from '@src/lang/constants'
|
||||||
|
|
||||||
const ANGLE_INDICATOR_RADIUS = 30 // in px
|
const ANGLE_INDICATOR_RADIUS = 30 // in px
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
prevSegment: Sketch['paths'][number]
|
prevSegment: Sketch['paths'][number]
|
||||||
@ -114,9 +108,6 @@ interface CreateSegmentArgs {
|
|||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra
|
||||||
selection?: Selections
|
selection?: Selections
|
||||||
// Add optional AST and code for constraint checking
|
|
||||||
ast?: Program
|
|
||||||
code?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSegmentArgs {
|
interface UpdateSegmentArgs {
|
||||||
@ -125,9 +116,6 @@ interface UpdateSegmentArgs {
|
|||||||
group: Group
|
group: Group
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra
|
||||||
scale?: number
|
scale?: number
|
||||||
// Add optional AST and code for constraint checking
|
|
||||||
ast?: Program
|
|
||||||
code?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateSegmentResult {
|
interface CreateSegmentResult {
|
||||||
@ -156,55 +144,6 @@ export interface SegmentUtils {
|
|||||||
) => CreateSegmentResult['updateOverlaysCallback'] | Error
|
) => CreateSegmentResult['updateOverlaysCallback'] | Error
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a segment is fully constrained by examining all its constraint info
|
|
||||||
*/
|
|
||||||
function isSegmentFullyConstrained(
|
|
||||||
pathToNode: PathToNode,
|
|
||||||
ast: Program,
|
|
||||||
code: string
|
|
||||||
): boolean {
|
|
||||||
try {
|
|
||||||
const nodeMeta = getNodeFromPath<any>(ast, pathToNode)
|
|
||||||
if (err(nodeMeta) || nodeMeta.node.type !== 'CallExpressionKw') {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const constraintInfos = getConstraintInfoKw(nodeMeta.node, code, pathToNode)
|
|
||||||
|
|
||||||
// If there are no constraints, consider it not fully constrained
|
|
||||||
if (constraintInfos.length === 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all constraints are constrained
|
|
||||||
return constraintInfos.every((info) => info.isConstrained)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Error checking segment constraints:', error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the appropriate color for a segment based on selection, constraints, and theme
|
|
||||||
*/
|
|
||||||
function getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected,
|
|
||||||
callExpName = '',
|
|
||||||
isFullyConstrained = false,
|
|
||||||
}: {
|
|
||||||
theme: Themes
|
|
||||||
isSelected: boolean
|
|
||||||
callExpName?: string
|
|
||||||
isFullyConstrained: boolean
|
|
||||||
}): number {
|
|
||||||
if (isSelected) return 0x0000ff // Blue for selected
|
|
||||||
if (callExpName === 'close') return 0x444444 // Gray for close segments
|
|
||||||
if (!isFullyConstrained) return getPrimaryColorForThreeJs() // Primary color for unconstrained segments
|
|
||||||
return getThemeColorForThreeJs(theme) // Default theme color for constrained segments
|
|
||||||
}
|
|
||||||
|
|
||||||
class StraightSegment implements SegmentUtils {
|
class StraightSegment implements SegmentUtils {
|
||||||
init: SegmentUtils['init'] = ({
|
init: SegmentUtils['init'] = ({
|
||||||
input,
|
input,
|
||||||
@ -219,32 +158,13 @@ class StraightSegment implements SegmentUtils {
|
|||||||
sceneInfra,
|
sceneInfra,
|
||||||
prevSegment,
|
prevSegment,
|
||||||
selection,
|
selection,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
const { from, to } = input
|
const { from, to } = input
|
||||||
|
const baseColor =
|
||||||
// Check if segment is fully constrained (only if we have AST and code)
|
callExpName === 'close' ? 0x444444 : getThemeColorForThreeJs(theme)
|
||||||
const isFullyConstrained =
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
callExpName === 'close'
|
|
||||||
? true
|
|
||||||
: ast && code
|
|
||||||
? isSegmentFullyConstrained(pathToNode, ast, code)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
callExpName,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: callExpName === 'close'
|
|
||||||
? 0x444444
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
const meshType = isDraftSegment
|
const meshType = isDraftSegment
|
||||||
? STRAIGHT_SEGMENT_DASH
|
? STRAIGHT_SEGMENT_DASH
|
||||||
: STRAIGHT_SEGMENT_BODY
|
: STRAIGHT_SEGMENT_BODY
|
||||||
@ -330,40 +250,12 @@ class StraightSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
const { from, to } = input
|
const { from, to } = input
|
||||||
group.userData.from = from
|
group.userData.from = from
|
||||||
group.userData.to = to
|
group.userData.to = to
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained =
|
|
||||||
group.userData.callExpName === 'close'
|
|
||||||
? true
|
|
||||||
: isSegmentFullyConstrained(pathToNode, ast, code)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
callExpName: group.userData.callExpName,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const straightSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
straightSegmentBody &&
|
|
||||||
straightSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
straightSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const shape = createLineShape(scale)
|
const shape = createLineShape(scale)
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
|
const labelGroup = group.getObjectByName(SEGMENT_LENGTH_LABEL) as Group
|
||||||
@ -499,8 +391,6 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
theme,
|
theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -519,19 +409,8 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
isDashed: isDraftSegment,
|
isDashed: isDraftSegment,
|
||||||
scale,
|
scale,
|
||||||
})
|
})
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
// Check if segment is fully constrained (only if we have AST and code)
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const isFullyConstrained =
|
|
||||||
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
const body = new MeshBasicMaterial({ color })
|
const body = new MeshBasicMaterial({ color })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
const arrowGroup = createArrowhead(scale, theme, color)
|
const arrowGroup = createArrowhead(scale, theme, color)
|
||||||
@ -574,8 +453,6 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -583,32 +460,6 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
group.userData.from = from
|
group.userData.from = from
|
||||||
group.userData.to = to
|
group.userData.to = to
|
||||||
group.userData.prevSegment = prevSegment
|
group.userData.prevSegment = prevSegment
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained = isSegmentFullyConstrained(
|
|
||||||
pathToNode,
|
|
||||||
ast,
|
|
||||||
code
|
|
||||||
)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const tangentialArcSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
tangentialArcSegmentBody &&
|
|
||||||
tangentialArcSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
tangentialArcSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
|
|
||||||
@ -737,26 +588,13 @@ class CircleSegment implements SegmentUtils {
|
|||||||
theme,
|
theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'arc-segment') {
|
if (input.type !== 'arc-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
}
|
}
|
||||||
const { from, center, radius } = input
|
const { from, center, radius } = input
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
// Check if segment is fully constrained
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const isFullyConstrained =
|
|
||||||
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
|
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
const geometry = createArcGeometry({
|
const geometry = createArcGeometry({
|
||||||
@ -840,8 +678,6 @@ class CircleSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'arc-segment') {
|
if (input.type !== 'arc-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -851,32 +687,6 @@ class CircleSegment implements SegmentUtils {
|
|||||||
group.userData.center = center
|
group.userData.center = center
|
||||||
group.userData.radius = radius
|
group.userData.radius = radius
|
||||||
group.userData.prevSegment = prevSegment
|
group.userData.prevSegment = prevSegment
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained = isSegmentFullyConstrained(
|
|
||||||
pathToNode,
|
|
||||||
ast,
|
|
||||||
code
|
|
||||||
)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const circleSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === CIRCLE_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
circleSegmentBody &&
|
|
||||||
circleSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
circleSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
const radiusLengthIndicator = group.getObjectByName(
|
const radiusLengthIndicator = group.getObjectByName(
|
||||||
SEGMENT_LENGTH_LABEL
|
SEGMENT_LENGTH_LABEL
|
||||||
@ -1021,8 +831,6 @@ class CircleThreePointSegment implements SegmentUtils {
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
prevSegment,
|
prevSegment,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'circle-three-point-segment') {
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -1037,19 +845,8 @@ class CircleThreePointSegment implements SegmentUtils {
|
|||||||
p3[1]
|
p3[1]
|
||||||
)
|
)
|
||||||
const center: [number, number] = [center_x, center_y]
|
const center: [number, number] = [center_x, center_y]
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
// Check if segment is fully constrained
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const isFullyConstrained =
|
|
||||||
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
|
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
const geometry = createArcGeometry({
|
const geometry = createArcGeometry({
|
||||||
@ -1122,8 +919,6 @@ class CircleThreePointSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'circle-three-point-segment') {
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -1132,32 +927,6 @@ class CircleThreePointSegment implements SegmentUtils {
|
|||||||
group.userData.p1 = p1
|
group.userData.p1 = p1
|
||||||
group.userData.p2 = p2
|
group.userData.p2 = p2
|
||||||
group.userData.p3 = p3
|
group.userData.p3 = p3
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained = isSegmentFullyConstrained(
|
|
||||||
pathToNode,
|
|
||||||
ast,
|
|
||||||
code
|
|
||||||
)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const circleSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === CIRCLE_THREE_POINT_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
circleSegmentBody &&
|
|
||||||
circleSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
circleSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||||
p1[0],
|
p1[0],
|
||||||
p1[1],
|
p1[1],
|
||||||
@ -1279,26 +1048,13 @@ class ArcSegment implements SegmentUtils {
|
|||||||
theme,
|
theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'arc-segment') {
|
if (input.type !== 'arc-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
}
|
}
|
||||||
const { from, to, center, radius, ccw } = input
|
const { from, to, center, radius, ccw } = input
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
// Check if segment is fully constrained
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const isFullyConstrained =
|
|
||||||
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
|
|
||||||
// Calculate start and end angles
|
// Calculate start and end angles
|
||||||
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
||||||
@ -1439,8 +1195,6 @@ class ArcSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'arc-segment') {
|
if (input.type !== 'arc-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -1453,32 +1207,6 @@ class ArcSegment implements SegmentUtils {
|
|||||||
group.userData.ccw = ccw
|
group.userData.ccw = ccw
|
||||||
group.userData.prevSegment = prevSegment
|
group.userData.prevSegment = prevSegment
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained = isSegmentFullyConstrained(
|
|
||||||
pathToNode,
|
|
||||||
ast,
|
|
||||||
code
|
|
||||||
)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const arcSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === ARC_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
arcSegmentBody &&
|
|
||||||
arcSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
arcSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate start and end angles
|
// Calculate start and end angles
|
||||||
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
||||||
const endAngle = Math.atan2(to[1] - center[1], to[0] - center[0])
|
const endAngle = Math.atan2(to[1] - center[1], to[0] - center[0])
|
||||||
@ -1675,8 +1403,6 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
prevSegment,
|
prevSegment,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'circle-three-point-segment') {
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -1691,19 +1417,8 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
p3[1]
|
p3[1]
|
||||||
)
|
)
|
||||||
const center: [number, number] = [center_x, center_y]
|
const center: [number, number] = [center_x, center_y]
|
||||||
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
// Check if segment is fully constrained
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
const isFullyConstrained =
|
|
||||||
ast && code ? isSegmentFullyConstrained(pathToNode, ast, code) : false
|
|
||||||
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme,
|
|
||||||
isSelected: !!isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
|
|
||||||
// Calculate start and end angles
|
// Calculate start and end angles
|
||||||
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
||||||
@ -1785,8 +1500,6 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
group,
|
group,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'circle-three-point-segment') {
|
if (input.type !== 'circle-three-point-segment') {
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -1810,32 +1523,6 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
group.userData.radius = radius
|
group.userData.radius = radius
|
||||||
group.userData.prevSegment = prevSegment
|
group.userData.prevSegment = prevSegment
|
||||||
|
|
||||||
// Check if segment is fully constrained and update color if needed
|
|
||||||
if (ast && code) {
|
|
||||||
const pathToNode = group.userData.pathToNode
|
|
||||||
const isFullyConstrained = isSegmentFullyConstrained(
|
|
||||||
pathToNode,
|
|
||||||
ast,
|
|
||||||
code
|
|
||||||
)
|
|
||||||
const color = getSegmentColor({
|
|
||||||
theme: sceneInfra._theme,
|
|
||||||
isSelected: group.userData.isSelected,
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update the material color
|
|
||||||
const arcSegmentBody = group.children.find(
|
|
||||||
(child) => child.userData.type === THREE_POINT_ARC_SEGMENT_BODY
|
|
||||||
) as Mesh
|
|
||||||
if (
|
|
||||||
arcSegmentBody &&
|
|
||||||
arcSegmentBody.material instanceof MeshBasicMaterial
|
|
||||||
) {
|
|
||||||
arcSegmentBody.material.color.set(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate start and end angles
|
// Calculate start and end angles
|
||||||
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
||||||
const endAngle = Math.atan2(p3[1] - center[1], p3[0] - center[0])
|
const endAngle = Math.atan2(p3[1] - center[1], p3[0] - center[0])
|
||||||
@ -1932,8 +1619,6 @@ export function createProfileStartHandle({
|
|||||||
theme,
|
theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
size = 12,
|
size = 12,
|
||||||
ast,
|
|
||||||
code,
|
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
@ -1941,30 +1626,15 @@ export function createProfileStartHandle({
|
|||||||
theme: Themes
|
theme: Themes
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
size?: number
|
size?: number
|
||||||
ast?: Program
|
|
||||||
code?: string
|
|
||||||
} & (
|
} & (
|
||||||
| { isDraft: true }
|
| { isDraft: true }
|
||||||
| { isDraft: false; id: string; pathToNode: PathToNode }
|
| { isDraft: false; id: string; pathToNode: PathToNode }
|
||||||
)) {
|
)) {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
// Check if profile start is fully constrained (only if we have AST, code, and it's not a draft)
|
|
||||||
const isFullyConstrained =
|
|
||||||
!isDraft && ast && code && 'pathToNode' in rest
|
|
||||||
? isSegmentFullyConstrained(rest.pathToNode, ast, code)
|
|
||||||
: false
|
|
||||||
|
|
||||||
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
|
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
|
||||||
const color = getSegmentColor({
|
const baseColor = getThemeColorForThreeJs(theme)
|
||||||
theme,
|
const color = isSelected ? 0x0000ff : baseColor
|
||||||
isSelected: !!isSelected,
|
|
||||||
callExpName: 'profileStart',
|
|
||||||
isFullyConstrained,
|
|
||||||
})
|
|
||||||
const baseColor = !isFullyConstrained
|
|
||||||
? getPrimaryColorForThreeJs()
|
|
||||||
: getThemeColorForThreeJs(theme)
|
|
||||||
const body = new MeshBasicMaterial({ color })
|
const body = new MeshBasicMaterial({ color })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
|
|
||||||
@ -1975,7 +1645,6 @@ export function createProfileStartHandle({
|
|||||||
from,
|
from,
|
||||||
isSelected,
|
isSelected,
|
||||||
baseColor,
|
baseColor,
|
||||||
isFullyConstrained,
|
|
||||||
...rest,
|
...rest,
|
||||||
}
|
}
|
||||||
group.name = isDraft ? DRAFT_POINT : PROFILE_START
|
group.name = isDraft ? DRAFT_POINT : PROFILE_START
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import type { AppTheme } from '@rust/kcl-lib/bindings/AppTheme'
|
import type { AppTheme } from '@rust/kcl-lib/bindings/AppTheme'
|
||||||
import { converter } from 'culori'
|
|
||||||
|
|
||||||
/** A media query matcher for dark mode */
|
/** A media query matcher for dark mode */
|
||||||
export const darkModeMatcher =
|
export const darkModeMatcher =
|
||||||
@ -59,84 +58,6 @@ export function getOppositeTheme(theme: Themes) {
|
|||||||
return resolvedTheme === Themes.Dark ? Themes.Light : Themes.Dark
|
return resolvedTheme === Themes.Dark ? Themes.Light : Themes.Dark
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts OKLCH values to RGB using Culori library
|
|
||||||
* @param l - Lightness (0-1)
|
|
||||||
* @param c - Chroma (0-1)
|
|
||||||
* @param h - Hue (0-360 degrees)
|
|
||||||
* @returns RGB values as [r, g, b] where each component is 0-255
|
|
||||||
*/
|
|
||||||
function oklchToRgb(l: number, c: number, h: number): [number, number, number] {
|
|
||||||
// Create a converter from OKLCH to RGB using Culori
|
|
||||||
const toRgb = converter('rgb')
|
|
||||||
|
|
||||||
// Convert OKLCH to RGB using Culori
|
|
||||||
const rgb = toRgb({ mode: 'oklch', l, c, h })
|
|
||||||
|
|
||||||
if (!rgb) {
|
|
||||||
// Fallback if conversion fails
|
|
||||||
return [255, 255, 255]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp values. When OKLCH values represent colors outside the sRGB gamut, the RGB values can be negative or greater than 1.
|
|
||||||
const clampedR = Math.max(0, Math.min(1, rgb.r))
|
|
||||||
const clampedG = Math.max(0, Math.min(1, rgb.g))
|
|
||||||
const clampedB = Math.max(0, Math.min(1, rgb.b))
|
|
||||||
|
|
||||||
// Convert from 0-1 range to 0-255 range
|
|
||||||
return [
|
|
||||||
Math.round(clampedR * 255),
|
|
||||||
Math.round(clampedG * 255),
|
|
||||||
Math.round(clampedB * 255),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the primary color from CSS custom properties and converts it to Three.js hex format
|
|
||||||
* @returns Primary color as a hex number for Three.js, or fallback purple if unable to get CSS value
|
|
||||||
*/
|
|
||||||
export function getPrimaryColorForThreeJs(): number {
|
|
||||||
if (typeof globalThis.window === 'undefined' || !globalThis.document) {
|
|
||||||
// Fallback for SSR or when DOM is not available
|
|
||||||
return 0x7c3aed // Default purple
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement)
|
|
||||||
|
|
||||||
// Get the individual primary color components
|
|
||||||
const hue = parseFloat(
|
|
||||||
computedStyle.getPropertyValue('--primary-hue').trim()
|
|
||||||
)
|
|
||||||
const chroma = parseFloat(
|
|
||||||
computedStyle.getPropertyValue('--primary-chroma').trim()
|
|
||||||
)
|
|
||||||
const lightness =
|
|
||||||
parseFloat(
|
|
||||||
computedStyle
|
|
||||||
.getPropertyValue('--primary-lightness')
|
|
||||||
.replace('%', '')
|
|
||||||
.trim()
|
|
||||||
) / 100
|
|
||||||
|
|
||||||
if (Number.isNaN(hue) || Number.isNaN(chroma) || Number.isNaN(lightness)) {
|
|
||||||
console.warn(
|
|
||||||
'Unable to parse primary color components from CSS, using fallback'
|
|
||||||
)
|
|
||||||
return 0x7c3aed // Default purple
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert OKLCH to RGB
|
|
||||||
const [r, g, b] = oklchToRgb(lightness, chroma, hue)
|
|
||||||
|
|
||||||
// Convert RGB to hex
|
|
||||||
return (r << 16) | (g << 8) | b
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Error getting primary color from CSS:', error)
|
|
||||||
return 0x7c3aed // Default purple fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The engine takes RGBA values from 0-1
|
* The engine takes RGBA values from 0-1
|
||||||
* So we convert from the conventional 0-255 found in Figma
|
* So we convert from the conventional 0-255 found in Figma
|
||||||
|