New segments can be added in the middle of a sketch (#1953)

* get branch up to where it was before

* setup dots properly

* only show extra handle on hover

* use partical texture for plus button

* fix regression

* fix deleted line

* fix sketch on face test

* caluclate segment length in screen-space/in-pixels

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

* side small segment handles on resize

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

* Make sure this works on setup and update of segments

* Add to tangential arcs

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 5dc1adacae.

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit b8ceea179c.

* try and fix sketch on face in CI

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

* more test fix

* convert scaling to be based on pixels

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

* trigger ci

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 6287c943dd.

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 1baa3819db.

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

* Update src/clientSideScene/segments.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

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

* reduce line thickness

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

* trigger CI

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

* trigger CI

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

* trigger CI

* try putting init script back in

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
This commit is contained in:
Kurt Hutten
2024-04-03 13:22:56 +11:00
committed by GitHub
parent 221f037eaa
commit b045a89854
22 changed files with 484 additions and 67 deletions

View File

@ -12,6 +12,7 @@ import {
OrthographicCamera,
PerspectiveCamera,
PlaneGeometry,
Points,
Quaternion,
Scene,
Shape,
@ -87,14 +88,18 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
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_BODY = 'straight-segment-body'
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 =
'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
export const SEGMENT_WIDTH_PX = 1.6
// This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements.
@ -111,8 +116,12 @@ export class SceneEntities {
this.engineCommandManager = engineCommandManager
this.scene = sceneInfra?.scene
sceneInfra?.camControls.subscribeToCamChange(this.onCamChange)
window.addEventListener('resize', this.onWindowResize)
}
onWindowResize = () => {
this.onCamChange()
}
onCamChange = () => {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -282,7 +291,6 @@ export class SceneEntities {
sketchGroup: SketchGroup
variableDeclarationName: string
}> {
sceneInfra.resetMouseListeners()
this.createIntersectionPlane()
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
@ -295,7 +303,7 @@ export class SceneEntities {
})
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
ast: kclManager.ast,
ast: maybeModdedAst,
programMemory,
})
if (!Array.isArray(sketchGroup?.value))
@ -383,6 +391,7 @@ export class SceneEntities {
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
})
} else {
seg = straightSegment({
@ -393,6 +402,7 @@ export class SceneEntities {
isDraftSegment,
scale: factor,
callExpName,
texture: sceneInfra.extraSegmentTexture,
})
}
seg.layers.set(SKETCH_LAYER)
@ -435,6 +445,7 @@ export class SceneEntities {
) => {
await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
await this.setupSketch({
sketchPathToNode,
forward,
@ -442,7 +453,12 @@ export class SceneEntities {
position: origin,
maybeModdedAst: kclManager.ast,
})
this.setupSketchIdleCallbacks(sketchPathToNode)
this.setupSketchIdleCallbacks({
forward,
up,
position: origin,
pathToNode: sketchPathToNode,
})
}
setUpDraftSegment = async (
sketchPathToNode: PathToNode,
@ -467,19 +483,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`
let modifiedAst = addNewSketchLn({
node: kclManager.ast,
const mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]],
from: [lastSeg.to[0], lastSeg.to[1]],
fnName: segmentName,
pathToNode: sketchPathToNode,
}).modifiedAst
modifiedAst = parse(recast(modifiedAst))
})
const modifiedAst = parse(recast(mod.modifiedAst))
const draftExpressionsIndices = { start: index, end: index }
if (shouldTearDown) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
const { truncatedAst, programMemoryOverride, sketchGroup } =
await this.setupSketch({
sketchPathToNode,
@ -549,10 +566,101 @@ export class SceneEntities {
...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({
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
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({
object: selected,
intersection2d: intersectionPoint.twoD,
@ -755,8 +863,7 @@ export class SceneEntities {
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
arrowGroup.position.set(to[0], to[1], 0)
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
const previousPoint =
prevSegment?.type === 'TangentialArcTo'
@ -774,13 +881,40 @@ export class SceneEntities {
obtuse: true,
})
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)
const pxLength = arcInfo.arcLength / scale
const shouldHide = pxLength < MIN_SEGMENT_LENGTH
if (arrowGroup) {
arrowGroup.position.set(to[0], to[1], 0)
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(
(child) => child.userData.type === TANGENTIAL_ARC_TO_SEGMENT_BODY
@ -827,10 +961,17 @@ export class SceneEntities {
group.userData.from = from
group.userData.to = to
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
shape.moveTo(0, (-SEGMENT_WIDTH_PX / 2) * scale) // The width of the line in px (2.4px in this case)
shape.lineTo(0, (SEGMENT_WIDTH_PX / 2) * scale)
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) {
arrowGroup.position.set(to[0], to[1], 0)
@ -842,6 +983,21 @@ export class SceneEntities {
.normalize()
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
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(
@ -1160,7 +1316,7 @@ function colorSegment(object: any, color: number) {
])
if (straightSegmentBody) {
straightSegmentBody.traverse((child) => {
if (child instanceof Mesh) {
if (child instanceof Mesh && !child.userData.ignoreColorChange) {
child.material.color.set(color)
}
})
@ -1264,7 +1420,7 @@ function massageFormats(a: any): Vector3 {
function mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
@ -1286,6 +1442,14 @@ function mouseEnterLeaveCallbacks() {
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
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
}
sceneInfra.highlightCallback([0, 0])
@ -1302,6 +1466,14 @@ function mouseEnterLeaveCallbacks() {
selected,
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)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial