Compare commits
25 Commits
reuse-exam
...
kurt-xstat
Author | SHA1 | Date | |
---|---|---|---|
2f36b6b7cb | |||
a37ccec39e | |||
6287c943dd | |||
6ef052cb7f | |||
7f10ea7371 | |||
00dcd3ac78 | |||
1baa3819db | |||
448db9d48c | |||
23b484d365 | |||
672cd652a8 | |||
cad4e18530 | |||
484717a354 | |||
c989340bcf | |||
5dc1adacae | |||
f2ea91b1ba | |||
b8ceea179c | |||
149130d264 | |||
b9e544d410 | |||
a4e39ce2e9 | |||
18cf6113d5 | |||
5dea7fd042 | |||
809ea86bfa | |||
c23b046c5e | |||
456d4912ca | |||
522352ec75 |
@ -1470,9 +1470,13 @@ test('Sketch on face', async ({ page, context }) => {
|
|||||||
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1200 })
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.updateCamPosition([452, -152, 1166])
|
||||||
|
await u.closeDebugPanel()
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
const pointToDragFirst = [691, 237]
|
const pointToDragFirst = [787, 565]
|
||||||
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
|
||||||
await page.mouse.down()
|
await page.mouse.down()
|
||||||
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
|
||||||
@ -1486,7 +1490,9 @@ test('Sketch on face', async ({ page, context }) => {
|
|||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> startProfileAt([1.03, 1.03], %)
|
|> startProfileAt([1.03, 1.03], %)
|
||||||
|> line([2.81, -0.33], %)
|
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
||||||
|
process?.env?.CI ? 0.24 : 0.2
|
||||||
|
}], %)
|
||||||
|> line([-4.44, -2.13], %)
|
|> line([-4.44, -2.13], %)
|
||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
|
|
||||||
@ -1509,7 +1515,9 @@ test('Sketch on face', async ({ page, context }) => {
|
|||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|
||||||
|> startProfileAt([1.03, 1.03], %)
|
|> startProfileAt([1.03, 1.03], %)
|
||||||
|> line([2.81, -0.33], %)
|
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
|
||||||
|
process?.env?.CI ? 0.24 : 0.2
|
||||||
|
}], %)
|
||||||
|> line([-4.44, -2.13], %)
|
|> line([-4.44, -2.13], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5 + 7, %)`)
|
|> extrude(5 + 7, %)`)
|
||||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
BIN
public/clientSideSceneAssets/extra-segment-texture.png
Normal file
After Width: | Height: | Size: 327 B |
@ -28,12 +28,15 @@ export function createGridHelper({
|
|||||||
gridHelper.rotation.x = Math.PI / 2
|
gridHelper.rotation.x = Math.PI / 2
|
||||||
return gridHelper
|
return gridHelper
|
||||||
}
|
}
|
||||||
|
const fudgeFactor = 72.66985970437086
|
||||||
|
|
||||||
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
|
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
|
||||||
0.55 / cam.zoom
|
(0.55 * fudgeFactor) / cam.zoom / window.innerHeight
|
||||||
|
|
||||||
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
|
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
|
||||||
(group.position.distanceTo(cam.position) * cam.fov) / 4000
|
(group.position.distanceTo(cam.position) * cam.fov * fudgeFactor) /
|
||||||
|
4000 /
|
||||||
|
window.innerHeight
|
||||||
|
|
||||||
export function isQuaternionVertical(q: Quaternion) {
|
export function isQuaternionVertical(q: Quaternion) {
|
||||||
const v = new Vector3(0, 0, 1).applyQuaternion(q)
|
const v = new Vector3(0, 0, 1).applyQuaternion(q)
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
OrthographicCamera,
|
OrthographicCamera,
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
PlaneGeometry,
|
PlaneGeometry,
|
||||||
|
Points,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
Scene,
|
Scene,
|
||||||
Shape,
|
Shape,
|
||||||
@ -87,14 +88,17 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
|
|||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
|
export const EXTRA_SEGMENT_HANDLE = 'extraSegmentHandle'
|
||||||
|
export const EXTRA_SEGMENT_OFFSET_PX = 8
|
||||||
|
export const PROFILE_START = 'profile-start'
|
||||||
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 TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
|
||||||
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
|
||||||
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
|
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
|
||||||
'tangential-arc-to-segment-body-dashed'
|
'tangential-arc-to-segment-body-dashed'
|
||||||
export const PROFILE_START = 'profile-start'
|
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
||||||
|
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
||||||
|
export const MIN_SEGMENT_LENGTH = 60 // in pixels
|
||||||
|
|
||||||
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
||||||
// That mostly mean sketch elements.
|
// That mostly mean sketch elements.
|
||||||
@ -111,8 +115,12 @@ export class SceneEntities {
|
|||||||
this.engineCommandManager = engineCommandManager
|
this.engineCommandManager = engineCommandManager
|
||||||
this.scene = sceneInfra?.scene
|
this.scene = sceneInfra?.scene
|
||||||
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
|
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
|
||||||
|
window.addEventListener('resize', this.onWindowResize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWindowResize = () => {
|
||||||
|
this.onCamChange()
|
||||||
|
}
|
||||||
onCamChange = () => {
|
onCamChange = () => {
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
@ -282,7 +290,6 @@ export class SceneEntities {
|
|||||||
sketchGroup: SketchGroup
|
sketchGroup: SketchGroup
|
||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}> {
|
}> {
|
||||||
sceneInfra.resetMouseListeners()
|
|
||||||
this.createIntersectionPlane()
|
this.createIntersectionPlane()
|
||||||
|
|
||||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||||
@ -295,7 +302,7 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
const sketchGroup = sketchGroupFromPathToNode({
|
const sketchGroup = sketchGroupFromPathToNode({
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
ast: kclManager.ast,
|
ast: maybeModdedAst,
|
||||||
programMemory,
|
programMemory,
|
||||||
})
|
})
|
||||||
if (!Array.isArray(sketchGroup?.value))
|
if (!Array.isArray(sketchGroup?.value))
|
||||||
@ -383,6 +390,7 @@ export class SceneEntities {
|
|||||||
pathToNode: segPathToNode,
|
pathToNode: segPathToNode,
|
||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
|
texture: sceneInfra.extraSegmentTexture,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
seg = straightSegment({
|
seg = straightSegment({
|
||||||
@ -393,6 +401,7 @@ export class SceneEntities {
|
|||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
callExpName,
|
callExpName,
|
||||||
|
texture: sceneInfra.extraSegmentTexture,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
seg.layers.set(SKETCH_LAYER)
|
seg.layers.set(SKETCH_LAYER)
|
||||||
@ -435,6 +444,7 @@ export class SceneEntities {
|
|||||||
) => {
|
) => {
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
|
sceneInfra.resetMouseListeners()
|
||||||
await this.setupSketch({
|
await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
forward,
|
forward,
|
||||||
@ -442,7 +452,12 @@ export class SceneEntities {
|
|||||||
position: origin,
|
position: origin,
|
||||||
maybeModdedAst: kclManager.ast,
|
maybeModdedAst: kclManager.ast,
|
||||||
})
|
})
|
||||||
this.setupSketchIdleCallbacks(sketchPathToNode)
|
this.setupSketchIdleCallbacks({
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
position: origin,
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setUpDraftSegment = async (
|
setUpDraftSegment = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
@ -467,19 +482,20 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||||
|
|
||||||
let modifiedAst = addNewSketchLn({
|
const mod = addNewSketchLn({
|
||||||
node: kclManager.ast,
|
node: _ast,
|
||||||
programMemory: kclManager.programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
to: [lastSeg.to[0], lastSeg.to[1]],
|
to: [lastSeg.to[0], lastSeg.to[1]],
|
||||||
from: [lastSeg.to[0], lastSeg.to[1]],
|
from: [lastSeg.to[0], lastSeg.to[1]],
|
||||||
fnName: segmentName,
|
fnName: segmentName,
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
}).modifiedAst
|
})
|
||||||
modifiedAst = parse(recast(modifiedAst))
|
const modifiedAst = parse(recast(mod.modifiedAst))
|
||||||
|
|
||||||
const draftExpressionsIndices = { start: index, end: index }
|
const draftExpressionsIndices = { start: index, end: index }
|
||||||
|
|
||||||
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
|
||||||
|
sceneInfra.resetMouseListeners()
|
||||||
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
const { truncatedAst, programMemoryOverride, sketchGroup } =
|
||||||
await this.setupSketch({
|
await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
@ -549,10 +565,101 @@ export class SceneEntities {
|
|||||||
...mouseEnterLeaveCallbacks(),
|
...mouseEnterLeaveCallbacks(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupSketchIdleCallbacks = (pathToNode: PathToNode) => {
|
setupSketchIdleCallbacks = ({
|
||||||
|
pathToNode,
|
||||||
|
up,
|
||||||
|
forward,
|
||||||
|
position,
|
||||||
|
}: {
|
||||||
|
pathToNode: PathToNode
|
||||||
|
forward: [number, number, number]
|
||||||
|
up: [number, number, number]
|
||||||
|
position?: [number, number, number]
|
||||||
|
}) => {
|
||||||
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
|
onDragEnd: async () => {
|
||||||
|
if (addingNewSegmentStatus !== 'nothing') {
|
||||||
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
|
this.setupSketch({
|
||||||
|
sketchPathToNode: pathToNode,
|
||||||
|
maybeModdedAst: kclManager.ast,
|
||||||
|
up,
|
||||||
|
forward,
|
||||||
|
position,
|
||||||
|
})
|
||||||
|
// setting up the callbacks again resets value in closures
|
||||||
|
this.setupSketchIdleCallbacks({
|
||||||
|
pathToNode,
|
||||||
|
up,
|
||||||
|
forward,
|
||||||
|
position,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrag: async ({
|
||||||
|
selected,
|
||||||
|
intersectionPoint,
|
||||||
|
mouseEvent,
|
||||||
|
intersects,
|
||||||
|
}) => {
|
||||||
if (mouseEvent.which !== 1) return
|
if (mouseEvent.which !== 1) return
|
||||||
|
|
||||||
|
const group = getParentGroup(selected, [EXTRA_SEGMENT_HANDLE])
|
||||||
|
if (group?.name === EXTRA_SEGMENT_HANDLE) {
|
||||||
|
const segGroup = getParentGroup(selected)
|
||||||
|
const pathToNode: PathToNode = segGroup?.userData?.pathToNode
|
||||||
|
const pathToNodeIndex = pathToNode.findIndex(
|
||||||
|
(x) => x[1] === 'PipeExpression'
|
||||||
|
)
|
||||||
|
|
||||||
|
const sketchGroup = sketchGroupFromPathToNode({
|
||||||
|
pathToNode,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
})
|
||||||
|
|
||||||
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
||||||
|
if (addingNewSegmentStatus === 'nothing') {
|
||||||
|
const prevSegment = sketchGroup.value[pipeIndex - 2]
|
||||||
|
const mod = addNewSketchLn({
|
||||||
|
node: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
|
||||||
|
from: [prevSegment.from[0], prevSegment.from[1]],
|
||||||
|
// TODO assuming it's always a straight segments being added
|
||||||
|
// as this is easiest, and we'll need to add "tabbing" behavior
|
||||||
|
// to support other segment types
|
||||||
|
fnName: 'line',
|
||||||
|
pathToNode: pathToNode,
|
||||||
|
spliceBetween: true,
|
||||||
|
})
|
||||||
|
addingNewSegmentStatus = 'pending'
|
||||||
|
await kclManager.executeAstMock(mod.modifiedAst, {
|
||||||
|
updates: 'code',
|
||||||
|
})
|
||||||
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
|
this.setupSketch({
|
||||||
|
sketchPathToNode: pathToNode,
|
||||||
|
maybeModdedAst: kclManager.ast,
|
||||||
|
up,
|
||||||
|
forward,
|
||||||
|
position,
|
||||||
|
})
|
||||||
|
addingNewSegmentStatus = 'added'
|
||||||
|
} else if (addingNewSegmentStatus === 'added') {
|
||||||
|
const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex)
|
||||||
|
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
|
||||||
|
this.onDragSegment({
|
||||||
|
sketchPathToNode: pathToNodeForNewSegment,
|
||||||
|
object: selected,
|
||||||
|
intersection2d: intersectionPoint.twoD,
|
||||||
|
intersects,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
object: selected,
|
object: selected,
|
||||||
intersection2d: intersectionPoint.twoD,
|
intersection2d: intersectionPoint.twoD,
|
||||||
@ -755,8 +862,7 @@ export class SceneEntities {
|
|||||||
group.userData.to = to
|
group.userData.to = to
|
||||||
group.userData.prevSegment = prevSegment
|
group.userData.prevSegment = prevSegment
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
|
||||||
|
|
||||||
const previousPoint =
|
const previousPoint =
|
||||||
prevSegment?.type === 'TangentialArcTo'
|
prevSegment?.type === 'TangentialArcTo'
|
||||||
@ -774,13 +880,40 @@ export class SceneEntities {
|
|||||||
obtuse: true,
|
obtuse: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const arrowheadAngle =
|
const pxLength = arcInfo.arcLength / scale
|
||||||
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
|
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
|
||||||
arrowGroup.quaternion.setFromUnitVectors(
|
|
||||||
new Vector3(0, 1, 0),
|
if (arrowGroup) {
|
||||||
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
)
|
|
||||||
arrowGroup.scale.set(scale, scale, scale)
|
const arrowheadAngle =
|
||||||
|
arcInfo.endAngle + (Math.PI / 2) * (arcInfo.ccw ? 1 : -1)
|
||||||
|
arrowGroup.quaternion.setFromUnitVectors(
|
||||||
|
new Vector3(0, 1, 0),
|
||||||
|
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
||||||
|
)
|
||||||
|
arrowGroup.scale.set(scale, scale, scale)
|
||||||
|
arrowGroup.visible = !shouldHide
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraSegmentGroup) {
|
||||||
|
const circumferenceInPx = (2 * Math.PI * arcInfo.radius) / scale
|
||||||
|
const extraSegmentAngleDelta =
|
||||||
|
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
||||||
|
const extraSegmentAngle =
|
||||||
|
arcInfo.startAngle + (arcInfo.ccw ? 1 : -1) * extraSegmentAngleDelta
|
||||||
|
const extraSegmentOffset = new Vector2(
|
||||||
|
Math.cos(extraSegmentAngle) * arcInfo.radius,
|
||||||
|
Math.sin(extraSegmentAngle) * arcInfo.radius
|
||||||
|
)
|
||||||
|
extraSegmentGroup.position.set(
|
||||||
|
arcInfo.center[0] + extraSegmentOffset.x,
|
||||||
|
arcInfo.center[1] + extraSegmentOffset.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
extraSegmentGroup.scale.set(scale, scale, scale)
|
||||||
|
extraSegmentGroup.visible = !shouldHide
|
||||||
|
}
|
||||||
|
|
||||||
const tangentialArcToSegmentBody = group.children.find(
|
const tangentialArcToSegmentBody = group.children.find(
|
||||||
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
|
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
|
||||||
@ -827,10 +960,17 @@ export class SceneEntities {
|
|||||||
group.userData.from = from
|
group.userData.from = from
|
||||||
group.userData.to = to
|
group.userData.to = to
|
||||||
const shape = new Shape()
|
const shape = new Shape()
|
||||||
shape.moveTo(0, -0.08 * scale)
|
shape.moveTo(0, -1.2 * scale) // The width of the line in px (2.4px in this case)
|
||||||
shape.lineTo(0, 0.08 * scale) // The width of the line
|
shape.lineTo(0, 1.2 * scale)
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
|
|
||||||
|
const length = Math.sqrt(
|
||||||
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
const pxLength = length / scale
|
||||||
|
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
|
||||||
|
|
||||||
if (arrowGroup) {
|
if (arrowGroup) {
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
|
||||||
@ -842,6 +982,21 @@ export class SceneEntities {
|
|||||||
.normalize()
|
.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 = !shouldHide
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
|
if (extraSegmentGroup) {
|
||||||
|
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
||||||
|
extraSegmentGroup.position.set(
|
||||||
|
from[0] + offsetFromBase.x,
|
||||||
|
from[1] + offsetFromBase.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
extraSegmentGroup.scale.set(scale, scale, scale)
|
||||||
|
extraSegmentGroup.visible = !shouldHide
|
||||||
}
|
}
|
||||||
|
|
||||||
const straightSegmentBody = group.children.find(
|
const straightSegmentBody = group.children.find(
|
||||||
@ -1160,7 +1315,7 @@ function colorSegment(object: any, color: number) {
|
|||||||
])
|
])
|
||||||
if (straightSegmentBody) {
|
if (straightSegmentBody) {
|
||||||
straightSegmentBody.traverse((child) => {
|
straightSegmentBody.traverse((child) => {
|
||||||
if (child instanceof Mesh) {
|
if (child instanceof Mesh && !child.userData.ignoreColorChange) {
|
||||||
child.material.color.set(color)
|
child.material.color.set(color)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1264,7 +1419,7 @@ function massageFormats(a: any): Vector3 {
|
|||||||
|
|
||||||
function mouseEnterLeaveCallbacks() {
|
function mouseEnterLeaveCallbacks() {
|
||||||
return {
|
return {
|
||||||
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => {
|
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
||||||
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
||||||
const obj = selected as Mesh
|
const obj = selected as Mesh
|
||||||
const mat = obj.material as MeshBasicMaterial
|
const mat = obj.material as MeshBasicMaterial
|
||||||
@ -1286,6 +1441,14 @@ function mouseEnterLeaveCallbacks() {
|
|||||||
sceneInfra.highlightCallback([node.start, node.end])
|
sceneInfra.highlightCallback([node.start, node.end])
|
||||||
const yellow = 0xffff00
|
const yellow = 0xffff00
|
||||||
colorSegment(selected, yellow)
|
colorSegment(selected, yellow)
|
||||||
|
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
|
if (extraSegmentGroup) {
|
||||||
|
extraSegmentGroup.traverse((child) => {
|
||||||
|
if (child instanceof Points || child instanceof Mesh) {
|
||||||
|
child.material.opacity = dragSelected ? 0 : 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sceneInfra.highlightCallback([0, 0])
|
sceneInfra.highlightCallback([0, 0])
|
||||||
@ -1302,6 +1465,14 @@ function mouseEnterLeaveCallbacks() {
|
|||||||
selected,
|
selected,
|
||||||
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
|
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
|
||||||
)
|
)
|
||||||
|
const extraSegmentGroup = parent?.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
|
if (extraSegmentGroup) {
|
||||||
|
extraSegmentGroup.traverse((child) => {
|
||||||
|
if (child instanceof Points || child instanceof Mesh) {
|
||||||
|
child.material.opacity = 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
||||||
const obj = selected as Mesh
|
const obj = selected as Mesh
|
||||||
const mat = obj.material as MeshBasicMaterial
|
const mat = obj.material as MeshBasicMaterial
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
|
TextureLoader,
|
||||||
|
Texture,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
@ -54,6 +56,7 @@ export const ARROWHEAD = 'arrowhead'
|
|||||||
|
|
||||||
export interface OnMouseEnterLeaveArgs {
|
export interface OnMouseEnterLeaveArgs {
|
||||||
selected: Object3D<Object3DEventMap>
|
selected: Object3D<Object3DEventMap>
|
||||||
|
dragSelected?: Object3D<Object3DEventMap>
|
||||||
mouseEvent: MouseEvent
|
mouseEvent: MouseEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,18 +101,25 @@ export class SceneInfra {
|
|||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
_baseUnit: BaseUnit = 'mm'
|
_baseUnit: BaseUnit = 'mm'
|
||||||
_baseUnitMultiplier = 1
|
_baseUnitMultiplier = 1
|
||||||
|
extraSegmentTexture: Texture
|
||||||
|
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
|
onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
||||||
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
|
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {}
|
||||||
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
||||||
setCallbacks = (callbacks: {
|
setCallbacks = (callbacks: {
|
||||||
|
onDragStart?: (arg: OnDragCallbackArgs) => void
|
||||||
|
onDragEnd?: (arg: OnDragCallbackArgs) => void
|
||||||
onDrag?: (arg: OnDragCallbackArgs) => void
|
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||||
onMove?: (arg: OnMoveCallbackArgs) => void
|
onMove?: (arg: OnMoveCallbackArgs) => void
|
||||||
onClick?: (arg: OnClickCallbackArgs) => void
|
onClick?: (arg: OnClickCallbackArgs) => void
|
||||||
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
||||||
}) => {
|
}) => {
|
||||||
|
this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback
|
||||||
|
this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback
|
||||||
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
||||||
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
||||||
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
||||||
@ -128,6 +138,8 @@ export class SceneInfra {
|
|||||||
}
|
}
|
||||||
resetMouseListeners = () => {
|
resetMouseListeners = () => {
|
||||||
this.setCallbacks({
|
this.setCallbacks({
|
||||||
|
onDragStart: () => {},
|
||||||
|
onDragEnd: () => {},
|
||||||
onDrag: () => {},
|
onDrag: () => {},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
@ -212,6 +224,13 @@ export class SceneInfra {
|
|||||||
const light = new AmbientLight(0x505050) // soft white light
|
const light = new AmbientLight(0x505050) // soft white light
|
||||||
this.scene.add(light)
|
this.scene.add(light)
|
||||||
|
|
||||||
|
const textureLoader = new TextureLoader()
|
||||||
|
this.extraSegmentTexture = textureLoader.load(
|
||||||
|
'/clientSideSceneAssets/extra-segment-texture.png'
|
||||||
|
)
|
||||||
|
this.extraSegmentTexture.anisotropy =
|
||||||
|
this.renderer?.capabilities?.getMaxAnisotropy?.()
|
||||||
|
|
||||||
SceneInfra.instance = this
|
SceneInfra.instance = this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +379,7 @@ export class SceneInfra {
|
|||||||
this.hoveredObject = firstIntersectObject
|
this.hoveredObject = firstIntersectObject
|
||||||
this.onMouseEnter({
|
this.onMouseEnter({
|
||||||
selected: this.hoveredObject,
|
selected: this.hoveredObject,
|
||||||
|
dragSelected: this.selected?.object,
|
||||||
mouseEvent: mouseEvent,
|
mouseEvent: mouseEvent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -367,6 +387,7 @@ export class SceneInfra {
|
|||||||
if (this.hoveredObject) {
|
if (this.hoveredObject) {
|
||||||
this.onMouseLeave({
|
this.onMouseLeave({
|
||||||
selected: this.hoveredObject,
|
selected: this.hoveredObject,
|
||||||
|
dragSelected: this.selected?.object,
|
||||||
mouseEvent: mouseEvent,
|
mouseEvent: mouseEvent,
|
||||||
})
|
})
|
||||||
this.hoveredObject = null
|
this.hoveredObject = null
|
||||||
@ -455,8 +476,16 @@ export class SceneInfra {
|
|||||||
|
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
if (this.selected.hasBeenDragged) {
|
if (this.selected.hasBeenDragged) {
|
||||||
// this is where we could fire a onDragEnd event
|
// TODO do the types properly here
|
||||||
// console.log('onDragEnd', this.selected)
|
this.onDragEndCallback({
|
||||||
|
intersectionPoint: {
|
||||||
|
twoD: planeIntersectPoint?.twoD as any,
|
||||||
|
threeD: planeIntersectPoint?.threeD as any,
|
||||||
|
},
|
||||||
|
intersects,
|
||||||
|
mouseEvent,
|
||||||
|
selected: this.selected as any,
|
||||||
|
})
|
||||||
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
||||||
// fire onClick event as there was no drags
|
// fire onClick event as there was no drags
|
||||||
this.onClickCallback({
|
this.onClickCallback({
|
||||||
|
@ -12,14 +12,20 @@ import {
|
|||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
NormalBufferAttributes,
|
NormalBufferAttributes,
|
||||||
|
Points,
|
||||||
|
PointsMaterial,
|
||||||
Shape,
|
Shape,
|
||||||
SphereGeometry,
|
SphereGeometry,
|
||||||
|
Texture,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||||
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
|
EXTRA_SEGMENT_HANDLE,
|
||||||
|
EXTRA_SEGMENT_OFFSET_PX,
|
||||||
|
MIN_SEGMENT_LENGTH,
|
||||||
PROFILE_START,
|
PROFILE_START,
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
STRAIGHT_SEGMENT_BODY,
|
STRAIGHT_SEGMENT_BODY,
|
||||||
@ -44,7 +50,7 @@ export function profileStart({
|
|||||||
}) {
|
}) {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
const geometry = new BoxGeometry(0.8, 0.8, 0.8)
|
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
|
||||||
const body = new MeshBasicMaterial({ color: 0xffffff })
|
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
|
|
||||||
@ -71,6 +77,7 @@ export function straightSegment({
|
|||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
callExpName,
|
callExpName,
|
||||||
|
texture,
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
to: Coords2d
|
to: Coords2d
|
||||||
@ -79,12 +86,13 @@ export function straightSegment({
|
|||||||
isDraftSegment?: boolean
|
isDraftSegment?: boolean
|
||||||
scale?: number
|
scale?: number
|
||||||
callExpName: string
|
callExpName: string
|
||||||
|
texture: Texture
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
const shape = new Shape()
|
const shape = new Shape()
|
||||||
shape.moveTo(0, -0.08 * scale)
|
shape.moveTo(0, -1.2 * scale)
|
||||||
shape.lineTo(0, 0.08 * scale) // The width of the line
|
shape.lineTo(0, 1.2 * scale)
|
||||||
|
|
||||||
let geometry
|
let geometry
|
||||||
if (isDraftSegment) {
|
if (isDraftSegment) {
|
||||||
@ -122,24 +130,44 @@ export function straightSegment({
|
|||||||
}
|
}
|
||||||
group.name = STRAIGHT_SEGMENT
|
group.name = STRAIGHT_SEGMENT
|
||||||
|
|
||||||
|
const length = Math.sqrt(
|
||||||
|
Math.pow(to[0] - from[0], 2) + Math.pow(to[1] - from[1], 2)
|
||||||
|
)
|
||||||
const arrowGroup = createArrowhead(scale)
|
const arrowGroup = createArrowhead(scale)
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
const dir = new Vector3()
|
const dir = new Vector3()
|
||||||
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
.subVectors(new Vector3(to[0], to[1], 0), new Vector3(from[0], from[1], 0))
|
||||||
.normalize()
|
.normalize()
|
||||||
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
|
const pxLength = length / scale
|
||||||
|
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
|
||||||
|
arrowGroup.visible = !shouldHide
|
||||||
|
|
||||||
group.add(mesh)
|
group.add(mesh)
|
||||||
if (callExpName !== 'close') group.add(arrowGroup)
|
if (callExpName !== 'close') group.add(arrowGroup)
|
||||||
|
|
||||||
|
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
||||||
|
const offsetFromBase = new Vector2(to[0] - from[0], to[1] - from[1])
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(EXTRA_SEGMENT_OFFSET_PX * scale)
|
||||||
|
extraSegmentGroup.position.set(
|
||||||
|
from[0] + offsetFromBase.x,
|
||||||
|
from[1] + offsetFromBase.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
extraSegmentGroup.visible = !shouldHide
|
||||||
|
group.add(extraSegmentGroup)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
function createArrowhead(scale = 1): Group {
|
function createArrowhead(scale = 1): Group {
|
||||||
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
const arrowMaterial = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
const arrowheadMesh = new Mesh(new ConeGeometry(0.31, 1.5, 12), arrowMaterial)
|
// specify the size of the geometry in pixels (i.e. cone height = 20px, cone radius = 4.5px)
|
||||||
arrowheadMesh.position.set(0, -0.6, 0)
|
// we'll scale the group to the correct size later to match these sizes in screen space
|
||||||
const sphereMesh = new Mesh(new SphereGeometry(0.27, 12, 12), arrowMaterial)
|
const arrowheadMesh = new Mesh(new ConeGeometry(4.5, 20, 12), arrowMaterial)
|
||||||
|
arrowheadMesh.position.set(0, -9, 0)
|
||||||
|
const sphereMesh = new Mesh(new SphereGeometry(4, 12, 12), arrowMaterial)
|
||||||
|
|
||||||
const arrowGroup = new Group()
|
const arrowGroup = new Group()
|
||||||
arrowGroup.userData.type = ARROWHEAD
|
arrowGroup.userData.type = ARROWHEAD
|
||||||
@ -150,6 +178,36 @@ function createArrowhead(scale = 1): Group {
|
|||||||
return arrowGroup
|
return arrowGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createExtraSegmentHandle(scale: number, texture: Texture): Group {
|
||||||
|
const particleMaterial = new PointsMaterial({
|
||||||
|
size: 12, // in pixels
|
||||||
|
map: texture,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0,
|
||||||
|
depthTest: false,
|
||||||
|
})
|
||||||
|
const mat = new MeshBasicMaterial({
|
||||||
|
transparent: true,
|
||||||
|
color: 0xffffff,
|
||||||
|
opacity: 0,
|
||||||
|
})
|
||||||
|
const particleGeometry = new BufferGeometry().setFromPoints([
|
||||||
|
new Vector3(0, 0, 0),
|
||||||
|
])
|
||||||
|
const sphereMesh = new Mesh(new SphereGeometry(6, 12, 12), mat) // sphere radius in pixels
|
||||||
|
const particle = new Points(particleGeometry, particleMaterial)
|
||||||
|
particle.userData.ignoreColorChange = true
|
||||||
|
particle.userData.type = EXTRA_SEGMENT_HANDLE
|
||||||
|
|
||||||
|
const extraSegmentGroup = new Group()
|
||||||
|
extraSegmentGroup.userData.type = EXTRA_SEGMENT_HANDLE
|
||||||
|
extraSegmentGroup.name = EXTRA_SEGMENT_HANDLE
|
||||||
|
extraSegmentGroup.add(sphereMesh)
|
||||||
|
extraSegmentGroup.add(particle)
|
||||||
|
extraSegmentGroup.scale.set(scale, scale, scale)
|
||||||
|
return extraSegmentGroup
|
||||||
|
}
|
||||||
|
|
||||||
export function tangentialArcToSegment({
|
export function tangentialArcToSegment({
|
||||||
prevSegment,
|
prevSegment,
|
||||||
from,
|
from,
|
||||||
@ -158,6 +216,7 @@ export function tangentialArcToSegment({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
|
texture,
|
||||||
}: {
|
}: {
|
||||||
prevSegment: SketchGroup['value'][number]
|
prevSegment: SketchGroup['value'][number]
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
@ -166,6 +225,7 @@ export function tangentialArcToSegment({
|
|||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
isDraftSegment?: boolean
|
isDraftSegment?: boolean
|
||||||
scale?: number
|
scale?: number
|
||||||
|
texture: Texture
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
@ -178,12 +238,13 @@ export function tangentialArcToSegment({
|
|||||||
)
|
)
|
||||||
: prevSegment.from
|
: prevSegment.from
|
||||||
|
|
||||||
const { center, radius, startAngle, endAngle, ccw } = getTangentialArcToInfo({
|
const { center, radius, startAngle, endAngle, ccw, arcLength } =
|
||||||
arcStartPoint: from,
|
getTangentialArcToInfo({
|
||||||
arcEndPoint: to,
|
arcStartPoint: from,
|
||||||
tanPreviousPoint: previousPoint,
|
arcEndPoint: to,
|
||||||
obtuse: true,
|
tanPreviousPoint: previousPoint,
|
||||||
})
|
obtuse: true,
|
||||||
|
})
|
||||||
|
|
||||||
const geometry = createArcGeometry({
|
const geometry = createArcGeometry({
|
||||||
center,
|
center,
|
||||||
@ -219,8 +280,28 @@ export function tangentialArcToSegment({
|
|||||||
new Vector3(0, 1, 0),
|
new Vector3(0, 1, 0),
|
||||||
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
|
||||||
)
|
)
|
||||||
|
const pxLength = arcLength / scale
|
||||||
|
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
|
||||||
|
arrowGroup.visible = !shouldHide
|
||||||
|
|
||||||
group.add(mesh, arrowGroup)
|
const extraSegmentGroup = createExtraSegmentHandle(scale, texture)
|
||||||
|
const circumferenceInPx = (2 * Math.PI * radius) / scale
|
||||||
|
const extraSegmentAngleDelta =
|
||||||
|
(EXTRA_SEGMENT_OFFSET_PX / circumferenceInPx) * Math.PI * 2
|
||||||
|
const extraSegmentAngle = startAngle + (ccw ? 1 : -1) * extraSegmentAngleDelta
|
||||||
|
const extraSegmentOffset = new Vector2(
|
||||||
|
Math.cos(extraSegmentAngle) * radius,
|
||||||
|
Math.sin(extraSegmentAngle) * radius
|
||||||
|
)
|
||||||
|
extraSegmentGroup.position.set(
|
||||||
|
center[0] + extraSegmentOffset.x,
|
||||||
|
center[1] + extraSegmentOffset.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
extraSegmentGroup.visible = !shouldHide
|
||||||
|
|
||||||
|
group.add(mesh, arrowGroup, extraSegmentGroup)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
@ -242,8 +323,8 @@ export function createArcGeometry({
|
|||||||
isDashed?: boolean
|
isDashed?: boolean
|
||||||
scale?: number
|
scale?: number
|
||||||
}): BufferGeometry {
|
}): BufferGeometry {
|
||||||
const dashSize = 1.2 * scale
|
const dashSizePx = 18 * scale
|
||||||
const gapSize = 1.2 * scale
|
const gapSizePx = 18 * scale
|
||||||
const arcStart = new EllipseCurve(
|
const arcStart = new EllipseCurve(
|
||||||
center[0],
|
center[0],
|
||||||
center[1],
|
center[1],
|
||||||
@ -265,8 +346,8 @@ export function createArcGeometry({
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
const shape = new Shape()
|
const shape = new Shape()
|
||||||
shape.moveTo(0, -0.08 * scale)
|
shape.moveTo(0, -1.2 * scale)
|
||||||
shape.lineTo(0, 0.08 * scale) // The width of the line
|
shape.lineTo(0, 1.2 * scale) // The width of the line
|
||||||
|
|
||||||
if (!isDashed) {
|
if (!isDashed) {
|
||||||
const points = arcStart.getPoints(50)
|
const points = arcStart.getPoints(50)
|
||||||
@ -281,7 +362,7 @@ export function createArcGeometry({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const length = arcStart.getLength()
|
const length = arcStart.getLength()
|
||||||
const totalDashes = length / (dashSize + gapSize) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
|
const totalDashes = length / (dashSizePx + gapSizePx) // rounding makes the dashes jittery since the new dash is suddenly appears instead of growing into place
|
||||||
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
|
const dashesAtEachEnd = Math.min(100, totalDashes / 2) // Assuming we want 50 dashes total, 25 at each end
|
||||||
|
|
||||||
const dashGeometries = []
|
const dashGeometries = []
|
||||||
@ -289,8 +370,8 @@ export function createArcGeometry({
|
|||||||
// Function to create a dash at a specific t value (0 to 1 along the curve)
|
// Function to create a dash at a specific t value (0 to 1 along the curve)
|
||||||
const createDashAt = (t: number, curve: EllipseCurve) => {
|
const createDashAt = (t: number, curve: EllipseCurve) => {
|
||||||
const startVec = curve.getPoint(t)
|
const startVec = curve.getPoint(t)
|
||||||
const endVec = curve.getPoint(Math.min(0.5, t + dashSize / length))
|
const endVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length))
|
||||||
const midVec = curve.getPoint(Math.min(0.5, t + dashSize / length / 2))
|
const midVec = curve.getPoint(Math.min(0.5, t + dashSizePx / length / 2))
|
||||||
const dashCurve = new CurvePath<Vector3>()
|
const dashCurve = new CurvePath<Vector3>()
|
||||||
dashCurve.add(
|
dashCurve.add(
|
||||||
new CatmullRomCurve3([
|
new CatmullRomCurve3([
|
||||||
@ -314,7 +395,8 @@ export function createArcGeometry({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fill in the remaining arc
|
// fill in the remaining arc
|
||||||
const remainingArcLength = length - dashesAtEachEnd * 2 * (dashSize + gapSize)
|
const remainingArcLength =
|
||||||
|
length - dashesAtEachEnd * 2 * (dashSizePx + gapSizePx)
|
||||||
if (remainingArcLength > 0) {
|
if (remainingArcLength > 0) {
|
||||||
const remainingArcStartT = dashesAtEachEnd / totalDashes
|
const remainingArcStartT = dashesAtEachEnd / totalDashes
|
||||||
const remainingArcEndT = 1 - remainingArcStartT
|
const remainingArcEndT = 1 - remainingArcStartT
|
||||||
@ -359,8 +441,8 @@ export function dashedStraight(
|
|||||||
shape: Shape,
|
shape: Shape,
|
||||||
scale = 1
|
scale = 1
|
||||||
): BufferGeometry<NormalBufferAttributes> {
|
): BufferGeometry<NormalBufferAttributes> {
|
||||||
const dashSize = 1.2 * scale
|
const dashSize = 18 * scale
|
||||||
const gapSize = 1.2 * scale // todo: gabSize is not respected
|
const gapSize = 18 * scale // todo: gabSize is not respected
|
||||||
const dashLine = new LineCurve3(
|
const dashLine = new LineCurve3(
|
||||||
new Vector3(from[0], from[1], 0),
|
new Vector3(from[0], from[1], 0),
|
||||||
new Vector3(to[0], to[1], 0)
|
new Vector3(to[0], to[1], 0)
|
||||||
|
@ -162,6 +162,7 @@ export const line: SketchLineHelper = {
|
|||||||
replaceExisting,
|
replaceExisting,
|
||||||
referencedSegment,
|
referencedSegment,
|
||||||
createCallback,
|
createCallback,
|
||||||
|
spliceBetween,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
|
||||||
@ -178,6 +179,30 @@ export const line: SketchLineHelper = {
|
|||||||
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
|
|
||||||
|
if (spliceBetween && !createCallback && pipe.type === 'PipeExpression') {
|
||||||
|
const callExp = createCallExpression('line', [
|
||||||
|
createArrayExpression([newXVal, newYVal]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
const pathToNodeIndex = pathToNode.findIndex(
|
||||||
|
(x) => x[1] === 'PipeExpression'
|
||||||
|
)
|
||||||
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0]
|
||||||
|
if (typeof pipeIndex === 'undefined' || typeof pipeIndex === 'string') {
|
||||||
|
throw new Error('pipeIndex is undefined')
|
||||||
|
// return
|
||||||
|
}
|
||||||
|
pipe.body = [
|
||||||
|
...pipe.body.slice(0, pipeIndex),
|
||||||
|
callExp,
|
||||||
|
...pipe.body.slice(pipeIndex),
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
const { callExp, valueUsedInTransform } = createCallback(
|
const { callExp, valueUsedInTransform } = createCallback(
|
||||||
@ -1023,15 +1048,6 @@ export function changeSketchArguments(
|
|||||||
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLineFnCallArgs {
|
|
||||||
node: Program
|
|
||||||
programMemory: ProgramMemory
|
|
||||||
to: [number, number]
|
|
||||||
from: [number, number]
|
|
||||||
fnName: ToolTip
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compareVec2Epsilon(
|
export function compareVec2Epsilon(
|
||||||
vec1: [number, number],
|
vec1: [number, number],
|
||||||
vec2: [number, number],
|
vec2: [number, number],
|
||||||
@ -1056,6 +1072,16 @@ export function compareVec2Epsilon2(
|
|||||||
return distance < compareEpsilon
|
return distance < compareEpsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateLineFnCallArgs {
|
||||||
|
node: Program
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
to: [number, number]
|
||||||
|
from: [number, number]
|
||||||
|
fnName: ToolTip
|
||||||
|
pathToNode: PathToNode
|
||||||
|
spliceBetween?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
node: _node,
|
node: _node,
|
||||||
programMemory: previousProgramMemory,
|
programMemory: previousProgramMemory,
|
||||||
@ -1063,6 +1089,7 @@ export function addNewSketchLn({
|
|||||||
fnName,
|
fnName,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
from,
|
from,
|
||||||
|
spliceBetween = false,
|
||||||
}: CreateLineFnCallArgs): {
|
}: CreateLineFnCallArgs): {
|
||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
@ -1083,6 +1110,7 @@ export function addNewSketchLn({
|
|||||||
to,
|
to,
|
||||||
from,
|
from,
|
||||||
replaceExisting: false,
|
replaceExisting: false,
|
||||||
|
spliceBetween,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ interface addCall extends ModifyAstBase {
|
|||||||
referencedSegment?: Path
|
referencedSegment?: Path
|
||||||
replaceExisting?: boolean
|
replaceExisting?: boolean
|
||||||
createCallback?: TransformCallback // TODO: #29 probably should not be optional
|
createCallback?: TransformCallback // TODO: #29 probably should not be optional
|
||||||
|
/// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression
|
||||||
|
spliceBetween?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface updateArgs extends ModifyAstBase {
|
interface updateArgs extends ModifyAstBase {
|
||||||
|
@ -251,6 +251,7 @@ export function getTangentialArcToInfo({
|
|||||||
startAngle: number
|
startAngle: number
|
||||||
endAngle: number
|
endAngle: number
|
||||||
ccw: boolean
|
ccw: boolean
|
||||||
|
arcLength: number
|
||||||
} {
|
} {
|
||||||
const result = get_tangential_arc_to_info(
|
const result = get_tangential_arc_to_info(
|
||||||
arcStartPoint[0],
|
arcStartPoint[0],
|
||||||
@ -268,6 +269,7 @@ export function getTangentialArcToInfo({
|
|||||||
startAngle: result.start_angle,
|
startAngle: result.start_angle,
|
||||||
endAngle: result.end_angle,
|
endAngle: result.end_angle,
|
||||||
ccw: result.ccw > 0,
|
ccw: result.ccw > 0,
|
||||||
|
arcLength: result.arc_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ export type MoveDesc = { line: number; snippet: string }
|
|||||||
|
|
||||||
export const modelingMachine = createMachine(
|
export const modelingMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaFlmZWZSmZRaKyGQzKYQaDYIGSmDSzdKyDSGcQ0KrAk5nTxtDpdAi3e5PWCvdgfKiGSLMVhjbh-BCCaw0WaQrZqdGLAr6RAZGSSWzCZSiIEKNFi5TY5q4y4E658dgPACuGC+NNi4wSQgqM3hNFEwNEGlEYthENELJkCjWplMFpsGlRUo8rVlNz4LAe7DV0Vpvy1jKtCkkVuZMjsDpNsOEwjKUIkcxNqWhomd5za3jJH2IZEomEzb0+9BGfs1oESEOtkmM0K0ahMplkZqOZUrYrRlgUJrTMoL5Pekj7HwAklcegEgl0BuFi99S-SA4ZoWUdWsTSLlsozZlzNkZPrG0a5F2e66hwPz6OCcgSK8+lAALZgO7+ABuj045BImB9PzL-CMeFW2qTQhU0ZMzVRKR0RoVJ6nyJQNFPC5z0HLN3ivbobzvIJH2fAJ3lQB5sAAL24dhv1-ed4nLQDoUkVRrX1CF8ihGQzXECFymNLJIU4-U8mQjN0LQwtMOIbhYEVEg8H8QjiLIu5v38CBsCk3MwCojUF1ohArCNCwNDSDFENMI51h5OEzGUCwKgtbJRDMtEhNE-tXJHMciEk6TZPfL1sC-TAVLUiiKE02d1TpGiAL05ZgztK0RVBYxNHYyzw1qcprCODQY2EZYjRc1DL087yHhk3B-AAQQAIW8fwAA0tKihkrC7JExEMGxOPhNJYXhAy7ShRY4NMWoGlcU5pTPESSoJLzcCk8rZNq+qAE1mv9XS2u2aw7GUOR9mFA7YU5YN8rstlHEdIrZvQ8SFqWir-DIKAuk2-8K2RaQlBA+QxWEUx+otXV1zyg1AbG27C3cjDSsWnzKq6fB2CLalfW06KvvEGYVANDE0TSfZ+sOc7gVRQHHSUKFobcubukexH-CYR4WdwVTyCVTASCeVT1LCj6dJiqwrVmW0eoyI5VC3dKxURWMqjG-LHVy1NJpxGaYfpiSEeWyr5NI8jv0wPQXpwKAhgijGWsXZZ+S7TQu3SI0aBWM0kpDO1QPysQVgmpoXRQu6xPhp7fI-ALjdN79sAtwWsbo8wcitQGkrMEUzQO7YTTGzFqiqWpaY+WGHrK57YFwEgmH8dhUEa+PWs4qRlnERYFDG6ozH6vcQzkeZ5n1RxwyLi97tDpmK6rmu642q2-yFr60QsIVOJjHHjBhdKupmI0h8cpc5hyEeS-HvX-DAABHJVlORqBUYb23G3KbfAayPIJG5Iol3RJETFg-VjqmGPtrRmZ8mA82NtQOe1FG75AsOGFY+xGwU03l-RsMwTCcUOLaTIag1YB3TLDE+80y6yQeGAB8qB3z+HIKQu4sAH7bWsMGQ41gDRWlRCacQZolBlEsMCfcOROQHWAWPAkAAlMAggwB8BCEqe4jDhZ1CkGiVQ+kdQGlhCCGyjk14misGZbIoiQ5yivtgauAAZPAYBp6oB-NAzGrV246JKKCW0CZ1CwiUNnTIe05CJghMY-s4lrhmOrqFGAdxsDKR5uQaeijEj7WDK3A0WD-rKObJYSQYFVDpMdKCY+VUADuMkCJEUNkpIKfNQqUH8HgAAZqgAgEBuBgHaLgV8qBXiSBgOwQQBtFIUUwIIBpqAEmIGMoifJGQxpmCVtw9KqJzBdlDFg2wENCklI4HJcpgzlLVI0nU3AjSCCPAeERSQTBubsEaQ8B8PS-D9N2UbYZozxlWX1NWLqqI0TGitGNfqrd+TZFdrBOorFDCbNKW+COgVgr81qaM5prT2mdO6b0wQflPzfhGccsZDiba6SJvyNEih7BQkMIKfqVopCOHfnYUo+UoXbKxZHKpIVDlIrORcq5JAblEXuRi1lgVcWNPecSyQix5D7zEJoalRlqzHVSK3ECShmUBFWo1I5JyWm4DaXgNFbSMUkAAEawEEHwUV+L0bzwTlZSE2SDqwXDLsCyRR4QHWrBkVEoJZAHULuraaQcYbFOhZqhq2qmncoeJc65tzBWPNNeay1byCVbRikTGYQj1B1FMPqLQ-UzI2TqNaCQG9oRAMDYHYSIatkarqv4NakbkV6tRV0o1iazWCD0Fa8VWDpAaMcvCHGxpTquyzYrC0XD83quqg2ptXKHjnJjby-ldyHl9KTd23tabPoTNXvAle+V5jpEhKdSEiJXbaJdaSmQs7XpdGbbq-VHT20bsEA+qRqabUwIDJmlIEJwWOTtIYYGBoBTogtOaE0UJhD3vwI+xdy7Y18vje+z9O6f2OL-Zxc62Q2pWDUGZT+EyRSAjRPIOC+oASztvqjJ9KKDVvoxXR94mGSzYaJZxbYyczAoP3PkEm7VXaMpUEuKwtHnx33eM26NKG10Jr6ax9jc5OMZv7cYEw+i1i5TSu60E-J5jMIRGIGMBpZ0sweGzDmXMebwpqTYpFz623oseZZ6zAVbMPEEAcsKKnIrpsSXbAUXDrAHTinIfq1RtjrzmMKdxch-ZTWrUQ0N2z3PPhs9zXmHKwqyaXTyuNAr30ZfZp57LPncuUH89bQLEzgtexROFtYkX0p5WkLGOLwH35JY1sGtyVi9W2MwOOPoU44jiv3EnQjRxMhHDyAs91Y1qxewxMwxid6q2ENQoNmxtc7GEjCTXV6+FolBVifE3dC8JkxkRPsMwsF1CKxlkUJWSIcaOCyCCY+u3huSGHLgDgBBJsqElSk2bCI5BA0smsMoaR24PZxoDNIP3rF-YB0DykWHCUZvyPyC0VoL2EeBNGQGswVgSHWcCWC+DkvbZEr9-bmBJC4AFd+Ubk4QgTau3a6wmVZDqClsyA0L3ECghmMuVWcsoKQq272BnaOmeSAAHJ1wAAqoDwOwWABAqoQAgIECiXpmaa7uOK+YiI80GkckZRYoJYTzCTmYV+mQc0KFR0NpXqv-Aa61zr0gYV7HY7q3pTQu5ZBAoxFoE0brEC8TFtg0oVoVjHwx+wYHPPWr8NmF2LIKhqNR7NGkKQcwhTLFBcsJ0cvNZuTT8DqkHGccVmYpKvNea5iyFgl1IvZlslOzmECbqkpq-9eLgAFRO1EmJDw4m1w5-0bnwe91wgOuYYESg7TolsPCIvqJJUXv3JYY03YR81rchP-Ap3p+z6aaEpU5j-CM9QHY83jpf6KEchaE6lkEQ2RjOGOoLIV2UvY+JUdmOudSL0c8AAeVwBbRfUNUkCqm8DH0EDAJaUEEgPYBgMtiX2uz0hKGDDyFqCckdDqDUFhCTAFGwTUFylbhKDg1P1hn8FZ38HqRIEoB6HGxUjAHYK5gCF5T1XeSZC4iOARCRy0ChBsFEAd2WH3zSByEhiFBsBHhYLrnYM4N8AnAX0GHeSFC+R0wkDmWyEOFhAdAgxKDsCUAGhXhcjIGwAfD5VaGnmZm5j1XgJczaXsMcPuEEFrkEA0PCjwLtQqGAldxBSWEyFkOSSsBKFSHbmhHUDsMBx8OcNrlcLIG6C0LGy510Mzz-Xyi+TkCh0YmhEoK0GrFWDsFghWBqGSIcKcPwBcJkRCmcNJELA8KY26W8L5SkX8PaP7HeSzngX9SqH2EUL00QFwwYihFbhBCe1yndxOFZwwHgCiD6ygEbxD0EEWDKFyEUHz3UC0EWyEEWElXwyFFrBxkOhcnxDAC2OX0EFBCIKhFmU0BNFyi8TFHKE5Hu1iMsErQIXl0LAePwPmJz0o3zzyELxhwYkaz-ljBMhuiYOKjEXuNUybyMAyGbjGlylsHeMBljz0kpTu1RGMj9jz16yDTP2LjSzKQUheXs05TxVBLtSlmDEdAxEUFjBqFSEBWSGdhgkd3yFlyBJr1pLrRhX8jhV80RRZIxJD3MhJVUEsGUVX1A3SjkBZFSFRDlnmFCNnXDUjVZKcSfg5AMRUGqIxELU4h2GGjUVyWOBRJEjpLnXWmNIVOXzEPxw4Vwz3DHWFF7jsjECqHzngzekc3lICy9Pbm2BWFgiESqDyEbGBjUElXhwqFUDLzqEkxRhk1GRNMXHbhZGxOtzFEbH7hJlB0BkpUcFeONBT2dNrWhVKyyzs1lMjMaULO2i7D-xBAtHAhsD9n6jJWyVrP3h1G+ybIG0V2f0wG7IzXsGXkIKNFDLWEoNUBDFRDmDrOHTUA9z2znP+0B3YAXMSX2D4XGi7BsHUHXJh2SEhBWHyQezmEBLp2BJnM9yPNZzuW-DPImRsCzTXj1EWFkEmIQHjyHMpST2tFkAPL+29193oX-LhGNGbl4TBBMDSDdksjLxDHbDRCOGULFFTxPJQuMEcgYmNCMmxI3270skrFbHMkA07BPzFNHwHAv0iU4Gv2nhQuPHlmPUtNz1tG3DyBDDWAWEow3Fpw2KIXQIgKN2wPQlgPIptB+N3lSVC2hyKCNBsgH1sGP2GnM2nI+DULYI4PROjPwKZAyDBwBHkFyloJkMsmovKAPC70yFTmsHqNSKaPSMEKstqy9JWAFCFHbztB5NdlkJshMHUCFF3k5EYPYraB6M4H8rrhaKkjaPQnIuW19mFCyAjE4lsCixZAHmLOZGtCtBcBcCAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwAgBRXHYwACdAgGs-cgALCN4Yji540ETBGWVhSStlU2UtK0NDVY1CxBlTDUkcnOtDcRoqzcaQdxb8ds7uvoHh2DH2SapDSLMVhzbi8JbWGjnfaWURqG4KTJHBAZGSSWzCZSiDYKa445QPJ6eNodLq9PjsQYAVww0yisziYKEFTWJxook2og0ohxSL2sMkMgUygUplMsJsGg0hgJzSJr1JPT4LEG7FpQNi80ZCGW6gF1RRdgl3KRwmEZVWEg0mIR+S5Mo8rUk3h+k2IZEomGd4ym9BmwIZCSMhiF60ykoRCLMsl5wbKe3Uexuwe59uebS9vwmTpdEwAkm8CMgSGNAmAoABbMD9fwANyGnHIJEwaui-s1gYQVlMceqmgxmlSBX0RilUhuNFS9XySg0qblGcm2e9+dJRZLQQrVYCE1Qg2wAC9uOwmy36e3FkZbZJVEK2Xt8qsZLzxHtylysvsX2y8nPHQus-+K7dEQ3CwBSJB4P4O57oe-RNv4EDYGB7pgKebagh2VichYGhpLcM7duoSJpFsFgVLC2SiN21y-i8-5LpmQHEKB4GQXWKrYI2mAIUhx4UKhvp0uhCz8JewponkChYtsxiaE+w4IDI+RSHkUp9qaKycrR6Y5gxkxMSBuBgYMEG4P4ACCABC3j+AAGmhGoYRenaCqI5ziGIhg2C+JxpMRYpnGKqwIpOpi1A0riPLKf66YBBaGcZpkWdZ-gAJoOSCImJFYUkCrh+xyIY7LsvJRTwgokjCHIFTQo4kraXpAE5gZLEmZBZBQF0GUBs51w5NISi9vIOLCKYxGwiy3LyKa7KjWFDX0XFpIJaxZldPg7A+oCraOVlRglGsKjsrc1xpEVxHKMGlWbFKo3hiowgLbFzXxa1SVMEMH24Ih5CUpgJDDIhyH8d156iS5grnKKPkZMGqjKMROJnGaVRhVVkoaGIT3eo1LVGatUG7geR5Npgej+E22BQLgoNOeDGyolJmhSeknI0Mo4i8tJApiup+TsiU2OZrjr3421ZnsQ2pPk5T1O03tLmje5gqjdJZhYryyhyOUIqjUK1RVLUQuLktwFvZBsC4CQTD+OwqB2fLWrXNcAocxGYXVGYxHZKiSnssoAdso4SnG01y6i4lFtWzbdtpY7mEvlImQc4YpoecYhwKYaaycsHVGp1aOShyLy3m2ZYAAI6UvB61QJt8e9ScKleaaYVsnIHm8rUkI5CYE5spi9yRYSMU46bzFi+9AOk9QgnqplTuyGsqQrC+VHpAXvKmB5FilJdoqZGoojF+PK3i-4gxgOWqB1v45Bl+wsAN-T1gVZd1glZoRU6ApwYvhY+xbDZBKGoLWJ8XqkgAEpgEEGAPgIRKQDGftlOoUhriqCwsydkSItjKHKGjOoOITgZ3AeHBUVdsA2wADJ4DALbVAqBmxzx2gvTCIo8ESBUDYMU6QiIKSUG5Ca1g-ZWixNKYe0U6LPTIe8ChMcOpbmwPBAG5B6HIOOPsCq4gpIp0xIoVBMZLDXVvMNdIhdi7mQAO4QW3ETWCx5uJAz4pQfweAABmqACAQG4GAdouAayoDGJIGA7BBDQWJnBTAgh3GoHUYpG4ZxJTbFuLUUUadiJSnMFJQUZ1NhyFWBY6xHBCYwRJo43iKFXG4A8QQIYgxdySCYP9dgHjBjlmCX4MJdiynROqbE5hZ46aJDwm5PYkpExckFGFYi2jUTZHZhOOoD5xFNAdFInGVibG1nrJxeCTjKkxK8T4vxASgkhMEJLXZUSYlxLOqia4ih7CrEMOiYigopCODyHka4L4qqFK2ZcriPFgYuMOXUhpTSSAtN3O085gKmy9I8bcnykgETyHzmITQbzcLrEHqkbRvYlD-OKVZGytkqk1O8bgXxeBTm+POSQAARrAQQfBEX9O2oMhWuTroyAnH7Qh-ktbrAyFKbYsgtZGwkWsnSGyikBFJXZClnjwWDEac01psLOlMpZWym5AzhJajOmsHIBK6imDZFofy+xpDckxF+TGNhiUKpSqlZVRzqUnMCfS7VzLBB6HZcim1dR0VUSbsILkSIbirChiKWE3IbhaWlWmRqkhNkktde61V6qoWao6aEnV-rA0Gt2ka35FgHweRWBzMVUb9hnHZrgv2DyZDOopvgLo7qqU0v8d6-NggOpdGLZyw1HZjUpETF80UGss4VDwdvNklgip2tNG2wddCwWDHqWqyF0K2n9vXcOv0pax0vgqlkKUgorBqG7EOIohU1iTqyHIduBTk3zl0umgItdNpduObSvt5yf0TCPUJE9zkzpuUuGYW6uFBwXVyuzUotRLpmlbe+0ewsv3+GA1mrdEKNUwv7cB0D88ergx5cYEw3JNGY1KscbYqIA6v1OGIGa+IMPrKw-K-wH1BhfR+n9AGwLnEbr6R6ntdL+18YE5xITgxBD7P4qRlh5HhkrFRLzWQVolLCjkIjbW6crSYmnR3NtMmqyCf+oDCp-E8PbpzXurVoSLPfTk9ZxTtnKAqa5UajTEklDWC1isaoohiKmkOmaYzVExRmc47K4WNDqX0MYb0ORtsFH9CUdxFRaiS2sIg6aM4RUzATnUKjBGCk0buQ8o4LIWxi5JboXbRhkhcy4A4AQW5aQ8GDSxAiU4cgxoKWFGUNIIpSseVGmkRrtCUuYDax19gXWATHoKxR-IqJYRXpknyzYJolZWlQbYdkFq2SzeSy1hbAA5e2AAFVAeBH4EHMhACAgRjwql449-otyA6BQHlRXCCJthIgDuYHIYU26KEGhd5rDCbv3Z+890g-EmEjvA-TTQ5gTDjb5acJmCgTRGJsPvUogoObF3a51uJxgbVaHkCYVQeRbiZyKFYEo5xVha3hqkGSVOlsrYx+t7Kd5UUWotUdvllg2eXm7JVFmVoNjeQ46slN9EAAqmXODKMGKou2aXKSUP8E1+bf3JTuUyFJMU7N6NJCtJVKqaRGfsytI9eLqbKTfXtshFU-4ADyuAJNeqCeZbwGvBBe+8YIX37AA803y2p-a2jyjKWopKOoagkTckhFsF5ahMbaIOqHfwuB7ZuJIJQHwwRQgITABXv6ARIXUricsV8wZThTa0AcMUYOVioo5zkOaGInUe7INgcsULWj0N4-9alweANBPH5PgYgg7aCAr5QOJNV1gIkUPMleiIRuQx8iAqZEb1ANWX1P-AM+4G8Wn98b0C-e1L46yvmB6+n+ZjiVrNyfP8gqgioh87cz1rxVhtEthytMYFAXBIoy8MB4AogR58A1sk9tQEQyhch9EbxmZOZf5xIld1Ah88l0M1c5QSQwA0CwYlhtgKpZowptEuQ+wkQExyh4QSsOdLBTBQ5qChlEAoDzgpIsgVB25Wdxo3I88LVuR8dMgaIPdFoIEqCwMRcRwTAeYI1ptNBKgidf4XlitL1o0DoTAIpyDMNFxsNwl7E9kvMxMPE+CFY4YKpJQUlHArBTRUgZlkhWZxxwd8gVkooZVU1sN4VykQU7DUAHCnZ1B7lVBLBUEtZuw3l+RUg1IvIA4ao21FVyUYkoi2Ft4LAlAuwVA7AJxht71t4yguRgoMFmdqgsjM1ciVD0CO8ttBQchgwfYo12Y3I-YsEqgDY10O0Ii8jeo41rxxxTUqg8ht5xo1BUVxs51k5nc21cMmiyMaCjARQe5qNrQXkVAA4LoVBKpuxDZVhTgBZzNPpLN3NhMlNQU+lRisd2QTjMQC9sgtgShwscRKoXlrdl1RpVdAj1ddJTcrsnjhl7ALBwopIbB1BhRs8fjt4gDUhbhNJTDgSP0cYwSEdFsOAISNEsRoSShYSBiESRtkh9ga0jtJch4zCuNFwcTWsy82kmwCTFIbATU05WRIwlJicqipDychRZA4d5tJBbt-AHsnskCNj+COSJA9QXwdhccVB8CigMRfYcRExgwR8cQBd8TmjNjOw2RzBthOQIQi9uxZdOxYx1hgwtSkwmZi4td8BFFdd9dIjDS5S5BNBHdV4tZhDRReR25XYhRhRGcsQ5Bi4o8fdPs48cxA92TNFzAJBc4BYE0Ths8HcldbAuRZodES8y9-BN9lDZSFZlgMhUUJU2NMYC8wsFIuQzh40ztjBW5VIr938b8oAZ9m9SzVMjTPw0QMRJcxQ0N2Y+88ETB1AMRc54R3d6S2hr9OBb9Y578wJH8cwkywpKp-ZGdDQXxbBEZIRA5tiIQhRBQ4CnAgA */
|
||||||
id: 'Modeling',
|
id: 'Modeling',
|
||||||
|
|
||||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
@ -166,12 +166,6 @@ export const modelingMachine = createMachine(
|
|||||||
states: {
|
states: {
|
||||||
idle: {
|
idle: {
|
||||||
on: {
|
on: {
|
||||||
'Set selection': {
|
|
||||||
target: 'idle',
|
|
||||||
internal: true,
|
|
||||||
actions: 'Set selection',
|
|
||||||
},
|
|
||||||
|
|
||||||
'Enter sketch': [
|
'Enter sketch': [
|
||||||
{
|
{
|
||||||
target: 'animating to existing sketch',
|
target: 'animating to existing sketch',
|
||||||
@ -202,12 +196,6 @@ export const modelingMachine = createMachine(
|
|||||||
states: {
|
states: {
|
||||||
SketchIdle: {
|
SketchIdle: {
|
||||||
on: {
|
on: {
|
||||||
'Set selection': {
|
|
||||||
target: 'SketchIdle',
|
|
||||||
internal: true,
|
|
||||||
actions: 'Set selection',
|
|
||||||
},
|
|
||||||
|
|
||||||
'Make segment vertical': {
|
'Make segment vertical': {
|
||||||
cond: 'Can make selection vertical',
|
cond: 'Can make selection vertical',
|
||||||
target: 'SketchIdle',
|
target: 'SketchIdle',
|
||||||
@ -411,12 +399,6 @@ export const modelingMachine = createMachine(
|
|||||||
exit: [],
|
exit: [],
|
||||||
|
|
||||||
on: {
|
on: {
|
||||||
'Set selection': {
|
|
||||||
target: 'Line tool',
|
|
||||||
description: `This is just here to stop one of the higher level "Set selections" firing when we are just trying to set the IDE code without triggering a full engine-execute`,
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
'Equip tangential arc to': {
|
'Equip tangential arc to': {
|
||||||
target: 'Tangential arc to',
|
target: 'Tangential arc to',
|
||||||
cond: 'is editing existing sketch',
|
cond: 'is editing existing sketch',
|
||||||
@ -435,14 +417,7 @@ export const modelingMachine = createMachine(
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
normal: {
|
normal: {},
|
||||||
on: {
|
|
||||||
'Set selection': {
|
|
||||||
target: 'normal',
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
'No Points': {
|
'No Points': {
|
||||||
entry: 'setup noPoints onClick listener',
|
entry: 'setup noPoints onClick listener',
|
||||||
@ -475,11 +450,6 @@ export const modelingMachine = createMachine(
|
|||||||
entry: 'set up draft arc',
|
entry: 'set up draft arc',
|
||||||
|
|
||||||
on: {
|
on: {
|
||||||
'Set selection': {
|
|
||||||
target: 'Tangential arc to',
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
'Equip Line tool': 'Line tool',
|
'Equip Line tool': 'Line tool',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -519,11 +489,6 @@ export const modelingMachine = createMachine(
|
|||||||
target: 'animating to plane',
|
target: 'animating to plane',
|
||||||
actions: ['reset sketch metadata'],
|
actions: ['reset sketch metadata'],
|
||||||
},
|
},
|
||||||
|
|
||||||
'Set selection': {
|
|
||||||
target: 'Sketch no face',
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -537,13 +502,6 @@ export const modelingMachine = createMachine(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
on: {
|
|
||||||
'Set selection': {
|
|
||||||
target: 'animating to plane',
|
|
||||||
internal: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
entry: 'clientToEngine cam sync direction',
|
entry: 'clientToEngine cam sync direction',
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -836,6 +794,7 @@ export const modelingMachine = createMachine(
|
|||||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||||
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||||
}
|
}
|
||||||
|
sceneInfra.resetMouseListeners()
|
||||||
await sceneEntitiesManager.setupSketch({
|
await sceneEntitiesManager.setupSketch({
|
||||||
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
sketchPathToNode: sketchDetails?.sketchPathToNode || [],
|
||||||
forward: sketchDetails.zAxis,
|
forward: sketchDetails.zAxis,
|
||||||
@ -843,9 +802,13 @@ export const modelingMachine = createMachine(
|
|||||||
position: sketchDetails.origin,
|
position: sketchDetails.origin,
|
||||||
maybeModdedAst: kclManager.ast,
|
maybeModdedAst: kclManager.ast,
|
||||||
})
|
})
|
||||||
sceneEntitiesManager.setupSketchIdleCallbacks(
|
sceneInfra.resetMouseListeners()
|
||||||
sketchDetails?.sketchPathToNode || []
|
sceneEntitiesManager.setupSketchIdleCallbacks({
|
||||||
)
|
pathToNode: sketchDetails?.sketchPathToNode || [],
|
||||||
|
forward: sketchDetails.zAxis,
|
||||||
|
up: sketchDetails.yAxis,
|
||||||
|
position: sketchDetails.origin,
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
},
|
},
|
||||||
'animate after sketch': () => {
|
'animate after sketch': () => {
|
||||||
|
@ -576,6 +576,8 @@ pub struct TangentialArcInfoOutput {
|
|||||||
pub end_angle: f64,
|
pub end_angle: f64,
|
||||||
/// If the arc is counter-clockwise.
|
/// If the arc is counter-clockwise.
|
||||||
pub ccw: i32,
|
pub ccw: i32,
|
||||||
|
/// The length of the arc.
|
||||||
|
pub arc_length: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// tanPreviousPoint and arcStartPoint make up a straight segment leading into the arc (of which the arc should be tangential). The arc should start at arcStartPoint and end at, arcEndPoint
|
// tanPreviousPoint and arcStartPoint make up a straight segment leading into the arc (of which the arc should be tangential). The arc should start at arcStartPoint and end at, arcEndPoint
|
||||||
@ -626,6 +628,17 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
|
|||||||
let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]);
|
let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]);
|
||||||
let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
|
let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]);
|
||||||
|
|
||||||
|
let arc_mid_angle = (arc_mid_point[1] - center[1]).atan2(arc_mid_point[0] - center[0]);
|
||||||
|
let start_to_mid_arc_length = radius
|
||||||
|
* delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle))
|
||||||
|
.radians()
|
||||||
|
.abs();
|
||||||
|
let mid_to_end_arc_length = radius
|
||||||
|
* delta(Angle::from_radians(arc_mid_angle), Angle::from_radians(end_angle))
|
||||||
|
.radians()
|
||||||
|
.abs();
|
||||||
|
let arc_length = start_to_mid_arc_length + mid_to_end_arc_length;
|
||||||
|
|
||||||
TangentialArcInfoOutput {
|
TangentialArcInfoOutput {
|
||||||
center,
|
center,
|
||||||
radius,
|
radius,
|
||||||
@ -633,6 +646,7 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
|
|||||||
start_angle,
|
start_angle,
|
||||||
end_angle,
|
end_angle,
|
||||||
ccw,
|
ccw,
|
||||||
|
arc_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,6 +772,58 @@ mod get_tangential_arc_to_info_tests {
|
|||||||
assert_relative_eq!(result.end_angle, -PI / 2.0);
|
assert_relative_eq!(result.end_angle, -PI / 2.0);
|
||||||
assert_eq!(result.ccw, 1);
|
assert_eq!(result.ccw, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arc_length_obtuse_cw() {
|
||||||
|
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||||
|
tan_previous_point: [-1.0, -1.0],
|
||||||
|
arc_start_point: [-1.0, 0.0],
|
||||||
|
arc_end_point: [0.0, -1.0],
|
||||||
|
obtuse: true,
|
||||||
|
});
|
||||||
|
let circumference = 2.0 * PI * result.radius;
|
||||||
|
let expected_length = circumference * 3.0 / 4.0; // 3 quarters of a circle circle
|
||||||
|
assert_relative_eq!(result.arc_length, expected_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arc_length_acute_cw() {
|
||||||
|
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||||
|
tan_previous_point: [-1.0, -1.0],
|
||||||
|
arc_start_point: [-1.0, 0.0],
|
||||||
|
arc_end_point: [0.0, 1.0],
|
||||||
|
obtuse: true,
|
||||||
|
});
|
||||||
|
let circumference = 2.0 * PI * result.radius;
|
||||||
|
let expected_length = circumference / 4.0; // 1 quarters of a circle circle
|
||||||
|
assert_relative_eq!(result.arc_length, expected_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arc_length_obtuse_ccw() {
|
||||||
|
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||||
|
tan_previous_point: [1.0, -1.0],
|
||||||
|
arc_start_point: [1.0, 0.0],
|
||||||
|
arc_end_point: [0.0, -1.0],
|
||||||
|
obtuse: true,
|
||||||
|
});
|
||||||
|
let circumference = 2.0 * PI * result.radius;
|
||||||
|
let expected_length = circumference * 3.0 / 4.0; // 1 quarters of a circle circle
|
||||||
|
assert_relative_eq!(result.arc_length, expected_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_arc_length_acute_ccw() {
|
||||||
|
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
|
||||||
|
tan_previous_point: [1.0, -1.0],
|
||||||
|
arc_start_point: [1.0, 0.0],
|
||||||
|
arc_end_point: [0.0, 1.0],
|
||||||
|
obtuse: true,
|
||||||
|
});
|
||||||
|
let circumference = 2.0 * PI * result.radius;
|
||||||
|
let expected_length = circumference / 4.0; // 1 quarters of a circle circle
|
||||||
|
assert_relative_eq!(result.arc_length, expected_length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tangent_point_from_previous_arc(
|
pub fn get_tangent_point_from_previous_arc(
|
||||||
|
@ -333,6 +333,8 @@ pub struct TangentialArcInfoOutputWasm {
|
|||||||
pub end_angle: f64,
|
pub end_angle: f64,
|
||||||
/// Flag to determine if the arc is counter clockwise.
|
/// Flag to determine if the arc is counter clockwise.
|
||||||
pub ccw: i32,
|
pub ccw: i32,
|
||||||
|
/// The length of the arc.
|
||||||
|
pub arc_length: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@ -362,6 +364,7 @@ pub fn get_tangential_arc_to_info(
|
|||||||
start_angle: result.start_angle,
|
start_angle: result.start_angle,
|
||||||
end_angle: result.end_angle,
|
end_angle: result.end_angle,
|
||||||
ccw: result.ccw,
|
ccw: result.ccw,
|
||||||
|
arc_length: result.arc_length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|