* first draft of making segment snap to previous arc's last tangent * ability to force/disable line snap, threshold in screen space * mouseEvent refactor tsc errors fixed * cleanups, extract getTanPreviousPoint function * add snap line support when previous segment is ARC * small cleanups * remove unused planeNodePath param from onDragSegment * renaming * Enable snapping when placing the segment point in onClick * refactor getSnappedDragPoint to include axis intersection * handle snapping to both axis and tangent direction * snap refinements * small cleanups * lint * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * A snapshot a day keeps the bugs away! 📷🐛 * generate tag for previous arc when snapping current straight segment * using previous arc's tag in snapped angledLine * angledLine uses object instead of array now * use more general snap object instead * snap tangent line visualized when snapping occurs * remove unused scale param from createLine * prettier * fix bug where segment body is not drawn * fix generated kcl error introduced in merge from main - modifiedAst needs to be passed to addNewSketchLn * add support for snapping to negative tangent direction * fix findTangentDirection for THREE_POINT_ARC_SEGMENT * fix tsc error by introducing overrideExpr * fix missing ccw for 3 point arc, fix tan_previous_point calculation for 3 point arcs * resolve clippy until confirmation for circle radius * fix runtime error when drawing a 3 point arc * add unit tests to closestPointoOnRay * unrelated react warning fixed * add playwright test for tangent snapping * better fix for tan_previous_point * fix lint * add simulation test for tangent_to_3_point_arc * Fix simulation test output * Add missing simulation test output files * fix tangent snapping bug: use current group instead of last group in activeSegments * make testcombos.test happy * cleanup merge * fix merge mistake, tsc error * update tangent_to_3_point_arc simulation test * fix angledLine related breaking tests * minimum distance added before snapping to tangent * circle is always ccw regardless of the order of points for tangential info calculation * fix snapping when different unit is used other than mm * update test: Straight line snapping to previous tangent * update rust snapshot test --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Frank Noirot <frank@zoo.dev> Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
@ -205,6 +205,11 @@ export class ToolbarFixture {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await this.page.getByTestId('dropdown-three-point-arc').click()
|
await this.page.getByTestId('dropdown-three-point-arc').click()
|
||||||
}
|
}
|
||||||
|
selectLine = async () => {
|
||||||
|
await this.page
|
||||||
|
.getByRole('button', { name: 'line Line', exact: true })
|
||||||
|
.click()
|
||||||
|
}
|
||||||
|
|
||||||
async closePane(paneId: SidebarType) {
|
async closePane(paneId: SidebarType) {
|
||||||
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||||
|
@ -3034,4 +3034,78 @@ test.describe('Redirecting to home page and back to the original file should cle
|
|||||||
await homePage.openProject('testDefault')
|
await homePage.openProject('testDefault')
|
||||||
await expect(page.getByText('323.49')).not.toBeVisible()
|
await expect(page.getByText('323.49')).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Straight line snapping to previous tangent', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
context,
|
||||||
|
editor,
|
||||||
|
}) => {
|
||||||
|
await context.addInitScript(() => {
|
||||||
|
localStorage.setItem('persistCode', `@settings(defaultLengthUnit = mm)`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const viewportSize = { width: 1200, height: 900 }
|
||||||
|
await page.setBodyDimensions(viewportSize)
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// wait until scene is ready to be interacted with
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
// select an axis plane
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
// Needed as we don't yet have a way to get a signal from the engine that the camera has animated to the sketch plane
|
||||||
|
await page.waitForTimeout(3000)
|
||||||
|
|
||||||
|
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||||
|
const { click00r } = getMovementUtils({ center, page })
|
||||||
|
|
||||||
|
// Draw line
|
||||||
|
await click00r(0, 0)
|
||||||
|
await click00r(200, -200)
|
||||||
|
|
||||||
|
// Draw arc
|
||||||
|
await toolbar.tangentialArcBtn.click()
|
||||||
|
await click00r(0, 0)
|
||||||
|
await click00r(100, 100)
|
||||||
|
|
||||||
|
// Switch back to line
|
||||||
|
await toolbar.selectLine()
|
||||||
|
await click00r(0, 0)
|
||||||
|
await click00r(-100, 100)
|
||||||
|
|
||||||
|
// Draw a 3 point arc
|
||||||
|
await toolbar.selectThreePointArc()
|
||||||
|
await click00r(0, 0)
|
||||||
|
await click00r(0, 100)
|
||||||
|
await click00r(100, 0)
|
||||||
|
|
||||||
|
// draw a line to opposite tangnet direction of previous arc
|
||||||
|
await toolbar.selectLine()
|
||||||
|
await click00r(0, 0)
|
||||||
|
await click00r(-200, 200)
|
||||||
|
|
||||||
|
await editor.expectEditor.toContain(
|
||||||
|
`@settings(defaultLengthUnit = mm)
|
||||||
|
|
||||||
|
sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = startProfileAt([0, 0], sketch001)
|
||||||
|
|> line(end = [191.39, 191.39])
|
||||||
|
|> tangentialArc(endAbsolute = [287.08, 95.69], tag = $seg01)
|
||||||
|
|> angledLine(angle = tangentToEnd(seg01), length = 135.34)
|
||||||
|
|> arcTo({
|
||||||
|
interior = [191.39, -95.69],
|
||||||
|
end = [287.08, -95.69]
|
||||||
|
}, %, $seg02)
|
||||||
|
|> angledLine(angle = tangentToEnd(seg02) + turns::HALF_TURN, length = 270.67)
|
||||||
|
`.replaceAll('\n', '')
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -644,7 +644,7 @@ impl GetTangentialInfoFromPathsResult {
|
|||||||
pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
|
pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
|
||||||
match self {
|
match self {
|
||||||
GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
|
GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
|
||||||
GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => {
|
GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
|
||||||
crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
|
crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
|
||||||
}
|
}
|
||||||
// The circle always starts at 0 degrees, so a suitable tangent
|
// The circle always starts at 0 degrees, so a suitable tangent
|
||||||
@ -1231,12 +1231,9 @@ impl Path {
|
|||||||
},
|
},
|
||||||
Path::ArcThreePoint { p1, p2, p3, .. } => {
|
Path::ArcThreePoint { p1, p2, p3, .. } => {
|
||||||
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
||||||
let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1);
|
GetTangentialInfoFromPathsResult::Arc {
|
||||||
let center_point = [circle_center.center[0], circle_center.center[1]];
|
center: circle_center.center,
|
||||||
GetTangentialInfoFromPathsResult::Circle {
|
ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
|
||||||
center: center_point,
|
|
||||||
ccw: true,
|
|
||||||
radius,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Path::Circle {
|
Path::Circle {
|
||||||
@ -1252,6 +1249,7 @@ impl Path {
|
|||||||
let center_point = [circle_center.center[0], circle_center.center[1]];
|
let center_point = [circle_center.center[0], circle_center.center[1]];
|
||||||
GetTangentialInfoFromPathsResult::Circle {
|
GetTangentialInfoFromPathsResult::Circle {
|
||||||
center: center_point,
|
center: center_point,
|
||||||
|
// Note: a circle is always ccw regardless of the order of points
|
||||||
ccw: true,
|
ccw: true,
|
||||||
radius,
|
radius,
|
||||||
}
|
}
|
||||||
|
@ -2482,6 +2482,7 @@ mod intersect_cubes {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod pattern_into_union {
|
mod pattern_into_union {
|
||||||
const TEST_NAME: &str = "pattern_into_union";
|
const TEST_NAME: &str = "pattern_into_union";
|
||||||
|
|
||||||
@ -2524,3 +2525,24 @@ mod subtract_doesnt_need_brackets {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod tangent_to_3_point_arc {
|
||||||
|
const TEST_NAME: &str = "tangent_to_3_point_arc";
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
156
rust/kcl-lib/tests/tangent_to_3_point_arc/artifact_commands.snap
Normal file
156
rust/kcl-lib/tests/tangent_to_3_point_arc/artifact_commands.snap
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Artifact commands tangent_to_3_point_arc.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": 0.0,
|
||||||
|
"z": 1.0
|
||||||
|
},
|
||||||
|
"size": 60.0,
|
||||||
|
"clobber": false,
|
||||||
|
"hide": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "enable_sketch_mode",
|
||||||
|
"entity_id": "[uuid]",
|
||||||
|
"ortho": false,
|
||||||
|
"animated": false,
|
||||||
|
"adjust_camera": false,
|
||||||
|
"planar_normal": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": -1.0,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "start_path"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "move_path_pen",
|
||||||
|
"path": "[uuid]",
|
||||||
|
"to": {
|
||||||
|
"x": 100.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "sketch_mode_disable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "extend_path",
|
||||||
|
"path": "[uuid]",
|
||||||
|
"segment": {
|
||||||
|
"type": "line",
|
||||||
|
"end": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 120.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"relative": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "extend_path",
|
||||||
|
"path": "[uuid]",
|
||||||
|
"segment": {
|
||||||
|
"type": "arc_to",
|
||||||
|
"interior": {
|
||||||
|
"x": 300.0,
|
||||||
|
"y": 100.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"x": 200.0,
|
||||||
|
"y": -100.0,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"relative": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cmdId": "[uuid]",
|
||||||
|
"range": [],
|
||||||
|
"command": {
|
||||||
|
"type": "extend_path",
|
||||||
|
"path": "[uuid]",
|
||||||
|
"segment": {
|
||||||
|
"type": "line",
|
||||||
|
"end": {
|
||||||
|
"x": -99.8038,
|
||||||
|
"y": -6.2608,
|
||||||
|
"z": 0.0
|
||||||
|
},
|
||||||
|
"relative": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Artifact graph flowchart tangent_to_3_point_arc.kcl
|
||||||
|
extension: md
|
||||||
|
snapshot_kind: binary
|
||||||
|
---
|
@ -0,0 +1,14 @@
|
|||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph path2 [Path]
|
||||||
|
2["Path<br>[43, 82, 0]"]
|
||||||
|
3["Segment<br>[88, 112, 0]"]
|
||||||
|
4["Segment<br>[118, 209, 0]"]
|
||||||
|
5["Segment<br>[215, 292, 0]"]
|
||||||
|
end
|
||||||
|
1["Plane<br>[12, 29, 0]"]
|
||||||
|
1 --- 2
|
||||||
|
2 --- 3
|
||||||
|
2 --- 4
|
||||||
|
2 --- 5
|
||||||
|
```
|
490
rust/kcl-lib/tests/tangent_to_3_point_arc/ast.snap
Normal file
490
rust/kcl-lib/tests/tangent_to_3_point_arc/ast.snap
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of parsing tangent_to_3_point_arc.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "sketch001",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "XZ",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"declaration": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"id": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "profile001",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "0.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "sketch001",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "startProfileAt",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "end",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "0.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "120.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 120.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "line",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"key": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "interior",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "300.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 300.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100.0",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"key": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "end",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ObjectProperty",
|
||||||
|
"value": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "200.00",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 200.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"argument": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100.00",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"operator": "-",
|
||||||
|
"start": 0,
|
||||||
|
"type": "UnaryExpression",
|
||||||
|
"type": "UnaryExpression"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "ArrayExpression",
|
||||||
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start": 0,
|
||||||
|
"type": "ObjectExpression",
|
||||||
|
"type": "ObjectExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "PipeSubstitution",
|
||||||
|
"type": "PipeSubstitution"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "seg01"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "arcTo",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "angle",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "seg01",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name",
|
||||||
|
"type": "Name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "tangentToEnd",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpression",
|
||||||
|
"type": "CallExpression"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "LabeledArg",
|
||||||
|
"label": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "length",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"raw": "100.00",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 100.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"callee": {
|
||||||
|
"abs_path": false,
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": {
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"name": "angledLine",
|
||||||
|
"start": 0,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"path": [],
|
||||||
|
"start": 0,
|
||||||
|
"type": "Name"
|
||||||
|
},
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0,
|
||||||
|
"type": "PipeExpression",
|
||||||
|
"type": "PipeExpression"
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 0,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 0,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commentStart": 0,
|
||||||
|
"end": 0,
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
11
rust/kcl-lib/tests/tangent_to_3_point_arc/input.kcl
Normal file
11
rust/kcl-lib/tests/tangent_to_3_point_arc/input.kcl
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = startProfileAt([100.0, 0.0], sketch001)
|
||||||
|
|> line(end = [0.0, 120.0])
|
||||||
|
|> arcTo({
|
||||||
|
interior = [300.0, 100.0],
|
||||||
|
end = [200.00, -100.00]
|
||||||
|
}, %, $seg01)
|
||||||
|
|> angledLine(
|
||||||
|
angle = tangentToEnd(seg01),
|
||||||
|
length = 100.00
|
||||||
|
)
|
21
rust/kcl-lib/tests/tangent_to_3_point_arc/ops.snap
Normal file
21
rust/kcl-lib/tests/tangent_to_3_point_arc/ops.snap
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Operations executed tangent_to_3_point_arc.kcl
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"labeledArgs": {
|
||||||
|
"planeOrSolid": {
|
||||||
|
"value": {
|
||||||
|
"type": "Plane",
|
||||||
|
"artifact_id": "[uuid]"
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"sourceRange": [],
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"unlabeledArg": null
|
||||||
|
}
|
||||||
|
]
|
208
rust/kcl-lib/tests/tangent_to_3_point_arc/program_memory.snap
Normal file
208
rust/kcl-lib/tests/tangent_to_3_point_arc/program_memory.snap
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Variables in memory after executing tangent_to_3_point_arc.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"profile001": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"paths": [
|
||||||
|
{
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
"from": [
|
||||||
|
100.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"tag": null,
|
||||||
|
"to": [
|
||||||
|
100.0,
|
||||||
|
120.0
|
||||||
|
],
|
||||||
|
"type": "ToPoint",
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
"from": [
|
||||||
|
100.0,
|
||||||
|
120.0
|
||||||
|
],
|
||||||
|
"p1": [
|
||||||
|
100.0,
|
||||||
|
120.0
|
||||||
|
],
|
||||||
|
"p2": [
|
||||||
|
300.0,
|
||||||
|
100.0
|
||||||
|
],
|
||||||
|
"p3": [
|
||||||
|
200.0,
|
||||||
|
-100.0
|
||||||
|
],
|
||||||
|
"tag": {
|
||||||
|
"commentStart": 202,
|
||||||
|
"end": 208,
|
||||||
|
"start": 202,
|
||||||
|
"type": "TagDeclarator",
|
||||||
|
"value": "seg01"
|
||||||
|
},
|
||||||
|
"to": [
|
||||||
|
200.0,
|
||||||
|
-100.0
|
||||||
|
],
|
||||||
|
"type": "ArcThreePoint",
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
|
"from": [
|
||||||
|
200.0,
|
||||||
|
-100.0
|
||||||
|
],
|
||||||
|
"tag": null,
|
||||||
|
"to": [
|
||||||
|
100.1962,
|
||||||
|
-106.2608
|
||||||
|
],
|
||||||
|
"type": "ToPoint",
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"on": {
|
||||||
|
"type": "plane",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"artifactId": "[uuid]",
|
||||||
|
"value": "XZ",
|
||||||
|
"origin": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 1.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": -1.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"from": [
|
||||||
|
100.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"to": [
|
||||||
|
100.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
},
|
||||||
|
"tag": null,
|
||||||
|
"__geoMeta": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"sourceRange": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"seg01": {
|
||||||
|
"type": "TagIdentifier",
|
||||||
|
"value": "seg01"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"artifactId": "[uuid]",
|
||||||
|
"originalId": "[uuid]",
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"seg01": {
|
||||||
|
"type": "TagIdentifier",
|
||||||
|
"type": "TagIdentifier",
|
||||||
|
"value": "seg01"
|
||||||
|
},
|
||||||
|
"sketch001": {
|
||||||
|
"type": "Plane",
|
||||||
|
"value": {
|
||||||
|
"id": "[uuid]",
|
||||||
|
"artifactId": "[uuid]",
|
||||||
|
"value": "XZ",
|
||||||
|
"origin": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"x": 1.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": 0.0,
|
||||||
|
"z": 1.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zAxis": {
|
||||||
|
"x": 0.0,
|
||||||
|
"y": -1.0,
|
||||||
|
"z": 0.0,
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"type": "Mm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
rust/kcl-lib/tests/tangent_to_3_point_arc/rendered_model.png
Normal file
BIN
rust/kcl-lib/tests/tangent_to_3_point_arc/rendered_model.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
12
rust/kcl-lib/tests/tangent_to_3_point_arc/unparsed.snap
Normal file
12
rust/kcl-lib/tests/tangent_to_3_point_arc/unparsed.snap
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
|
description: Result of unparsing tangent_to_3_point_arc.kcl
|
||||||
|
---
|
||||||
|
sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = startProfileAt([100.0, 0.0], sketch001)
|
||||||
|
|> line(end = [0.0, 120.0])
|
||||||
|
|> arcTo({
|
||||||
|
interior = [300.0, 100.0],
|
||||||
|
end = [200.0, -100.0]
|
||||||
|
}, %, $seg01)
|
||||||
|
|> angledLine(angle = tangentToEnd(seg01), length = 100.0)
|
@ -9,6 +9,7 @@ export const ARC_SEGMENT_DASH = 'arc-segment-dash'
|
|||||||
export const STRAIGHT_SEGMENT = 'straight-segment'
|
export const STRAIGHT_SEGMENT = 'straight-segment'
|
||||||
export const STRAIGHT_SEGMENT_BODY = 'straight-segment-body'
|
export const STRAIGHT_SEGMENT_BODY = 'straight-segment-body'
|
||||||
export const STRAIGHT_SEGMENT_DASH = 'straight-segment-body-dashed'
|
export const STRAIGHT_SEGMENT_DASH = 'straight-segment-body-dashed'
|
||||||
|
export const STRAIGHT_SEGMENT_SNAP_LINE = 'straight-segment-snap-line'
|
||||||
export const CIRCLE_SEGMENT = 'circle-segment'
|
export const CIRCLE_SEGMENT = 'circle-segment'
|
||||||
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
|
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
|
||||||
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
|
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
|
||||||
@ -62,6 +63,12 @@ export const SEGMENT_BODIES_PLUS_PROFILE_START = [
|
|||||||
PROFILE_START,
|
PROFILE_START,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const ARC_SEGMENT_TYPES = [
|
||||||
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
|
THREE_POINT_ARC_SEGMENT,
|
||||||
|
ARC_SEGMENT,
|
||||||
|
]
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
export function getParentGroup(
|
export function getParentGroup(
|
||||||
object: any,
|
object: any,
|
||||||
|
@ -5,6 +5,7 @@ import type {
|
|||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BoxGeometry,
|
BoxGeometry,
|
||||||
DoubleSide,
|
DoubleSide,
|
||||||
@ -36,7 +37,8 @@ import type { Sketch } from '@rust/kcl-lib/bindings/Sketch'
|
|||||||
import type { SourceRange } from '@rust/kcl-lib/bindings/SourceRange'
|
import type { SourceRange } from '@rust/kcl-lib/bindings/SourceRange'
|
||||||
import type { VariableDeclaration } from '@rust/kcl-lib/bindings/VariableDeclaration'
|
import type { VariableDeclaration } from '@rust/kcl-lib/bindings/VariableDeclaration'
|
||||||
import type { VariableDeclarator } from '@rust/kcl-lib/bindings/VariableDeclarator'
|
import type { VariableDeclarator } from '@rust/kcl-lib/bindings/VariableDeclarator'
|
||||||
import { uuidv4 } from '@src/lib/utils'
|
import type { SafeArray } from '@src/lib/utils'
|
||||||
|
import { getAngle, getLength, uuidv4 } from '@src/lib/utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createGridHelper,
|
createGridHelper,
|
||||||
@ -48,6 +50,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ARC_ANGLE_END,
|
ARC_ANGLE_END,
|
||||||
ARC_SEGMENT,
|
ARC_SEGMENT,
|
||||||
|
ARC_SEGMENT_TYPES,
|
||||||
CIRCLE_CENTER_HANDLE,
|
CIRCLE_CENTER_HANDLE,
|
||||||
CIRCLE_SEGMENT,
|
CIRCLE_SEGMENT,
|
||||||
CIRCLE_THREE_POINT_HANDLE1,
|
CIRCLE_THREE_POINT_HANDLE1,
|
||||||
@ -56,6 +59,7 @@ import {
|
|||||||
CIRCLE_THREE_POINT_SEGMENT,
|
CIRCLE_THREE_POINT_SEGMENT,
|
||||||
DRAFT_DASHED_LINE,
|
DRAFT_DASHED_LINE,
|
||||||
EXTRA_SEGMENT_HANDLE,
|
EXTRA_SEGMENT_HANDLE,
|
||||||
|
getParentGroup,
|
||||||
PROFILE_START,
|
PROFILE_START,
|
||||||
SEGMENT_BODIES,
|
SEGMENT_BODIES,
|
||||||
SEGMENT_BODIES_PLUS_PROFILE_START,
|
SEGMENT_BODIES_PLUS_PROFILE_START,
|
||||||
@ -66,31 +70,32 @@ import {
|
|||||||
THREE_POINT_ARC_HANDLE2,
|
THREE_POINT_ARC_HANDLE2,
|
||||||
THREE_POINT_ARC_HANDLE3,
|
THREE_POINT_ARC_HANDLE3,
|
||||||
THREE_POINT_ARC_SEGMENT,
|
THREE_POINT_ARC_SEGMENT,
|
||||||
getParentGroup,
|
|
||||||
} from '@src/clientSideScene/sceneConstants'
|
} from '@src/clientSideScene/sceneConstants'
|
||||||
import type {
|
import type {
|
||||||
OnClickCallbackArgs,
|
OnClickCallbackArgs,
|
||||||
OnMouseEnterLeaveArgs,
|
OnMouseEnterLeaveArgs,
|
||||||
SceneInfra,
|
SceneInfra,
|
||||||
} from '@src/clientSideScene/sceneInfra'
|
} from '@src/clientSideScene/sceneInfra'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ANGLE_SNAP_THRESHOLD_DEGREES,
|
ANGLE_SNAP_THRESHOLD_DEGREES,
|
||||||
ARROWHEAD,
|
ARROWHEAD,
|
||||||
AXIS_GROUP,
|
AXIS_GROUP,
|
||||||
DRAFT_POINT,
|
DRAFT_POINT,
|
||||||
DRAFT_POINT_GROUP,
|
DRAFT_POINT_GROUP,
|
||||||
|
getSceneScale,
|
||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
RAYCASTABLE_PLANE,
|
RAYCASTABLE_PLANE,
|
||||||
SKETCH_GROUP_SEGMENTS,
|
SKETCH_GROUP_SEGMENTS,
|
||||||
SKETCH_LAYER,
|
SKETCH_LAYER,
|
||||||
X_AXIS,
|
X_AXIS,
|
||||||
Y_AXIS,
|
Y_AXIS,
|
||||||
getSceneScale,
|
|
||||||
} from '@src/clientSideScene/sceneUtils'
|
} from '@src/clientSideScene/sceneUtils'
|
||||||
import type { SegmentUtils } from '@src/clientSideScene/segments'
|
import type { SegmentUtils } from '@src/clientSideScene/segments'
|
||||||
import {
|
import {
|
||||||
createProfileStartHandle,
|
createProfileStartHandle,
|
||||||
dashedStraight,
|
dashedStraight,
|
||||||
|
getTanPreviousPoint,
|
||||||
segmentUtils,
|
segmentUtils,
|
||||||
} from '@src/clientSideScene/segments'
|
} from '@src/clientSideScene/segments'
|
||||||
import type EditorManager from '@src/editor/manager'
|
import type EditorManager from '@src/editor/manager'
|
||||||
@ -118,6 +123,7 @@ import {
|
|||||||
insertNewStartProfileAt,
|
insertNewStartProfileAt,
|
||||||
updateSketchNodePathsWithInsertIndex,
|
updateSketchNodePathsWithInsertIndex,
|
||||||
} from '@src/lang/modifyAst'
|
} from '@src/lang/modifyAst'
|
||||||
|
import { mutateAstWithTagForSketchSegment } from '@src/lang/modifyAst/addEdgeTreatment'
|
||||||
import { getNodeFromPath } from '@src/lang/queryAst'
|
import { getNodeFromPath } from '@src/lang/queryAst'
|
||||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
import {
|
import {
|
||||||
@ -138,6 +144,7 @@ import { topLevelRange } from '@src/lang/util'
|
|||||||
import type { PathToNode, VariableMap } from '@src/lang/wasm'
|
import type { PathToNode, VariableMap } from '@src/lang/wasm'
|
||||||
import {
|
import {
|
||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
|
getTangentialArcToInfo,
|
||||||
parse,
|
parse,
|
||||||
recast,
|
recast,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
@ -157,12 +164,14 @@ import type { Themes } from '@src/lib/theme'
|
|||||||
import { getThemeColorForThreeJs } from '@src/lib/theme'
|
import { getThemeColorForThreeJs } from '@src/lib/theme'
|
||||||
import { err, reportRejection, trap } from '@src/lib/trap'
|
import { err, reportRejection, trap } from '@src/lib/trap'
|
||||||
import { isArray, isOverlap, roundOff } from '@src/lib/utils'
|
import { isArray, isOverlap, roundOff } from '@src/lib/utils'
|
||||||
|
import { closestPointOnRay, deg2Rad } from '@src/lib/utils2d'
|
||||||
import type {
|
import type {
|
||||||
SegmentOverlayPayload,
|
SegmentOverlayPayload,
|
||||||
SketchDetails,
|
SketchDetails,
|
||||||
SketchDetailsUpdate,
|
SketchDetailsUpdate,
|
||||||
SketchTool,
|
SketchTool,
|
||||||
} from '@src/machines/modelingMachine'
|
} from '@src/machines/modelingMachine'
|
||||||
|
import { calculateIntersectionOfTwoLines } from 'sketch-helpers'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArc'
|
type DraftSegment = 'line' | 'tangentialArc'
|
||||||
|
|
||||||
@ -183,6 +192,7 @@ export class SceneEntities {
|
|||||||
axisGroup: Group | null = null
|
axisGroup: Group | null = null
|
||||||
draftPointGroups: Group[] = []
|
draftPointGroups: Group[] = []
|
||||||
currentSketchQuaternion: Quaternion | null = null
|
currentSketchQuaternion: Quaternion | null = null
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
sceneInfra: SceneInfra,
|
sceneInfra: SceneInfra,
|
||||||
@ -344,6 +354,7 @@ export class SceneEntities {
|
|||||||
sceneInfra.scene.add(intersectionPlane)
|
sceneInfra.scene.add(intersectionPlane)
|
||||||
return intersectionPlane
|
return intersectionPlane
|
||||||
}
|
}
|
||||||
|
|
||||||
createSketchAxis(
|
createSketchAxis(
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
forward: [number, number, number],
|
forward: [number, number, number],
|
||||||
@ -423,9 +434,11 @@ export class SceneEntities {
|
|||||||
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
sketchPosition && this.axisGroup.position.set(...sketchPosition)
|
||||||
this.sceneInfra.scene.add(this.axisGroup)
|
this.sceneInfra.scene.add(this.axisGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
getDraftPoint() {
|
getDraftPoint() {
|
||||||
return this.sceneInfra.scene.getObjectByName(DRAFT_POINT)
|
return this.sceneInfra.scene.getObjectByName(DRAFT_POINT)
|
||||||
}
|
}
|
||||||
|
|
||||||
createDraftPoint({
|
createDraftPoint({
|
||||||
point,
|
point,
|
||||||
origin,
|
origin,
|
||||||
@ -857,6 +870,7 @@ export class SceneEntities {
|
|||||||
variableDeclarationName,
|
variableDeclarationName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAstAndRejigSketch = async (
|
updateAstAndRejigSketch = async (
|
||||||
sketchEntryNodePath: PathToNode,
|
sketchEntryNodePath: PathToNode,
|
||||||
sketchNodePaths: PathToNode[],
|
sketchNodePaths: PathToNode[],
|
||||||
@ -1016,22 +1030,25 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||||
} else if (intersection2d) {
|
} else if (intersection2d) {
|
||||||
const intersectsYAxis = args.intersects.find(
|
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
|
||||||
(sceneObject) => sceneObject.object.name === Y_AXIS
|
|
||||||
)
|
let {
|
||||||
const intersectsXAxis = args.intersects.find(
|
snappedPoint,
|
||||||
(sceneObject) => sceneObject.object.name === X_AXIS
|
snappedToTangent,
|
||||||
|
intersectsXAxis,
|
||||||
|
intersectsYAxis,
|
||||||
|
negativeTangentDirection,
|
||||||
|
} = this.getSnappedDragPoint(
|
||||||
|
intersection2d,
|
||||||
|
args.intersects,
|
||||||
|
args.mouseEvent,
|
||||||
|
Object.values(this.activeSegments).at(-1)
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
|
|
||||||
const snappedPoint = {
|
|
||||||
x: intersectsYAxis ? 0 : intersection2d.x,
|
|
||||||
y: intersectsXAxis ? 0 : intersection2d.y,
|
|
||||||
}
|
|
||||||
// Get the angle between the previous segment (or sketch start)'s end and this one's
|
// Get the angle between the previous segment (or sketch start)'s end and this one's
|
||||||
const angle = Math.atan2(
|
const angle = Math.atan2(
|
||||||
snappedPoint.y - lastSegment.to[1],
|
snappedPoint[1] - lastSegment.to[1],
|
||||||
snappedPoint.x - lastSegment.to[0]
|
snappedPoint[0] - lastSegment.to[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
const isHorizontal =
|
const isHorizontal =
|
||||||
@ -1043,6 +1060,12 @@ export class SceneEntities {
|
|||||||
ANGLE_SNAP_THRESHOLD_DEGREES
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
||||||
|
|
||||||
let resolvedFunctionName: ToolTip = 'line'
|
let resolvedFunctionName: ToolTip = 'line'
|
||||||
|
const snaps = {
|
||||||
|
previousArcTag: '',
|
||||||
|
negativeTangentDirection,
|
||||||
|
xAxis: !!intersectsXAxis,
|
||||||
|
yAxis: !!intersectsYAxis,
|
||||||
|
}
|
||||||
|
|
||||||
// This might need to become its own function if we want more
|
// This might need to become its own function if we want more
|
||||||
// case-based logic for different segment types
|
// case-based logic for different segment types
|
||||||
@ -1051,27 +1074,46 @@ export class SceneEntities {
|
|||||||
segmentName === 'tangentialArc'
|
segmentName === 'tangentialArc'
|
||||||
) {
|
) {
|
||||||
resolvedFunctionName = 'tangentialArc'
|
resolvedFunctionName = 'tangentialArc'
|
||||||
|
} else if (snappedToTangent) {
|
||||||
|
// Generate tag for previous arc segment and use it for the angle of angledLine:
|
||||||
|
// |> tangentialArcTo([5, -10], %, $arc001)
|
||||||
|
// |> angledLine({ angle = tangentToEnd(arc001), length = 12 }, %)
|
||||||
|
|
||||||
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||||
|
modifiedAst,
|
||||||
|
sourceRangeFromRust(lastSegment.__geoMeta.sourceRange)
|
||||||
|
)
|
||||||
|
const taggedAstResult = mutateAstWithTagForSketchSegment(
|
||||||
|
modifiedAst,
|
||||||
|
previousSegmentPathToNode
|
||||||
|
)
|
||||||
|
if (trap(taggedAstResult)) return Promise.reject(taggedAstResult)
|
||||||
|
|
||||||
|
modifiedAst = taggedAstResult.modifiedAst
|
||||||
|
snaps.previousArcTag = taggedAstResult.tag
|
||||||
|
resolvedFunctionName = 'angledLine'
|
||||||
} else if (isHorizontal) {
|
} else if (isHorizontal) {
|
||||||
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
|
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
|
||||||
resolvedFunctionName = 'xLine'
|
resolvedFunctionName = 'xLine'
|
||||||
} else if (isVertical) {
|
} else if (isVertical) {
|
||||||
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
|
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
|
||||||
resolvedFunctionName = 'yLine'
|
resolvedFunctionName = 'yLine'
|
||||||
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
|
} else if (snappedPoint[0] === 0 || snappedPoint[1] === 0) {
|
||||||
// We consider a point placed on axes or origin to be absolute
|
// We consider a point placed on axes or origin to be absolute
|
||||||
resolvedFunctionName = 'lineTo'
|
resolvedFunctionName = 'lineTo'
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmp = addNewSketchLn({
|
const tmp = addNewSketchLn({
|
||||||
node: this.kclManager.ast,
|
node: modifiedAst,
|
||||||
variables: this.kclManager.variables,
|
variables: this.kclManager.variables,
|
||||||
input: {
|
input: {
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
from: [lastSegment.to[0], lastSegment.to[1]],
|
from: [lastSegment.to[0], lastSegment.to[1]],
|
||||||
to: [snappedPoint.x, snappedPoint.y],
|
to: [snappedPoint[0], snappedPoint[1]],
|
||||||
},
|
},
|
||||||
fnName: resolvedFunctionName,
|
fnName: resolvedFunctionName,
|
||||||
pathToNode: sketchEntryNodePath,
|
pathToNode: sketchEntryNodePath,
|
||||||
|
snaps,
|
||||||
})
|
})
|
||||||
if (trap(tmp)) return Promise.reject(tmp)
|
if (trap(tmp)) return Promise.reject(tmp)
|
||||||
modifiedAst = tmp.modifiedAst
|
modifiedAst = tmp.modifiedAst
|
||||||
@ -1118,11 +1160,11 @@ export class SceneEntities {
|
|||||||
intersects: args.intersects,
|
intersects: args.intersects,
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
planeNodePath,
|
|
||||||
draftInfo: {
|
draftInfo: {
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
variableDeclarationName,
|
variableDeclarationName,
|
||||||
},
|
},
|
||||||
|
mouseEvent: args.mouseEvent,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1263,10 +1305,11 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { intersectionPoint } = args
|
const { intersectionPoint } = args
|
||||||
if (!intersectionPoint?.twoD) return
|
if (!intersectionPoint?.twoD) return
|
||||||
const { snappedPoint, isSnapped } = this.getSnappedDragPoint({
|
const { snappedPoint, isSnapped } = this.getSnappedDragPoint(
|
||||||
intersection2d: intersectionPoint.twoD,
|
intersectionPoint.twoD,
|
||||||
intersects: args.intersects,
|
args.intersects,
|
||||||
})
|
args.mouseEvent
|
||||||
|
)
|
||||||
if (isSnapped) {
|
if (isSnapped) {
|
||||||
this.positionDraftPoint({
|
this.positionDraftPoint({
|
||||||
snappedPoint: new Vector2(...snappedPoint),
|
snappedPoint: new Vector2(...snappedPoint),
|
||||||
@ -2046,10 +2089,11 @@ export class SceneEntities {
|
|||||||
if (trap(_node)) return
|
if (trap(_node)) return
|
||||||
const sketchInit = _node.node.declaration.init
|
const sketchInit = _node.node.declaration.init
|
||||||
|
|
||||||
const maybeSnapToAxis = this.getSnappedDragPoint({
|
const maybeSnapToAxis = this.getSnappedDragPoint(
|
||||||
intersection2d: args.intersectionPoint.twoD,
|
args.intersectionPoint.twoD,
|
||||||
intersects: args.intersects,
|
args.intersects,
|
||||||
}).snappedPoint
|
args.mouseEvent
|
||||||
|
).snappedPoint
|
||||||
|
|
||||||
const maybeSnapToProfileStart = doNotSnapAsThreePointArcIsTheOnlySegment
|
const maybeSnapToProfileStart = doNotSnapAsThreePointArcIsTheOnlySegment
|
||||||
? new Vector2(...maybeSnapToAxis)
|
? new Vector2(...maybeSnapToAxis)
|
||||||
@ -2148,10 +2192,11 @@ export class SceneEntities {
|
|||||||
type: 'circle-three-point-segment',
|
type: 'circle-three-point-segment',
|
||||||
p1,
|
p1,
|
||||||
p2,
|
p2,
|
||||||
p3: this.getSnappedDragPoint({
|
p3: this.getSnappedDragPoint(
|
||||||
intersection2d: args.intersectionPoint.twoD,
|
args.intersectionPoint.twoD,
|
||||||
intersects: args.intersects,
|
args.intersects,
|
||||||
}).snappedPoint,
|
args.mouseEvent
|
||||||
|
).snappedPoint,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (err(moddedResult)) return
|
if (err(moddedResult)) return
|
||||||
@ -2524,10 +2569,10 @@ export class SceneEntities {
|
|||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
sketchEntryNodePath: pathToNodeForNewSegment,
|
sketchEntryNodePath: pathToNodeForNewSegment,
|
||||||
planeNodePath,
|
|
||||||
object: selected,
|
object: selected,
|
||||||
intersection2d: intersectionPoint.twoD,
|
intersection2d: intersectionPoint.twoD,
|
||||||
intersects,
|
intersects,
|
||||||
|
mouseEvent: mouseEvent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -2536,10 +2581,10 @@ export class SceneEntities {
|
|||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
object: selected,
|
object: selected,
|
||||||
intersection2d: intersectionPoint.twoD,
|
intersection2d: intersectionPoint.twoD,
|
||||||
planeNodePath,
|
|
||||||
intersects,
|
intersects,
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
|
mouseEvent: mouseEvent,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
@ -2578,13 +2623,19 @@ export class SceneEntities {
|
|||||||
this.kclManager.lastSuccessfulVariables,
|
this.kclManager.lastSuccessfulVariables,
|
||||||
draftSegment
|
draftSegment
|
||||||
)
|
)
|
||||||
getSnappedDragPoint({
|
|
||||||
intersects,
|
getSnappedDragPoint(
|
||||||
intersection2d,
|
pos: Vector2,
|
||||||
}: {
|
intersects: Intersection<Object3D<Object3DEventMap>>[],
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
mouseEvent: MouseEvent,
|
||||||
intersection2d: Vector2
|
// During draft segment mouse move:
|
||||||
}): { snappedPoint: [number, number]; isSnapped: boolean } {
|
// - the three.js object currently being dragged: the new draft segment or existing segment (may not be the last in activeSegments)
|
||||||
|
// When placing the draft segment::
|
||||||
|
// - the last segment in activeSegments
|
||||||
|
currentObject?: Object3D | Group
|
||||||
|
) {
|
||||||
|
let snappedPoint: Coords2d = [pos.x, pos.y]
|
||||||
|
|
||||||
const intersectsYAxis = intersects.find(
|
const intersectsYAxis = intersects.find(
|
||||||
(sceneObject) => sceneObject.object.name === Y_AXIS
|
(sceneObject) => sceneObject.object.name === Y_AXIS
|
||||||
)
|
)
|
||||||
@ -2592,16 +2643,116 @@ export class SceneEntities {
|
|||||||
(sceneObject) => sceneObject.object.name === X_AXIS
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
||||||
)
|
)
|
||||||
|
|
||||||
const snappedPoint = new Vector2(
|
// Snap to previous segment's tangent direction when drawing a straight segment
|
||||||
intersectsYAxis ? 0 : intersection2d.x,
|
let snappedToTangent = false
|
||||||
intersectsXAxis ? 0 : intersection2d.y
|
let negativeTangentDirection = false
|
||||||
)
|
|
||||||
|
const disableTangentSnapping = mouseEvent.ctrlKey || mouseEvent.altKey
|
||||||
|
const forceDirectionSnapping = mouseEvent.shiftKey
|
||||||
|
if (!disableTangentSnapping) {
|
||||||
|
const segments: SafeArray<Group> = Object.values(this.activeSegments) // Using the order in the object feels wrong
|
||||||
|
const currentIndex =
|
||||||
|
currentObject instanceof Group ? segments.indexOf(currentObject) : -1
|
||||||
|
const current = segments[currentIndex]
|
||||||
|
if (
|
||||||
|
current?.userData.type === STRAIGHT_SEGMENT &&
|
||||||
|
// This draft check is not strictly necessary currently, but we only want
|
||||||
|
// to snap when drawing a new segment, this makes that more robust.
|
||||||
|
current?.userData.draft
|
||||||
|
) {
|
||||||
|
const prev = segments[currentIndex - 1]
|
||||||
|
if (prev && ARC_SEGMENT_TYPES.includes(prev.userData.type)) {
|
||||||
|
const snapDirection = findTangentDirection(prev)
|
||||||
|
if (snapDirection) {
|
||||||
|
const SNAP_TOLERANCE_PIXELS = 12 * window.devicePixelRatio
|
||||||
|
const SNAP_MIN_DISTANCE_PIXELS = 5 * window.devicePixelRatio
|
||||||
|
const orthoFactor = orthoScale(this.sceneInfra.camControls.camera)
|
||||||
|
|
||||||
|
// See if snapDirection intersects with any of the axes
|
||||||
|
if (intersectsXAxis || intersectsYAxis) {
|
||||||
|
let intersectionPoint: Coords2d | undefined
|
||||||
|
if (intersectsXAxis && intersectsYAxis) {
|
||||||
|
// Current mouse position intersects with both axes (origin) -> that has precedence over tangent so we snap to the origin.
|
||||||
|
intersectionPoint = [0, 0]
|
||||||
|
} else {
|
||||||
|
// Intersects only one axis
|
||||||
|
const axisLine: [Coords2d, Coords2d] = intersectsXAxis
|
||||||
|
? [
|
||||||
|
[0, 0],
|
||||||
|
[1, 0],
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
[0, 0],
|
||||||
|
[0, 1],
|
||||||
|
]
|
||||||
|
// See if that axis line intersects with the tangent direction
|
||||||
|
// Note: this includes both positive and negative tangent directions as it just checks 2 lines.
|
||||||
|
intersectionPoint = calculateIntersectionOfTwoLines({
|
||||||
|
line1: axisLine,
|
||||||
|
line2Angle: getAngle([0, 0], snapDirection),
|
||||||
|
line2Point: current.userData.from,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If yes, see if that intersection point is within tolerance and if yes snap to it.
|
||||||
|
if (
|
||||||
|
intersectionPoint &&
|
||||||
|
getLength(intersectionPoint, snappedPoint) / orthoFactor <
|
||||||
|
SNAP_TOLERANCE_PIXELS
|
||||||
|
) {
|
||||||
|
snappedPoint = intersectionPoint
|
||||||
|
snappedToTangent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!snappedToTangent) {
|
||||||
|
// Otherwise, try to snap to the tangent direction, in both positive and negative directions
|
||||||
|
const { closestPoint, t } = closestPointOnRay(
|
||||||
|
prev.userData.to,
|
||||||
|
snapDirection,
|
||||||
|
snappedPoint,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
forceDirectionSnapping ||
|
||||||
|
(this.sceneInfra.screenSpaceDistance(
|
||||||
|
closestPoint,
|
||||||
|
snappedPoint
|
||||||
|
) < SNAP_TOLERANCE_PIXELS &&
|
||||||
|
// We only want to snap to the tangent direction if the mouse has moved enough to avoid quick jumps
|
||||||
|
// at the beginning of the drag
|
||||||
|
this.sceneInfra.screenSpaceDistance(
|
||||||
|
current.userData.from,
|
||||||
|
current.userData.to
|
||||||
|
) > SNAP_MIN_DISTANCE_PIXELS)
|
||||||
|
) {
|
||||||
|
snappedPoint = closestPoint
|
||||||
|
snappedToTangent = true
|
||||||
|
negativeTangentDirection = t < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap to the main axes if there was no snapping to tangent direction
|
||||||
|
if (!snappedToTangent) {
|
||||||
|
snappedPoint = [
|
||||||
|
intersectsYAxis ? 0 : snappedPoint[0],
|
||||||
|
intersectsXAxis ? 0 : snappedPoint[1],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
snappedPoint: [snappedPoint.x, snappedPoint.y],
|
isSnapped: !!(intersectsYAxis || intersectsXAxis || snappedToTangent),
|
||||||
isSnapped: !!(intersectsYAxis || intersectsXAxis),
|
snappedToTangent,
|
||||||
|
negativeTangentDirection,
|
||||||
|
snappedPoint,
|
||||||
|
intersectsXAxis,
|
||||||
|
intersectsYAxis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
positionDraftPoint({
|
positionDraftPoint({
|
||||||
origin,
|
origin,
|
||||||
yAxis,
|
yAxis,
|
||||||
@ -2634,6 +2785,7 @@ export class SceneEntities {
|
|||||||
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
|
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeSnapProfileStartIntersect2d({
|
maybeSnapProfileStartIntersect2d({
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
intersects,
|
intersects,
|
||||||
@ -2654,25 +2806,26 @@ export class SceneEntities {
|
|||||||
: _intersection2d
|
: _intersection2d
|
||||||
return intersection2d
|
return intersection2d
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragSegment({
|
onDragSegment({
|
||||||
object,
|
object,
|
||||||
intersection2d: _intersection2d,
|
intersection2d: _intersection2d,
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
sketchNodePaths,
|
sketchNodePaths,
|
||||||
planeNodePath,
|
|
||||||
draftInfo,
|
draftInfo,
|
||||||
intersects,
|
intersects,
|
||||||
|
mouseEvent,
|
||||||
}: {
|
}: {
|
||||||
object: Object3D<Object3DEventMap>
|
object: Object3D<Object3DEventMap>
|
||||||
intersection2d: Vector2
|
intersection2d: Vector2
|
||||||
sketchEntryNodePath: PathToNode
|
sketchEntryNodePath: PathToNode
|
||||||
sketchNodePaths: PathToNode[]
|
sketchNodePaths: PathToNode[]
|
||||||
planeNodePath: PathToNode
|
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
draftInfo?: {
|
draftInfo?: {
|
||||||
truncatedAst: Node<Program>
|
truncatedAst: Node<Program>
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
|
mouseEvent: MouseEvent
|
||||||
}) {
|
}) {
|
||||||
const intersection2d = this.maybeSnapProfileStartIntersect2d({
|
const intersection2d = this.maybeSnapProfileStartIntersect2d({
|
||||||
sketchEntryNodePath,
|
sketchEntryNodePath,
|
||||||
@ -2705,10 +2858,12 @@ export class SceneEntities {
|
|||||||
group.userData?.from?.[0],
|
group.userData?.from?.[0],
|
||||||
group.userData?.from?.[1],
|
group.userData?.from?.[1],
|
||||||
]
|
]
|
||||||
const dragTo = this.getSnappedDragPoint({
|
const { snappedPoint: dragTo, snappedToTangent } = this.getSnappedDragPoint(
|
||||||
intersects,
|
|
||||||
intersection2d,
|
intersection2d,
|
||||||
}).snappedPoint
|
intersects,
|
||||||
|
mouseEvent,
|
||||||
|
object
|
||||||
|
)
|
||||||
let modifiedAst = draftInfo
|
let modifiedAst = draftInfo
|
||||||
? draftInfo.truncatedAst
|
? draftInfo.truncatedAst
|
||||||
: { ...this.kclManager.ast }
|
: { ...this.kclManager.ast }
|
||||||
@ -2938,7 +3093,8 @@ export class SceneEntities {
|
|||||||
varDecIndex,
|
varDecIndex,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
orthoFactor,
|
orthoFactor,
|
||||||
sketch
|
sketch,
|
||||||
|
snappedToTangent
|
||||||
)
|
)
|
||||||
|
|
||||||
callBacks.push(
|
callBacks.push(
|
||||||
@ -2949,7 +3105,8 @@ export class SceneEntities {
|
|||||||
varDecIndex,
|
varDecIndex,
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
orthoFactor,
|
orthoFactor,
|
||||||
sketch
|
sketch,
|
||||||
|
snappedToTangent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -2967,6 +3124,7 @@ export class SceneEntities {
|
|||||||
* @param modifiedAst
|
* @param modifiedAst
|
||||||
* @param orthoFactor
|
* @param orthoFactor
|
||||||
* @param sketch
|
* @param sketch
|
||||||
|
* @param snappedToTangent if currently drawn draft segment is snapping to previous arc tangent
|
||||||
*/
|
*/
|
||||||
updateSegment = (
|
updateSegment = (
|
||||||
segment: Path | Sketch['start'],
|
segment: Path | Sketch['start'],
|
||||||
@ -2974,7 +3132,8 @@ export class SceneEntities {
|
|||||||
varDecIndex: number,
|
varDecIndex: number,
|
||||||
modifiedAst: Program,
|
modifiedAst: Program,
|
||||||
orthoFactor: number,
|
orthoFactor: number,
|
||||||
sketch: Sketch
|
sketch: Sketch,
|
||||||
|
snappedToTangent: boolean = false
|
||||||
): (() => SegmentOverlayPayload | null) => {
|
): (() => SegmentOverlayPayload | null) => {
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
const segPathToNode = getNodePathFromSourceRange(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
@ -2998,6 +3157,7 @@ export class SceneEntities {
|
|||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
from: segment.from,
|
from: segment.from,
|
||||||
to: segment.to,
|
to: segment.to,
|
||||||
|
snap: snappedToTangent,
|
||||||
}
|
}
|
||||||
let update: SegmentUtils['update'] | null = null
|
let update: SegmentUtils['update'] | null = null
|
||||||
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
@ -3095,9 +3255,11 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
removeSketchGrid() {
|
removeSketchGrid() {
|
||||||
if (this.axisGroup) this.sceneInfra.scene.remove(this.axisGroup)
|
if (this.axisGroup) this.sceneInfra.scene.remove(this.axisGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
tearDownSketch({ removeAxis = true }: { removeAxis?: boolean }) {
|
tearDownSketch({ removeAxis = true }: { removeAxis?: boolean }) {
|
||||||
// Remove all draft groups
|
// Remove all draft groups
|
||||||
this.draftPointGroups.forEach((draftPointGroup) => {
|
this.draftPointGroups.forEach((draftPointGroup) => {
|
||||||
@ -3125,6 +3287,7 @@ export class SceneEntities {
|
|||||||
this.sceneInfra.camControls.enableRotate = true
|
this.sceneInfra.camControls.enableRotate = true
|
||||||
this.activeSegments = {}
|
this.activeSegments = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseEnterLeaveCallbacks() {
|
mouseEnterLeaveCallbacks() {
|
||||||
return {
|
return {
|
||||||
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
||||||
@ -3333,6 +3496,7 @@ export class SceneEntities {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetOverlays() {
|
resetOverlays() {
|
||||||
this.sceneInfra.modelingSend({
|
this.sceneInfra.modelingSend({
|
||||||
type: 'Set Segment Overlays',
|
type: 'Set Segment Overlays',
|
||||||
@ -3713,6 +3877,7 @@ function getSketchesInfo({
|
|||||||
}
|
}
|
||||||
return sketchesInfo
|
return sketchesInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a SourceRange [x,y,boolean] create a Selections object which contains
|
* Given a SourceRange [x,y,boolean] create a Selections object which contains
|
||||||
* graphSelections with the artifact and codeRef.
|
* graphSelections with the artifact and codeRef.
|
||||||
@ -3747,3 +3912,36 @@ function isGroupStartProfileForCurrentProfile(sketchEntryNodePath: PathToNode) {
|
|||||||
return isProfileStartOfCurrentExpr
|
return isProfileStartOfCurrentExpr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the 2D tangent direction vector at the end of the segmentGroup if it's an arc.
|
||||||
|
function findTangentDirection(segmentGroup: Group) {
|
||||||
|
let tangentDirection: Coords2d | undefined
|
||||||
|
if (segmentGroup.userData.type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
|
const prevSegment = segmentGroup.userData.prevSegment
|
||||||
|
const arcInfo = getTangentialArcToInfo({
|
||||||
|
arcStartPoint: segmentGroup.userData.from,
|
||||||
|
arcEndPoint: segmentGroup.userData.to,
|
||||||
|
tanPreviousPoint: getTanPreviousPoint(prevSegment),
|
||||||
|
obtuse: true,
|
||||||
|
})
|
||||||
|
const tangentAngle =
|
||||||
|
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
|
||||||
|
tangentDirection = [Math.cos(tangentAngle), Math.sin(tangentAngle)]
|
||||||
|
} else if (
|
||||||
|
segmentGroup.userData.type === ARC_SEGMENT ||
|
||||||
|
segmentGroup.userData.type === THREE_POINT_ARC_SEGMENT
|
||||||
|
) {
|
||||||
|
const tangentAngle =
|
||||||
|
deg2Rad(
|
||||||
|
getAngle(segmentGroup.userData.center, segmentGroup.userData.to)
|
||||||
|
) +
|
||||||
|
(Math.PI / 2) * (segmentGroup.userData.ccw ? 1 : -1)
|
||||||
|
tangentDirection = [Math.cos(tangentAngle), Math.sin(tangentAngle)]
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
'Unsupported segment type for tangent direction calculation: ',
|
||||||
|
segmentGroup.userData.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return tangentDirection
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ import * as TWEEN from '@tweenjs/tween.js'
|
|||||||
import type {
|
import type {
|
||||||
Group,
|
Group,
|
||||||
Intersection,
|
Intersection,
|
||||||
Mesh,
|
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Object3D,
|
Object3D,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
@ -13,6 +12,7 @@ import {
|
|||||||
Color,
|
Color,
|
||||||
GridHelper,
|
GridHelper,
|
||||||
LineBasicMaterial,
|
LineBasicMaterial,
|
||||||
|
Mesh,
|
||||||
OrthographicCamera,
|
OrthographicCamera,
|
||||||
Raycaster,
|
Raycaster,
|
||||||
Scene,
|
Scene,
|
||||||
@ -42,7 +42,7 @@ import { compareVec2Epsilon2 } from '@src/lang/std/sketch'
|
|||||||
import type { Axis, NonCodeSelection } from '@src/lib/selections'
|
import type { Axis, NonCodeSelection } from '@src/lib/selections'
|
||||||
import { type BaseUnit } from '@src/lib/settings/settingsTypes'
|
import { type BaseUnit } from '@src/lib/settings/settingsTypes'
|
||||||
import { Themes } from '@src/lib/theme'
|
import { Themes } from '@src/lib/theme'
|
||||||
import { getAngle, throttle } from '@src/lib/utils'
|
import { getAngle, getLength, throttle } from '@src/lib/utils'
|
||||||
import type {
|
import type {
|
||||||
MouseState,
|
MouseState,
|
||||||
SegmentOverlayPayload,
|
SegmentOverlayPayload,
|
||||||
@ -68,6 +68,7 @@ interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
|
|||||||
}
|
}
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnClickCallbackArgs {
|
export interface OnClickCallbackArgs {
|
||||||
mouseEvent: MouseEvent
|
mouseEvent: MouseEvent
|
||||||
intersectionPoint?: {
|
intersectionPoint?: {
|
||||||
@ -93,6 +94,7 @@ interface OnMoveCallbackArgs {
|
|||||||
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
|
||||||
|
|
||||||
type Voidish = void | Promise<void>
|
type Voidish = void | Promise<void>
|
||||||
|
|
||||||
export class SceneInfra {
|
export class SceneInfra {
|
||||||
static instance: SceneInfra
|
static instance: SceneInfra
|
||||||
readonly scene: Scene
|
readonly scene: Scene
|
||||||
@ -130,6 +132,7 @@ export class SceneInfra {
|
|||||||
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
||||||
this.selected = null // following selections between callbacks being set is too tricky
|
this.selected = null // following selections between callbacks being set is too tricky
|
||||||
}
|
}
|
||||||
|
|
||||||
set baseUnit(unit: BaseUnit) {
|
set baseUnit(unit: BaseUnit) {
|
||||||
this._baseUnitMultiplier = baseUnitTomm(unit)
|
this._baseUnitMultiplier = baseUnitTomm(unit)
|
||||||
this.scene.scale.set(
|
this.scene.scale.set(
|
||||||
@ -138,9 +141,11 @@ export class SceneInfra {
|
|||||||
this._baseUnitMultiplier
|
this._baseUnitMultiplier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
set theme(theme: Themes) {
|
set theme(theme: Themes) {
|
||||||
this._theme = theme
|
this._theme = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
resetMouseListeners = () => {
|
resetMouseListeners = () => {
|
||||||
this.setCallbacks({
|
this.setCallbacks({
|
||||||
onDragStart: () => {},
|
onDragStart: () => {},
|
||||||
@ -155,12 +160,15 @@ export class SceneInfra {
|
|||||||
|
|
||||||
modelingSend: SendType = (() => {}) as any
|
modelingSend: SendType = (() => {}) as any
|
||||||
throttledModelingSend: any = (() => {}) as any
|
throttledModelingSend: any = (() => {}) as any
|
||||||
|
|
||||||
setSend(send: SendType) {
|
setSend(send: SendType) {
|
||||||
this.modelingSend = send
|
this.modelingSend = send
|
||||||
this.throttledModelingSend = throttle(send, 100)
|
this.throttledModelingSend = throttle(send, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayTimeout = 0
|
overlayTimeout = 0
|
||||||
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
callbacks: (() => SegmentOverlayPayload | null)[] = []
|
||||||
|
|
||||||
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
|
||||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||||
type: 'add-many',
|
type: 'add-many',
|
||||||
@ -179,6 +187,7 @@ export class SceneInfra {
|
|||||||
data: segmentOverlayPayload,
|
data: segmentOverlayPayload,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayCallbacks(
|
overlayCallbacks(
|
||||||
callbacks: (() => SegmentOverlayPayload | null)[],
|
callbacks: (() => SegmentOverlayPayload | null)[],
|
||||||
instant = false
|
instant = false
|
||||||
@ -195,6 +204,7 @@ export class SceneInfra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
|
||||||
|
|
||||||
updateOverlayDetails({
|
updateOverlayDetails({
|
||||||
handle,
|
handle,
|
||||||
group,
|
group,
|
||||||
@ -349,6 +359,7 @@ export class SceneInfra {
|
|||||||
window.removeEventListener('resize', this.onWindowResize)
|
window.removeEventListener('resize', this.onWindowResize)
|
||||||
// Dispose of any other resources like geometries, materials, textures
|
// Dispose of any other resources like geometries, materials, textures
|
||||||
}
|
}
|
||||||
|
|
||||||
getClientSceneScaleFactor(meshOrGroup: Mesh | Group) {
|
getClientSceneScaleFactor(meshOrGroup: Mesh | Group) {
|
||||||
const orthoFactor = orthoScale(this.camControls.camera)
|
const orthoFactor = orthoScale(this.camControls.camera)
|
||||||
const factor =
|
const factor =
|
||||||
@ -358,6 +369,7 @@ export class SceneInfra {
|
|||||||
this._baseUnitMultiplier
|
this._baseUnitMultiplier
|
||||||
return factor
|
return factor
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaneIntersectPoint = (): {
|
getPlaneIntersectPoint = (): {
|
||||||
twoD?: Vector2
|
twoD?: Vector2
|
||||||
threeD?: Vector3
|
threeD?: Vector3
|
||||||
@ -556,6 +568,7 @@ export class SceneInfra {
|
|||||||
(a, b) => a.distance - b.distance
|
(a, b) => a.distance - b.distance
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMouseState(mouseState: MouseState) {
|
updateMouseState(mouseState: MouseState) {
|
||||||
if (this.lastMouseState.type === mouseState.type) return
|
if (this.lastMouseState.type === mouseState.type) return
|
||||||
this.lastMouseState = mouseState
|
this.lastMouseState = mouseState
|
||||||
@ -665,6 +678,13 @@ export class SceneInfra {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
screenSpaceDistance(a: Coords2d, b: Coords2d): number {
|
||||||
|
const dummy = new Mesh()
|
||||||
|
dummy.position.set(0, 0, 0)
|
||||||
|
const scale = this.getClientSceneScaleFactor(dummy)
|
||||||
|
return getLength(a, b) / scale
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function baseUnitTomm(baseUnit: BaseUnit) {
|
function baseUnitTomm(baseUnit: BaseUnit) {
|
||||||
|
@ -54,6 +54,7 @@ import {
|
|||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
STRAIGHT_SEGMENT_BODY,
|
STRAIGHT_SEGMENT_BODY,
|
||||||
STRAIGHT_SEGMENT_DASH,
|
STRAIGHT_SEGMENT_DASH,
|
||||||
|
STRAIGHT_SEGMENT_SNAP_LINE,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT_BODY,
|
TANGENTIAL_ARC_TO_SEGMENT_BODY,
|
||||||
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
||||||
@ -216,7 +217,17 @@ class StraightSegment implements SegmentUtils {
|
|||||||
segmentGroup.add(lengthIndicatorGroup)
|
segmentGroup.add(lengthIndicatorGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDraftSegment) {
|
||||||
|
const snapLine = createLine({
|
||||||
|
from: [0, 0],
|
||||||
|
to: [0, 0],
|
||||||
|
color: 0xcccccc,
|
||||||
|
})
|
||||||
|
snapLine.name = STRAIGHT_SEGMENT_SNAP_LINE
|
||||||
|
segmentGroup.add(snapLine)
|
||||||
|
}
|
||||||
segmentGroup.add(mesh, extraSegmentGroup)
|
segmentGroup.add(mesh, extraSegmentGroup)
|
||||||
|
|
||||||
let updateOverlaysCallback = this.update({
|
let updateOverlaysCallback = this.update({
|
||||||
prevSegment,
|
prevSegment,
|
||||||
input,
|
input,
|
||||||
@ -265,20 +276,39 @@ class StraightSegment implements SegmentUtils {
|
|||||||
isHandlesVisible = !shouldHideHover
|
isHandlesVisible = !shouldHideHover
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dir = new Vector3()
|
||||||
|
.subVectors(
|
||||||
|
new Vector3(to[0], to[1], 0),
|
||||||
|
new Vector3(from[0], from[1], 0)
|
||||||
|
)
|
||||||
|
.normalize()
|
||||||
|
|
||||||
if (arrowGroup) {
|
if (arrowGroup) {
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
|
||||||
const dir = new Vector3()
|
|
||||||
.subVectors(
|
|
||||||
new Vector3(to[0], to[1], 0),
|
|
||||||
new Vector3(from[0], from[1], 0)
|
|
||||||
)
|
|
||||||
.normalize()
|
|
||||||
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
arrowGroup.scale.set(scale, scale, scale)
|
arrowGroup.scale.set(scale, scale, scale)
|
||||||
arrowGroup.visible = isHandlesVisible
|
arrowGroup.visible = isHandlesVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const snapLine = group.getObjectByName(STRAIGHT_SEGMENT_SNAP_LINE) as Line
|
||||||
|
if (snapLine) {
|
||||||
|
snapLine.visible = !!input.snap
|
||||||
|
if (snapLine.visible) {
|
||||||
|
const snapLineFrom = to
|
||||||
|
const snapLineTo = new Vector3(to[0], to[1], 0).addScaledVector(
|
||||||
|
dir,
|
||||||
|
// Draw a large enough line that reaches the screen edge
|
||||||
|
// Cleaner way would be to draw in screen space
|
||||||
|
9999999 * scale
|
||||||
|
)
|
||||||
|
updateLine(snapLine, {
|
||||||
|
from: snapLineFrom,
|
||||||
|
to: [snapLineTo.x, snapLineTo.y],
|
||||||
|
scale,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
if (extraSegmentGroup) {
|
if (extraSegmentGroup) {
|
||||||
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
@ -431,33 +461,10 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
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)
|
||||||
|
|
||||||
let previousPoint = prevSegment.from
|
|
||||||
if (prevSegment?.type === 'TangentialArcTo') {
|
|
||||||
previousPoint = getTangentPointFromPreviousArc(
|
|
||||||
prevSegment.center,
|
|
||||||
prevSegment.ccw,
|
|
||||||
prevSegment.to
|
|
||||||
)
|
|
||||||
} else if (prevSegment?.type === 'ArcThreePoint') {
|
|
||||||
const arcDetails = calculate_circle_from_3_points(
|
|
||||||
prevSegment.p1[0],
|
|
||||||
prevSegment.p1[1],
|
|
||||||
prevSegment.p2[0],
|
|
||||||
prevSegment.p2[1],
|
|
||||||
prevSegment.p3[0],
|
|
||||||
prevSegment.p3[1]
|
|
||||||
)
|
|
||||||
previousPoint = getTangentPointFromPreviousArc(
|
|
||||||
[arcDetails.center_x, arcDetails.center_y],
|
|
||||||
!isClockwise([prevSegment.p1, prevSegment.p2, prevSegment.p3]),
|
|
||||||
prevSegment.p3
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const arcInfo = getTangentialArcToInfo({
|
const arcInfo = getTangentialArcToInfo({
|
||||||
arcStartPoint: from,
|
arcStartPoint: from,
|
||||||
arcEndPoint: to,
|
arcEndPoint: to,
|
||||||
tanPreviousPoint: previousPoint,
|
tanPreviousPoint: getTanPreviousPoint(prevSegment),
|
||||||
obtuse: true,
|
obtuse: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -539,6 +546,32 @@ class TangentialArcToSegment implements SegmentUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTanPreviousPoint(prevSegment: Sketch['paths'][number]) {
|
||||||
|
let previousPoint = prevSegment.from
|
||||||
|
if (prevSegment.type === 'TangentialArcTo') {
|
||||||
|
previousPoint = getTangentPointFromPreviousArc(
|
||||||
|
prevSegment.center,
|
||||||
|
prevSegment.ccw,
|
||||||
|
prevSegment.to
|
||||||
|
)
|
||||||
|
} else if (prevSegment.type === 'ArcThreePoint') {
|
||||||
|
const arcDetails = calculate_circle_from_3_points(
|
||||||
|
prevSegment.p1[0],
|
||||||
|
prevSegment.p1[1],
|
||||||
|
prevSegment.p2[0],
|
||||||
|
prevSegment.p2[1],
|
||||||
|
prevSegment.p3[0],
|
||||||
|
prevSegment.p3[1]
|
||||||
|
)
|
||||||
|
previousPoint = getTangentPointFromPreviousArc(
|
||||||
|
[arcDetails.center_x, arcDetails.center_y],
|
||||||
|
!isClockwise([prevSegment.p1, prevSegment.p2, prevSegment.p3]),
|
||||||
|
prevSegment.p3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return previousPoint
|
||||||
|
}
|
||||||
|
|
||||||
class CircleSegment implements SegmentUtils {
|
class CircleSegment implements SegmentUtils {
|
||||||
init: SegmentUtils['init'] = ({
|
init: SegmentUtils['init'] = ({
|
||||||
prevSegment,
|
prevSegment,
|
||||||
@ -1018,21 +1051,18 @@ class ArcSegment implements SegmentUtils {
|
|||||||
const centerToFromLine = createLine({
|
const centerToFromLine = createLine({
|
||||||
from: center,
|
from: center,
|
||||||
to: from,
|
to: from,
|
||||||
scale,
|
|
||||||
color: grey, // Light gray color for the line
|
color: grey, // Light gray color for the line
|
||||||
})
|
})
|
||||||
centerToFromLine.name = ARC_CENTER_TO_FROM
|
centerToFromLine.name = ARC_CENTER_TO_FROM
|
||||||
const centerToToLine = createLine({
|
const centerToToLine = createLine({
|
||||||
from: center,
|
from: center,
|
||||||
to,
|
to,
|
||||||
scale,
|
|
||||||
color: grey, // Light gray color for the line
|
color: grey, // Light gray color for the line
|
||||||
})
|
})
|
||||||
centerToToLine.name = ARC_CENTER_TO_TO
|
centerToToLine.name = ARC_CENTER_TO_TO
|
||||||
const angleReferenceLine = createLine({
|
const angleReferenceLine = createLine({
|
||||||
from: [center[0] + (ANGLE_INDICATOR_RADIUS - 2) * scale, center[1]],
|
from: [center[0] + (ANGLE_INDICATOR_RADIUS - 2) * scale, center[1]],
|
||||||
to: [center[0] + (ANGLE_INDICATOR_RADIUS + 2) * scale, center[1]],
|
to: [center[0] + (ANGLE_INDICATOR_RADIUS + 2) * scale, center[1]],
|
||||||
scale,
|
|
||||||
color: grey, // Light gray color for the line
|
color: grey, // Light gray color for the line
|
||||||
})
|
})
|
||||||
angleReferenceLine.name = ARC_ANGLE_REFERENCE_LINE
|
angleReferenceLine.name = ARC_ANGLE_REFERENCE_LINE
|
||||||
@ -1398,7 +1428,7 @@ class ThreePointArcSegment implements SegmentUtils {
|
|||||||
p3,
|
p3,
|
||||||
radius,
|
radius,
|
||||||
center,
|
center,
|
||||||
ccw: false,
|
ccw: !isClockwise([p1, p2, p3]),
|
||||||
prevSegment,
|
prevSegment,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -1992,12 +2022,10 @@ export function dashedStraight(
|
|||||||
function createLine({
|
function createLine({
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
scale,
|
|
||||||
color,
|
color,
|
||||||
}: {
|
}: {
|
||||||
from: [number, number]
|
from: [number, number]
|
||||||
to: [number, number]
|
to: [number, number]
|
||||||
scale: number
|
|
||||||
color: number
|
color: number
|
||||||
}): Line {
|
}): Line {
|
||||||
// Implementation for creating a line
|
// Implementation for creating a line
|
||||||
|
@ -312,7 +312,7 @@ export function getPathToExtrudeForSegmentSelection(
|
|||||||
export function mutateAstWithTagForSketchSegment(
|
export function mutateAstWithTagForSketchSegment(
|
||||||
astClone: Node<Program>,
|
astClone: Node<Program>,
|
||||||
pathToSegmentNode: PathToNode
|
pathToSegmentNode: PathToNode
|
||||||
): { modifiedAst: Program; tag: string } | Error {
|
): { modifiedAst: Node<Program>; tag: string } | Error {
|
||||||
const segmentNode = getNodeFromPath<CallExpression | CallExpressionKw>(
|
const segmentNode = getNodeFromPath<CallExpression | CallExpressionKw>(
|
||||||
astClone,
|
astClone,
|
||||||
pathToSegmentNode,
|
pathToSegmentNode,
|
||||||
|
@ -20,10 +20,12 @@ import {
|
|||||||
} from '@src/lang/constants'
|
} from '@src/lang/constants'
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
|
createBinaryExpression,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createCallExpressionStdLibKw,
|
createCallExpressionStdLibKw,
|
||||||
createLabeledArg,
|
createLabeledArg,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
|
createLocalName,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
createPipeExpression,
|
createPipeExpression,
|
||||||
createPipeSubstitution,
|
createPipeSubstitution,
|
||||||
@ -60,6 +62,7 @@ import type {
|
|||||||
SingleValueInput,
|
SingleValueInput,
|
||||||
SketchLineHelper,
|
SketchLineHelper,
|
||||||
SketchLineHelperKw,
|
SketchLineHelperKw,
|
||||||
|
addCall,
|
||||||
} from '@src/lang/std/stdTypes'
|
} from '@src/lang/std/stdTypes'
|
||||||
import {
|
import {
|
||||||
findKwArg,
|
findKwArg,
|
||||||
@ -2324,8 +2327,9 @@ export const circleThreePoint: SketchLineHelperKw = {
|
|||||||
return finalConstraints
|
return finalConstraints
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLine: SketchLineHelperKw = {
|
export const angledLine: SketchLineHelperKw = {
|
||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback, snaps }) => {
|
||||||
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
|
||||||
const { from, to } = segmentInput
|
const { from, to } = segmentInput
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -2334,11 +2338,27 @@ export const angledLine: SketchLineHelperKw = {
|
|||||||
if (err(_node1)) return _node1
|
if (err(_node1)) return _node1
|
||||||
const { node: pipe } = _node1
|
const { node: pipe } = _node1
|
||||||
|
|
||||||
const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0))
|
// When snapping to previous arc's tangent direction, create this expression:
|
||||||
|
// angledLine({ angle = tangentToEnd(arc001), length = 12 }, %)
|
||||||
|
// Or if snapping to the negative direction:
|
||||||
|
// angledLine({ angle = tangentToEnd(arc001) + turns::HALF_TURN, length = 12 }, %)
|
||||||
|
const newAngleVal = snaps?.previousArcTag
|
||||||
|
? snaps.negativeTangentDirection
|
||||||
|
? createBinaryExpression([
|
||||||
|
createCallExpression('tangentToEnd', [
|
||||||
|
createLocalName(snaps?.previousArcTag),
|
||||||
|
]),
|
||||||
|
'+',
|
||||||
|
createLocalName('turns::HALF_TURN'),
|
||||||
|
])
|
||||||
|
: createCallExpression('tangentToEnd', [
|
||||||
|
createLocalName(snaps?.previousArcTag),
|
||||||
|
])
|
||||||
|
: createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
|
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
|
||||||
const newLine = createCallExpressionStdLibKw('angledLine', null, [
|
const newLine = createCallExpressionStdLibKw('angledLine', null, [
|
||||||
createLabeledArg('angle', newAngleVal),
|
createLabeledArg(ARG_ANGLE, newAngleVal),
|
||||||
createLabeledArg('length', newLengthVal),
|
createLabeledArg(ARG_LENGTH, newLengthVal),
|
||||||
])
|
])
|
||||||
|
|
||||||
if (replaceExistingCallback) {
|
if (replaceExistingCallback) {
|
||||||
@ -2348,7 +2368,12 @@ export const angledLine: SketchLineHelperKw = {
|
|||||||
type: 'labeledArg',
|
type: 'labeledArg',
|
||||||
key: 'angle',
|
key: 'angle',
|
||||||
argType: 'angle',
|
argType: 'angle',
|
||||||
expr: newAngleVal,
|
// We cannot pass newAngleVal to expr because it is a Node<Literal>.
|
||||||
|
// We couldn't change that type to be Node<Expr> because there is a lot of code assuming it to be Node<Literal>.
|
||||||
|
// So we added a new optional overrideExpr which can be Node<Expr> and this is used if present in sketchcombos/createNode().
|
||||||
|
expr:
|
||||||
|
newAngleVal.type === 'Literal' ? newAngleVal : createLiteral(''),
|
||||||
|
overrideExpr: newAngleVal,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'labeledArg',
|
type: 'labeledArg',
|
||||||
@ -3309,6 +3334,7 @@ interface CreateLineFnCallArgs {
|
|||||||
fnName: ToolTip
|
fnName: ToolTip
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
spliceBetween?: boolean
|
spliceBetween?: boolean
|
||||||
|
snaps?: addCall['snaps']
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
@ -3318,6 +3344,7 @@ export function addNewSketchLn({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
input: segmentInput,
|
input: segmentInput,
|
||||||
spliceBetween = false,
|
spliceBetween = false,
|
||||||
|
snaps,
|
||||||
}: CreateLineFnCallArgs):
|
}: CreateLineFnCallArgs):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -3347,6 +3374,7 @@ export function addNewSketchLn({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
segmentInput,
|
segmentInput,
|
||||||
spliceBetween,
|
spliceBetween,
|
||||||
|
snaps,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1538,20 +1538,22 @@ export function removeSingleConstraint({
|
|||||||
}
|
}
|
||||||
if (inputToReplace.type === 'arrayItem') {
|
if (inputToReplace.type === 'arrayItem') {
|
||||||
const values = inputs.map((arg) => {
|
const values = inputs.map((arg) => {
|
||||||
|
const argExpr = arg.overrideExpr ?? arg.expr
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
(arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') &&
|
(arg.type === 'arrayItem' || arg.type === 'arrayOrObjItem') &&
|
||||||
arg.index === inputToReplace.index
|
arg.index === inputToReplace.index
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return arg.expr
|
return argExpr
|
||||||
const literal = rawArgs.find(
|
const rawArg = rawArgs.find(
|
||||||
(rawValue) =>
|
(rawValue) =>
|
||||||
(rawValue.type === 'arrayItem' ||
|
(rawValue.type === 'arrayItem' ||
|
||||||
rawValue.type === 'arrayOrObjItem') &&
|
rawValue.type === 'arrayOrObjItem') &&
|
||||||
rawValue.index === inputToReplace.index
|
rawValue.index === inputToReplace.index
|
||||||
)?.expr
|
)
|
||||||
return (arg.index === inputToReplace.index && literal) || arg.expr
|
const literal = rawArg?.overrideExpr ?? rawArg?.expr
|
||||||
|
return (arg.index === inputToReplace.index && literal) || argExpr
|
||||||
})
|
})
|
||||||
if (callExp.node.type === 'CallExpression') {
|
if (callExp.node.type === 'CallExpression') {
|
||||||
return createStdlibCallExpression(
|
return createStdlibCallExpression(
|
||||||
@ -1589,6 +1591,7 @@ export function removeSingleConstraint({
|
|||||||
const objInput: Parameters<typeof createObjectExpression>[0] = {}
|
const objInput: Parameters<typeof createObjectExpression>[0] = {}
|
||||||
const kwArgInput: ReturnType<typeof createLabeledArg>[] = []
|
const kwArgInput: ReturnType<typeof createLabeledArg>[] = []
|
||||||
inputs.forEach((currentArg) => {
|
inputs.forEach((currentArg) => {
|
||||||
|
const currentArgExpr = currentArg.overrideExpr ?? currentArg.expr
|
||||||
if (
|
if (
|
||||||
// should be one of these, return early to make TS happy.
|
// should be one of these, return early to make TS happy.
|
||||||
currentArg.type !== 'objectProperty' &&
|
currentArg.type !== 'objectProperty' &&
|
||||||
@ -1619,8 +1622,11 @@ export function removeSingleConstraint({
|
|||||||
if (!arrayInput[currentArg.key]) {
|
if (!arrayInput[currentArg.key]) {
|
||||||
arrayInput[currentArg.key] = []
|
arrayInput[currentArg.key] = []
|
||||||
}
|
}
|
||||||
arrayInput[inputToReplace.key][inputToReplace.index] =
|
const rawLiteralArrayInObjectExpr =
|
||||||
|
rawLiteralArrayInObject.overrideExpr ??
|
||||||
rawLiteralArrayInObject.expr
|
rawLiteralArrayInObject.expr
|
||||||
|
arrayInput[inputToReplace.key][inputToReplace.index] =
|
||||||
|
rawLiteralArrayInObjectExpr
|
||||||
let existingKwgForKey = kwArgInput.find(
|
let existingKwgForKey = kwArgInput.find(
|
||||||
(kwArg) => kwArg.label.name === currentArg.key
|
(kwArg) => kwArg.label.name === currentArg.key
|
||||||
)
|
)
|
||||||
@ -1633,7 +1639,7 @@ export function removeSingleConstraint({
|
|||||||
}
|
}
|
||||||
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
||||||
existingKwgForKey.arg.elements[inputToReplace.index] =
|
existingKwgForKey.arg.elements[inputToReplace.index] =
|
||||||
rawLiteralArrayInObject.expr
|
rawLiteralArrayInObjectExpr
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
inputToReplace.type === 'objectProperty' &&
|
inputToReplace.type === 'objectProperty' &&
|
||||||
@ -1642,10 +1648,11 @@ export function removeSingleConstraint({
|
|||||||
rawLiteralObjProp?.key === inputToReplace.key &&
|
rawLiteralObjProp?.key === inputToReplace.key &&
|
||||||
currentArg.key === inputToReplace.key
|
currentArg.key === inputToReplace.key
|
||||||
) {
|
) {
|
||||||
objInput[inputToReplace.key] = rawLiteralObjProp.expr
|
objInput[inputToReplace.key] =
|
||||||
|
rawLiteralObjProp.overrideExpr ?? rawLiteralObjProp.expr
|
||||||
} else if (currentArg.type === 'arrayInObject') {
|
} else if (currentArg.type === 'arrayInObject') {
|
||||||
if (!arrayInput[currentArg.key]) arrayInput[currentArg.key] = []
|
if (!arrayInput[currentArg.key]) arrayInput[currentArg.key] = []
|
||||||
arrayInput[currentArg.key][currentArg.index] = currentArg.expr
|
arrayInput[currentArg.key][currentArg.index] = currentArgExpr
|
||||||
let existingKwgForKey = kwArgInput.find(
|
let existingKwgForKey = kwArgInput.find(
|
||||||
(kwArg) => kwArg.label.name === currentArg.key
|
(kwArg) => kwArg.label.name === currentArg.key
|
||||||
)
|
)
|
||||||
@ -1657,10 +1664,10 @@ export function removeSingleConstraint({
|
|||||||
kwArgInput.push(existingKwgForKey)
|
kwArgInput.push(existingKwgForKey)
|
||||||
}
|
}
|
||||||
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
if (existingKwgForKey.arg.type === 'ArrayExpression') {
|
||||||
existingKwgForKey.arg.elements[currentArg.index] = currentArg.expr
|
existingKwgForKey.arg.elements[currentArg.index] = currentArgExpr
|
||||||
}
|
}
|
||||||
} else if (currentArg.type === 'objectProperty') {
|
} else if (currentArg.type === 'objectProperty') {
|
||||||
objInput[currentArg.key] = currentArg.expr
|
objInput[currentArg.key] = currentArgExpr
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const createObjParam: Parameters<typeof createObjectExpression>[0] = {}
|
const createObjParam: Parameters<typeof createObjectExpression>[0] = {}
|
||||||
@ -1694,7 +1701,7 @@ export function removeSingleConstraint({
|
|||||||
|
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
callExp.node.callee.name.name as any,
|
callExp.node.callee.name.name as any,
|
||||||
rawArgs[0].expr,
|
rawArgs[0].overrideExpr ?? rawArgs[0].expr,
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -44,6 +44,7 @@ interface StraightSegmentInput {
|
|||||||
type: 'straight-segment'
|
type: 'straight-segment'
|
||||||
from: [number, number]
|
from: [number, number]
|
||||||
to: [number, number]
|
to: [number, number]
|
||||||
|
snap?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Inputs for arcs, excluding tangentialArc for reasons explain in the
|
/** Inputs for arcs, excluding tangentialArc for reasons explain in the
|
||||||
@ -92,6 +93,12 @@ export interface addCall extends ModifyAstBase {
|
|||||||
) => CreatedSketchExprResult | Error
|
) => CreatedSketchExprResult | Error
|
||||||
referencedSegment?: Path
|
referencedSegment?: Path
|
||||||
spliceBetween?: boolean
|
spliceBetween?: boolean
|
||||||
|
snaps?: {
|
||||||
|
previousArcTag?: string
|
||||||
|
negativeTangentDirection: boolean
|
||||||
|
xAxis?: boolean
|
||||||
|
yAxis?: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface updateArgs extends ModifyAstBase {
|
interface updateArgs extends ModifyAstBase {
|
||||||
@ -121,18 +128,21 @@ export interface SingleValueInput<T> {
|
|||||||
type: 'singleValue'
|
type: 'singleValue'
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
export interface ArrayItemInput<T> {
|
export interface ArrayItemInput<T> {
|
||||||
type: 'arrayItem'
|
type: 'arrayItem'
|
||||||
index: 0 | 1
|
index: 0 | 1
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
export interface ObjectPropertyInput<T> {
|
export interface ObjectPropertyInput<T> {
|
||||||
type: 'objectProperty'
|
type: 'objectProperty'
|
||||||
key: InputArgKeys
|
key: InputArgKeys
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArrayOrObjItemInput<T> {
|
interface ArrayOrObjItemInput<T> {
|
||||||
@ -141,6 +151,7 @@ interface ArrayOrObjItemInput<T> {
|
|||||||
index: 0 | 1
|
index: 0 | 1
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ArrayInObject<T> {
|
interface ArrayInObject<T> {
|
||||||
@ -149,6 +160,7 @@ interface ArrayInObject<T> {
|
|||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
index: 0 | 1
|
index: 0 | 1
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LabeledArg<T> {
|
interface LabeledArg<T> {
|
||||||
@ -156,6 +168,7 @@ interface LabeledArg<T> {
|
|||||||
key: InputArgKeys
|
key: InputArgKeys
|
||||||
argType: LineInputsType
|
argType: LineInputsType
|
||||||
expr: T
|
expr: T
|
||||||
|
overrideExpr?: Node<Expr>
|
||||||
}
|
}
|
||||||
|
|
||||||
type _InputArg<T> =
|
type _InputArg<T> =
|
||||||
|
@ -23,6 +23,10 @@ export function isArray(val: any): val is unknown[] {
|
|||||||
return Array.isArray(val)
|
return Array.isArray(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SafeArray<T> = Omit<Array<T>, number> & {
|
||||||
|
[index: number]: T | undefined
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An alternative to `Object.keys()` that returns an array of keys with types.
|
* An alternative to `Object.keys()` that returns an array of keys with types.
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { Coords2d } from '@src/lang/std/sketch'
|
import type { Coords2d } from '@src/lang/std/sketch'
|
||||||
import { isPointsCCW } from '@src/lang/wasm'
|
import { isPointsCCW } from '@src/lang/wasm'
|
||||||
import { initPromise } from '@src/lang/wasmUtils'
|
import { initPromise } from '@src/lang/wasmUtils'
|
||||||
|
import { closestPointOnRay } from '@src/lib/utils2d'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -20,3 +21,72 @@ describe('test isPointsCW', () => {
|
|||||||
expect(CW).toBe(-1)
|
expect(CW).toBe(-1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('test closestPointOnRay', () => {
|
||||||
|
test('point lies on ray', () => {
|
||||||
|
const rayOrigin: Coords2d = [0, 0]
|
||||||
|
const rayDirection: Coords2d = [1, 0]
|
||||||
|
const pointToCheck: Coords2d = [7, 0]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(rayOrigin, rayDirection, pointToCheck)
|
||||||
|
expect(result.closestPoint).toEqual([7, 0])
|
||||||
|
expect(result.t).toBe(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('point is above ray', () => {
|
||||||
|
const rayOrigin: Coords2d = [1, 0]
|
||||||
|
const rayDirection: Coords2d = [1, 0]
|
||||||
|
const pointToCheck: Coords2d = [7, 7]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(rayOrigin, rayDirection, pointToCheck)
|
||||||
|
expect(result.closestPoint).toEqual([7, 0])
|
||||||
|
expect(result.t).toBe(6)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('point lies behind ray origin and allowNegative=false', () => {
|
||||||
|
const rayOrigin: Coords2d = [0, 0]
|
||||||
|
const rayDirection: Coords2d = [1, 0]
|
||||||
|
const pointToCheck: Coords2d = [-7, 7]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(rayOrigin, rayDirection, pointToCheck)
|
||||||
|
expect(result.closestPoint).toEqual([0, 0])
|
||||||
|
expect(result.t).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('point lies behind ray origin and allowNegative=true', () => {
|
||||||
|
const rayOrigin: Coords2d = [0, 0]
|
||||||
|
const rayDirection: Coords2d = [1, 0]
|
||||||
|
const pointToCheck: Coords2d = [-7, 7]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(
|
||||||
|
rayOrigin,
|
||||||
|
rayDirection,
|
||||||
|
pointToCheck,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
expect(result.closestPoint).toEqual([-7, 0])
|
||||||
|
expect(result.t).toBe(-7)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('diagonal ray and point', () => {
|
||||||
|
const rayOrigin: Coords2d = [0, 0]
|
||||||
|
const rayDirection: Coords2d = [1, 1]
|
||||||
|
const pointToCheck: Coords2d = [3, 4]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(rayOrigin, rayDirection, pointToCheck)
|
||||||
|
expect(result.closestPoint[0]).toBeCloseTo(3.5)
|
||||||
|
expect(result.closestPoint[1]).toBeCloseTo(3.5)
|
||||||
|
expect(result.t).toBeCloseTo(4.95, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('non-normalized direction vector', () => {
|
||||||
|
const rayOrigin: Coords2d = [0, 0]
|
||||||
|
const rayDirection: Coords2d = [2, 2]
|
||||||
|
const pointToCheck: Coords2d = [3, 4]
|
||||||
|
|
||||||
|
const result = closestPointOnRay(rayOrigin, rayDirection, pointToCheck)
|
||||||
|
expect(result.closestPoint[0]).toBeCloseTo(3.5)
|
||||||
|
expect(result.closestPoint[1]).toBeCloseTo(3.5)
|
||||||
|
expect(result.t).toBeCloseTo(4.95, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -17,3 +17,38 @@ export function getTangentPointFromPreviousArc(
|
|||||||
Math.sin(deg2Rad(tangentialAngle)) * 10 + lastArcEnd[1],
|
Math.sin(deg2Rad(tangentialAngle)) * 10 + lastArcEnd[1],
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function closestPointOnRay(
|
||||||
|
rayOrigin: Coords2d,
|
||||||
|
rayDirection: Coords2d,
|
||||||
|
pointToCheck: Coords2d,
|
||||||
|
allowNegative = false
|
||||||
|
) {
|
||||||
|
const dirMagnitude = Math.sqrt(
|
||||||
|
rayDirection[0] * rayDirection[0] + rayDirection[1] * rayDirection[1]
|
||||||
|
)
|
||||||
|
const normalizedDir: Coords2d = [
|
||||||
|
rayDirection[0] / dirMagnitude,
|
||||||
|
rayDirection[1] / dirMagnitude,
|
||||||
|
]
|
||||||
|
|
||||||
|
const originToPoint: Coords2d = [
|
||||||
|
pointToCheck[0] - rayOrigin[0],
|
||||||
|
pointToCheck[1] - rayOrigin[1],
|
||||||
|
]
|
||||||
|
|
||||||
|
let t =
|
||||||
|
originToPoint[0] * normalizedDir[0] + originToPoint[1] * normalizedDir[1]
|
||||||
|
|
||||||
|
if (!allowNegative) {
|
||||||
|
t = Math.max(0, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
closestPoint: [
|
||||||
|
rayOrigin[0] + normalizedDir[0] * t,
|
||||||
|
rayOrigin[1] + normalizedDir[1] * t,
|
||||||
|
] as Coords2d,
|
||||||
|
t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -866,10 +866,11 @@ export const modelingMachine = setup({
|
|||||||
if (twoD) {
|
if (twoD) {
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
type: 'click in scene',
|
type: 'click in scene',
|
||||||
data: sceneEntitiesManager.getSnappedDragPoint({
|
data: sceneEntitiesManager.getSnappedDragPoint(
|
||||||
intersection2d: twoD,
|
twoD,
|
||||||
intersects: args.intersects,
|
args.intersects,
|
||||||
}).snappedPoint,
|
args.mouseEvent
|
||||||
|
).snappedPoint,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.error('No intersection point found')
|
console.error('No intersection point found')
|
||||||
@ -1238,10 +1239,11 @@ export const modelingMachine = setup({
|
|||||||
if (!intersectionPoint?.twoD) return
|
if (!intersectionPoint?.twoD) return
|
||||||
if (!context.sketchDetails) return
|
if (!context.sketchDetails) return
|
||||||
const { snappedPoint, isSnapped } =
|
const { snappedPoint, isSnapped } =
|
||||||
sceneEntitiesManager.getSnappedDragPoint({
|
sceneEntitiesManager.getSnappedDragPoint(
|
||||||
intersection2d: intersectionPoint.twoD,
|
intersectionPoint.twoD,
|
||||||
intersects: args.intersects,
|
args.intersects,
|
||||||
})
|
args.mouseEvent
|
||||||
|
)
|
||||||
if (isSnapped) {
|
if (isSnapped) {
|
||||||
sceneEntitiesManager.positionDraftPoint({
|
sceneEntitiesManager.positionDraftPoint({
|
||||||
snappedPoint: new Vector2(...snappedPoint),
|
snappedPoint: new Vector2(...snappedPoint),
|
||||||
|
@ -82,16 +82,14 @@ const SignIn = () => {
|
|||||||
style={
|
style={
|
||||||
isDesktop()
|
isDesktop()
|
||||||
? ({
|
? ({
|
||||||
'-webkit-app-region': 'drag',
|
WebkitAppRegion: 'drag',
|
||||||
} as CSSProperties)
|
} as CSSProperties)
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={
|
style={
|
||||||
isDesktop()
|
isDesktop() ? ({ WebkitAppRegion: 'no-drag' } as CSSProperties) : {}
|
||||||
? ({ '-webkit-app-region': 'no-drag' } as CSSProperties)
|
|
||||||
: {}
|
|
||||||
}
|
}
|
||||||
className="body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
className="body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
|
||||||
>
|
>
|
||||||
|
Reference in New Issue
Block a user