Add basic horizontal and vertical snapping for Line tool (#4465)

* Add on-click snapping behavior

* Fix math error with horizontal/vertical determination

* Move snap constant to where other sketch constants are

* fmt

* Fixing up some of the tests, some still need updating

* A couple other little PW test issues

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Fix remaining tests that need updating

* Make `yarn lint` happy

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Frank Noirot
2024-11-13 09:41:27 -05:00
committed by GitHub
parent c5d051855f
commit b798f7da03
19 changed files with 113 additions and 80 deletions

View File

@ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> xLine(${commonPoints.num1}, %)`)
} }
await page.waitForTimeout(500) await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> xLine(${commonPoints.num1}, %)
|> line([0, ${commonPoints.num1 + 0.01}], %)`) |> yLine(${commonPoints.num1 + 0.01}, %)`)
} else { } else {
await page.waitForTimeout(500) await page.waitForTimeout(500)
} }
@ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
if (openPanes.includes('code')) { if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> xLine(${commonPoints.num1}, %)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> yLine(${commonPoints.num1 + 0.01}, %)
|> lineTo([0, ${commonPoints.num3}], %)`) |> xLine(${commonPoints.num2 * -1}, %)`)
} }
// deselect line tool // deselect line tool
@ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
await u.openKclCodePanel() await u.openKclCodePanel()
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %, $seg01) |> xLine(${commonPoints.num1}, %, $seg01)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> yLine(${commonPoints.num1 + 0.01}, %)
|> angledLine([180, segLen(seg01)], %)`) |> xLine(-segLen(seg01), %)`)
} }
test.describe('Basic sketch', () => { test.describe('Basic sketch', () => {

View File

@ -452,7 +452,7 @@ sketch002 = startSketchOn(extrude001, seg03)
) )
}) })
test(`Verify axis and origin snapping`, async ({ test(`Verify axis, origin, and horizontal snapping`, async ({
app, app,
editor, editor,
toolbar, toolbar,
@ -505,7 +505,7 @@ test(`Verify axis and origin snapping`, async ({
const expectedCodeSnippets = { const expectedCodeSnippets = {
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`, sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`, pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
segmentOnXAxis: `lineTo([${xAxisSloppy.kcl[0]}, ${xAxisSloppy.kcl[1]}], %)`, segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`, afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`, afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
} }

View File

@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
'persistCode', 'persistCode',
`sketch001 = startSketchOn('XZ') `sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %) |> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %) |> xLine(12.73, %)
|> tangentialArcTo([24.95, -5.38], %)` |> tangentialArcTo([24.95, -5.38], %)`
) )
}) })
@ -156,7 +156,7 @@ test.describe('Sketch tests', () => {
await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`sketch001 = startSketchOn('XZ') .toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %) |> startProfileAt([12.34, -12.34], %)
|> line([-12.34, 12.34], %) |> yLine(12.34, %)
`) `)
}).toPass({ timeout: 40_000, intervals: [1_000] }) }).toPass({ timeout: 40_000, intervals: [1_000] })
@ -645,7 +645,7 @@ test.describe('Sketch tests', () => {
await u.openDebugPanel() await u.openDebugPanel()
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
const { toSU, click00r } = getMovementUtils({ center, page }) const { toSU, toU, click00r } = getMovementUtils({ center, page })
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
@ -674,16 +674,15 @@ test.describe('Sketch tests', () => {
await click00r(50, 0) await click00r(50, 0)
await page.waitForTimeout(100) await page.waitForTimeout(100)
codeStr += ` |> lineTo(${toSU([50, 0])}, %)` codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 50) await click00r(0, 50)
codeStr += ` |> line(${toSU([0, 50])}, %)` codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
let clickCoords = await click00r(-50, 0) await click00r(-50, 0)
expect(clickCoords).not.toBeUndefined() codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
codeStr += ` |> lineTo(${toSU(clickCoords!)}, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker // exit the sketch, reset relative clicker
@ -712,15 +711,15 @@ test.describe('Sketch tests', () => {
// TODO: I couldn't use `toSU` here because of some rounding error causing // TODO: I couldn't use `toSU` here because of some rounding error causing
// it to be off by 0.01 // it to be off by 0.01
await click00r(30, 0) await click00r(30, 0)
codeStr += ` |> lineTo([4.07, 0], %)` codeStr += ` |> xLine(2.04, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30) await click00r(0, 30)
codeStr += ` |> line([0, -2.03], %)` codeStr += ` |> yLine(-2.03, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(-30, 0) await click00r(-30, 0)
codeStr += ` |> line([-2.04, 0], %)` codeStr += ` |> xLine(-2.04, %)`
await expect(u.codeLocator).toHaveText(codeStr) await expect(u.codeLocator).toHaveText(codeStr)
await click00r(undefined, undefined) await click00r(undefined, undefined)
@ -744,8 +743,8 @@ test.describe('Sketch tests', () => {
const code = `sketch001 = startSketchOn('-XZ') const code = `sketch001 = startSketchOn('-XZ')
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %) |> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|> line([${roundOff(scale * 139.19)}, 0], %) |> xLine(${roundOff(scale * 139.19)}, %)
|> line([0, -${roundOff(scale * 139.2)}], %) |> yLine(-${roundOff(scale * 139.2)}, %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)` |> close(%)`

View File

@ -462,7 +462,7 @@ test(
await page.waitForTimeout(100) await page.waitForTimeout(100)
code += ` code += `
|> line([7.25, 0], %)` |> xLine(7.25, %)`
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(code)
await page await page
@ -647,7 +647,7 @@ test.describe(
await page.waitForTimeout(100) await page.waitForTimeout(100)
code += ` code += `
|> line([7.25, 0], %)` |> xLine(7.25, %)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await page
@ -752,7 +752,7 @@ test.describe(
await page.waitForTimeout(100) await page.waitForTimeout(100)
code += ` code += `
|> line([184.3, 0], %)` |> xLine(184.3, %)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await page

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> xLine(${commonPoints.num1}, %)`)
// Expect the network to be up // Expect the network to be up
await expect(networkToggle).toContainText('Connected') await expect(networkToggle).toContainText('Connected')
@ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => {
await expect.poll(u.normalisedEditorCode) await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ') .toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %) |> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %) |> xLine(12.34, %)
|> line([-12.34, 12.34], %) |> line([-12.34, 12.34], %)
`) `)
@ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => {
await expect.poll(u.normalisedEditorCode) await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ') .toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %) |> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %) |> xLine(12.34, %)
|> line([-12.34, 12.34], %) |> line([-12.34, 12.34], %)
|> lineTo([0, -12.34], %) |> xLine(-12.34, %)
`) `)

View File

@ -305,7 +305,7 @@ export const getMovementUtils = (opts: any) => {
return [last.x, last.y] return [last.x, last.y]
} }
return { toSU, click00r } return { toSU, toU, click00r }
} }
async function waitForAuthAndLsp(page: Page) { async function waitForAuthAndLsp(page: Page) {

View File

@ -32,10 +32,17 @@ test.describe('Testing selections', () => {
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await u.openDebugPanel()
const xAxisClick = () => const yAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100)) test.step('Click on Y axis', async () => {
await page.mouse.move(600, 200, { steps: 5 })
await page.mouse.click(600, 200)
await page.waitForTimeout(100)
})
const xAxisClickAfterExitingSketch = () => const xAxisClickAfterExitingSketch = () =>
page.mouse.click(639, 278).then(() => page.waitForTimeout(100)) test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
await page.mouse.click(639, 278)
await page.waitForTimeout(100)
})
const emptySpaceHover = () => const emptySpaceHover = () =>
test.step('Hover over empty space', async () => { test.step('Hover over empty space', async () => {
await page.mouse.move(700, 143, { steps: 5 }) await page.mouse.move(700, 143, { steps: 5 })
@ -80,23 +87,23 @@ test.describe('Testing selections', () => {
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> xLine(${commonPoints.num1}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> xLine(${commonPoints.num1}, %)
|> line([0, ${commonPoints.num1 + 0.01}], %)`) |> yLine(${commonPoints.num1 + 0.01}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ') .toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> xLine(${commonPoints.num1}, %)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> yLine(${commonPoints.num1 + 0.01}, %)
|> lineTo([0, ${commonPoints.num3}], %)`) |> xLine(${commonPoints.num2 * -1}, %)`)
// deselect line tool // deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -121,53 +128,58 @@ test.describe('Testing selections', () => {
// now check clicking works including axis // now check clicking works including axis
// click a segment hold shift and click an axis, see that a relevant constraint is enabled // click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { const constrainButton = page.getByRole('button', {
name: 'Length: open menu', name: 'Length: open menu',
}) })
const absYButton = page.getByRole('button', { name: 'Absolute Y' }) const absXButton = page.getByRole('button', { name: 'Absolute X' })
await constrainButton.click()
await expect(absYButton).toBeDisabled() await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
await page.waitForTimeout(100) await topHorzSegmentClick()
await xAxisClick() await page.keyboard.down('Shift')
await page.keyboard.up('Shift') await constrainButton.click()
await constrainButton.click() await expect(absXButton).toBeDisabled()
await absYButton.and(page.locator(':not([disabled])')).waitFor() await page.waitForTimeout(100)
await expect(absYButton).not.toBeDisabled() await yAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absXButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing
await emptySpaceClick() await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.keyboard.up('Shift') await test.step(`Same selection but click the axis first`, async () => {
await constrainButton.click() await yAxisClick()
await expect(absYButton).not.toBeDisabled() await constrainButton.click()
await expect(absXButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing // clear selection by clicking on nothing
await emptySpaceClick() await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis // check the same selection again by putting cursor in code first then selecting axis
await page await test.step(`Same selection but code selection then axis`, async () => {
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`) await page
.click() .getByText(` |> xLine(${commonPoints.num2 * -1}, %)`)
await page.keyboard.down('Shift') .click()
await constrainButton.click() await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled() await constrainButton.click()
await page.waitForTimeout(100) await expect(absXButton).toBeDisabled()
await xAxisClick() await page.waitForTimeout(100)
await page.keyboard.up('Shift') await yAxisClick()
await constrainButton.click() await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled() await constrainButton.click()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing // clear selection by clicking on nothing
await emptySpaceClick() await emptySpaceClick()
@ -182,9 +194,7 @@ test.describe('Testing selections', () => {
process.platform === 'linux' ? 'Control' : 'Meta' process.platform === 'linux' ? 'Control' : 'Meta'
) )
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await expect(page.locator('.cm-cursor')).toHaveCount(2) await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500) await page.waitForTimeout(500)
@ -928,6 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
// test fillet button with the body in the scene // test fillet button with the body in the scene
const codeToAdd = `${await u.codeLocator.allInnerTexts()} const codeToAdd = `${await u.codeLocator.allInnerTexts()}
extrude001 = extrude(10, sketch001)` extrude001 = extrude(10, sketch001)`
await u.codeLocator.clear()
await u.codeLocator.fill(codeToAdd) await u.codeLocator.fill(codeToAdd)
await selectSegment() await selectSegment()
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled() await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()

View File

@ -17,6 +17,7 @@ import {
Vector3, Vector3,
} from 'three' } from 'three'
import { import {
ANGLE_SNAP_THRESHOLD_DEGREES,
ARROWHEAD, ARROWHEAD,
AXIS_GROUP, AXIS_GROUP,
DRAFT_POINT, DRAFT_POINT,
@ -95,6 +96,7 @@ import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes' import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { radToDeg } from 'three/src/math/MathUtils'
type DraftSegment = 'line' | 'tangentialArcTo' type DraftSegment = 'line' | 'tangentialArcTo'
@ -798,11 +800,24 @@ export class SceneEntities {
(sceneObject) => sceneObject.object.name === X_AXIS (sceneObject) => sceneObject.object.name === X_AXIS
) )
const lastSegment = sketch.paths.slice(-1)[0] const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
const snappedPoint = { const snappedPoint = {
x: intersectsYAxis ? 0 : intersection2d.x, x: intersectsYAxis ? 0 : intersection2d.x,
y: intersectsXAxis ? 0 : intersection2d.y, y: intersectsXAxis ? 0 : intersection2d.y,
} }
// Get the angle between the previous segment (or sketch start)'s end and this one's
const angle = Math.atan2(
snappedPoint.y - lastSegment.to[1],
snappedPoint.x - lastSegment.to[0]
)
const isHorizontal =
radToDeg(Math.abs(angle)) < ANGLE_SNAP_THRESHOLD_DEGREES ||
Math.abs(radToDeg(Math.abs(angle) - Math.PI)) <
ANGLE_SNAP_THRESHOLD_DEGREES
const isVertical =
Math.abs(radToDeg(Math.abs(angle) - Math.PI / 2)) <
ANGLE_SNAP_THRESHOLD_DEGREES
let resolvedFunctionName: ToolTip = 'line' let resolvedFunctionName: ToolTip = 'line'
@ -810,6 +825,12 @@ export class SceneEntities {
// case-based logic for different segment types // case-based logic for different segment types
if (lastSegment.type === 'TangentialArcTo') { if (lastSegment.type === 'TangentialArcTo') {
resolvedFunctionName = 'tangentialArcTo' resolvedFunctionName = 'tangentialArcTo'
} else if (isHorizontal) {
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
resolvedFunctionName = 'xLine'
} else if (isVertical) {
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
resolvedFunctionName = 'yLine'
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) { } else if (snappedPoint.x === 0 || snappedPoint.y === 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'

View File

@ -50,6 +50,8 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const X_AXIS = 'xAxis' export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis' export const Y_AXIS = 'yAxis'
/** If a segment angle is less than this many degrees off a meanginful angle it'll snap to it */
export const ANGLE_SNAP_THRESHOLD_DEGREES = 3
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */ /** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
export const DRAFT_POINT_GROUP = 'draft-point-group' export const DRAFT_POINT_GROUP = 'draft-point-group'
/** the THREEjs representation of a "snapped" point that is not yet placed */ /** the THREEjs representation of a "snapped" point that is not yet placed */