Compare commits

...

4 Commits

Author SHA1 Message Date
cf068f325e fix really dumb bug 2024-07-07 20:28:41 +10:00
d60d2292f3 replace another callbacks call 2024-07-07 18:39:05 +10:00
7d564cc2ac eliminate unneeded setCallbacks 2024-07-07 16:48:02 +10:00
05a84213dd move idleCallbacks 2024-07-07 16:09:46 +10:00
9 changed files with 630 additions and 627 deletions

View File

@ -19,7 +19,10 @@ import {
PROFILE_START,
getParentGroup,
} from './sceneEntities'
import { SegmentOverlay, SketchDetails } from 'machines/modelingMachine'
import {
SegmentOverlay,
ModelingMachineContext as ModelingContextType,
} from 'machines/modelingMachine'
import { findUsesOfTagInPipe, getNodeFromPath } from 'lang/queryAst'
import {
CallExpression,
@ -353,11 +356,12 @@ export const confirmModal = create<ConfirmModalProps, boolean, boolean>(
export async function deleteSegment({
pathToNode,
sketchDetails,
context,
}: {
pathToNode: PathToNode
sketchDetails: SketchDetails | null
context: ModelingContextType
}) {
const { sketchDetails } = context
let modifiedAst: Program | Error = kclManager.ast
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
@ -394,13 +398,7 @@ export async function deleteSegment({
}
if (!sketchDetails) return
await sceneEntitiesManager.updateAstAndRejigSketch(
pathToNode,
modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
)
await sceneEntitiesManager.updateAstAndRejigSketch(modifiedAst, context)
// Now 'Set sketchDetails' is called with the modified pathToNode
}

View File

@ -0,0 +1,362 @@
import { getEventForSegmentSelection } from 'lib/selections'
import {
editorManager,
kclManager,
sceneEntitiesManager,
sceneInfra,
} from 'lib/singletons'
import {
Intersection,
Mesh,
MeshBasicMaterial,
Object3D,
Object3DEventMap,
OrthographicCamera,
Points,
Vector2,
Vector3,
} from 'three'
import {
EXTRA_SEGMENT_HANDLE,
PROFILE_START,
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup,
sketchGroupFromPathToNode,
} from './sceneEntities'
import { trap } from 'lib/trap'
import { addNewSketchLn } from 'lang/std/sketch'
import { CallExpression, PathToNode, parse, recast } from 'lang/wasm'
import { ARROWHEAD, X_AXIS, Y_AXIS } from './sceneInfra'
import { getNodeFromPath } from 'lang/queryAst'
import { getThemeColorForThreeJs } from 'lib/theme'
import { orthoScale, perspScale, quaternionFromUpNForward } from './helpers'
import { ModelingMachineContext } from 'machines/modelingMachine'
import { addStartProfileAt } from 'lang/modifyAst'
export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent
}
export interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
}
export interface OnClickCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint?: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
export interface OnMoveCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
interface Callbacks {
onDragStart: (arg: OnDragCallbackArgs) => void
onDragEnd: (arg: OnDragCallbackArgs) => void
onDrag: (arg: OnDragCallbackArgs) => void
onMove: (arg: OnMoveCallbackArgs) => void
onClick: (arg: OnClickCallbackArgs) => void
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void
}
type SetCallbacksWithCtx = (context: ModelingMachineContext) => Callbacks
const dummyListenersAll: Callbacks = {
onDragStart: () => {},
onDragEnd: () => {},
onDrag: () => {},
onMove: () => {},
onClick: () => {},
onMouseEnter: () => {},
onMouseLeave: () => {},
}
const onMousEnterLeaveCallbacks = {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return
const _node = getNodeFromPath<CallExpression>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
)
if (trap(_node, { suppress: true })) return
const node = _node.node
editorManager.setHighlightRange([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
}
})
}
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
if (parent.name === STRAIGHT_SEGMENT) {
sceneEntitiesManager.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
sceneEntitiesManager.updateTangentialArcToSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
}
return
}
editorManager.setHighlightRange([0, 0])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, parent)) /
sceneInfra._baseUnitMultiplier
if (parent.name === STRAIGHT_SEGMENT) {
sceneEntitiesManager.updateStraightSegment({
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
} else if (parent.name === TANGENTIAL_ARC_TO_SEGMENT) {
sceneEntitiesManager.updateTangentialArcToSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
group: parent,
scale: factor,
})
}
}
const isSelected = parent?.userData?.isSelected
colorSegment(
selected,
isSelected
? 0x0000ff
: parent?.userData?.baseColor ||
getThemeColorForThreeJs(sceneInfra._theme)
)
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
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
}
},
} as const
export const idleCallbacks: SetCallbacksWithCtx = (context) => {
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
return {
onDragStart: () => {},
onDragEnd: () => {},
onMove: () => {},
...onMousEnterLeaveCallbacks,
onClick: (args) => {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
},
})
return
}
const { selected } = args
const event = getEventForSegmentSelection(selected)
if (!event) return
sceneInfra.modelingSend(event)
},
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,
})
if (trap(sketchGroup)) return
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'
if (trap(mod)) return
await kclManager.executeAstMock(mod.modifiedAst)
await sceneEntitiesManager.tearDownSketch({ removeAxis: false })
sceneEntitiesManager.setupSketch({
sketchPathToNode: pathToNode,
maybeModdedAst: kclManager.ast,
up: context.sketchDetails?.yAxis || [0, 1, 0],
forward: context.sketchDetails?.zAxis || [0, 0, 1],
position: context.sketchDetails?.origin || [0, 0, 0],
})
addingNewSegmentStatus = 'added'
} else if (addingNewSegmentStatus === 'added') {
const pathToNodeForNewSegment = pathToNode.slice(0, pathToNodeIndex)
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
sceneEntitiesManager.onDragSegment({
sketchPathToNode: pathToNodeForNewSegment,
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
})
}
return
}
sceneEntitiesManager.onDragSegment({
object: selected,
intersection2d: intersectionPoint.twoD,
intersects,
sketchPathToNode: context.sketchDetails?.sketchPathToNode || [],
})
},
}
}
export const Sketch_LineTool_NoPoints: SetCallbacksWithCtx = ({
sketchDetails,
}) => {
if (!sketchDetails) return dummyListenersAll
sceneEntitiesManager.createIntersectionPlane()
const quaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis)
)
sceneEntitiesManager.intersectionPlane &&
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(quaternion)
sceneEntitiesManager.intersectionPlane &&
sceneEntitiesManager.intersectionPlane.position.copy(
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
)
return {
...dummyListenersAll,
onClick: async (args) => {
if (!args) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
const addStartProfileAtRes = addStartProfileAt(
kclManager.ast,
sketchDetails.sketchPathToNode,
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
)
if (trap(addStartProfileAtRes)) return
const { modifiedAst } = addStartProfileAtRes
await kclManager.updateAst(modifiedAst, false)
sceneEntitiesManager.removeIntersectionPlane()
sceneInfra.modelingSend('Add start point')
},
}
}
function colorSegment(object: any, color: number) {
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
if (segmentHead) {
segmentHead.traverse((child) => {
if (child instanceof Mesh) {
child.material.color.set(color)
}
})
return
}
const straightSegmentBody = getParentGroup(object, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
])
if (straightSegmentBody) {
straightSegmentBody.traverse((child) => {
if (child instanceof Mesh && !child.userData.ignoreColorChange) {
child.material.color.set(color)
}
})
return
}
}

View File

@ -22,12 +22,8 @@ import {
import {
ARROWHEAD,
AXIS_GROUP,
DEFAULT_PLANES,
DefaultPlane,
defaultPlaneColor,
getSceneScale,
INTERSECTION_PLANE_LAYER,
OnMouseEnterLeaveArgs,
RAYCASTABLE_PLANE,
SKETCH_GROUP_SEGMENTS,
SKETCH_LAYER,
@ -82,16 +78,16 @@ import {
createPipeSubstitution,
findUniqueName,
} from 'lang/modifyAst'
import {
Selections,
getEventForSegmentSelection,
sendSelectEventToEngine,
} from 'lib/selections'
import { Selections, sendSelectEventToEngine } from 'lib/selections'
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { createGridHelper, orthoScale, perspScale } from './helpers'
import { Models } from '@kittycad/lib'
import { uuidv4 } from 'lib/utils'
import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine'
import {
SegmentOverlayPayload,
SketchDetails,
ModelingMachineContext,
} from 'machines/modelingMachine'
import {
ArtifactMapCommand,
EngineCommandManager,
@ -102,6 +98,7 @@ import {
} from 'lib/rectangleTool'
import { getThemeColorForThreeJs } from 'lib/theme'
import { err, trap } from 'lib/trap'
import { OnMouseEnterLeaveArgs, idleCallbacks } from './sceneCallbacks'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -499,31 +496,23 @@ export class SceneEntities {
variableDeclarationName,
}
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
updateAstAndRejigSketch: (
modifiedAst: Program | Error,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number]
) => {
context: ModelingMachineContext
) => Promise<any> = async (modifiedAst, context) => {
if (err(modifiedAst)) return modifiedAst
const nextAst = await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
await this.setupSketch({
sketchPathToNode,
forward,
up,
position: origin,
sketchPathToNode: context.sketchDetails?.sketchPathToNode || [],
forward: context.sketchDetails?.zAxis || [0, 0, 1],
up: context.sketchDetails?.yAxis || [0, 1, 0],
position: context.sketchDetails?.origin || [0, 0, 0],
maybeModdedAst: nextAst.newAst,
})
this.setupSketchIdleCallbacks({
forward,
up,
position: origin,
pathToNode: sketchPathToNode,
})
this.setupSketchIdleCallbacks(context)
return nextAst
}
setUpDraftSegment = async (
@ -838,128 +827,8 @@ export class SceneEntities {
},
})
}
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({
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,
})
if (trap(sketchGroup)) return
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'
if (trap(mod)) return
await kclManager.executeAstMock(mod.modifiedAst)
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,
intersects,
sketchPathToNode: pathToNode,
})
},
onMove: () => {},
onClick: (args) => {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
},
})
return
}
const { selected } = args
const event = getEventForSegmentSelection(selected)
if (!event) return
sceneInfra.modelingSend(event)
},
...this.mouseEnterLeaveCallbacks(),
})
setupSketchIdleCallbacks = (context: ModelingMachineContext) => {
sceneInfra.setCallbacks(idleCallbacks(context))
}
prepareTruncatedMemoryAndAst = (
sketchPathToNode: PathToNode,
@ -1429,150 +1298,6 @@ export class SceneEntities {
this._tearDownSketch(0, resolve, reject, { removeAxis })
})
}
setupDefaultPlaneHover() {
sceneInfra.setCallbacks({
onMouseEnter: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type, 0.5, 1)
},
onMouseLeave: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type)
},
onClick: async (args) => {
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
sceneInfra._streamDimensions
)
let _entity_id = entity_id
if (!_entity_id) return
if (
engineCommandManager.defaultPlanes?.xy === _entity_id ||
engineCommandManager.defaultPlanes?.xz === _entity_id ||
engineCommandManager.defaultPlanes?.yz === _entity_id ||
engineCommandManager.defaultPlanes?.negXy === _entity_id ||
engineCommandManager.defaultPlanes?.negXz === _entity_id ||
engineCommandManager.defaultPlanes?.negYz === _entity_id
) {
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[engineCommandManager.defaultPlanes.xy]: 'XY',
[engineCommandManager.defaultPlanes.xz]: 'XZ',
[engineCommandManager.defaultPlanes.yz]: 'YZ',
[engineCommandManager.defaultPlanes.negXy]: '-XY',
[engineCommandManager.defaultPlanes.negXz]: '-XZ',
[engineCommandManager.defaultPlanes.negYz]: '-YZ',
}
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
// get unit vector from camera position to target
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
if (engineCommandManager.defaultPlanes?.xy === _entity_id) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
_entity_id = engineCommandManager.defaultPlanes?.negXy || ''
}
} else if (engineCommandManager.defaultPlanes?.yz === _entity_id) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
_entity_id = engineCommandManager.defaultPlanes?.negYz || ''
}
} else if (engineCommandManager.defaultPlanes?.xz === _entity_id) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
_entity_id = engineCommandManager.defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
_entity_id = engineCommandManager.defaultPlanes?.xz || ''
}
}
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'defaultPlane',
planeId: _entity_id,
plane: defaultPlaneStrMap[_entity_id],
zAxis,
yAxis,
},
})
return
}
const artifact = this.engineCommandManager.artifactMap[_entity_id]
// If we clicked on an extrude wall, we climb up the parent Id
// to get the sketch profile's face ID. If we clicked on an endcap,
// we already have it.
const targetId =
'additionalData' in artifact &&
artifact.additionalData?.type === 'cap'
? _entity_id
: artifact.parentId
// tsc cannot infer that target can have extrusions
// from the commandType (why?) so we need to cast it
const target = this.engineCommandManager.artifactMap?.[
targetId || ''
] as ArtifactMapCommand & { extrusions?: string[] }
// TODO: We get the first extrusion command ID,
// which is fine while backend systems only support one extrusion.
// but we need to more robustly handle resolving to the correct extrusion
// if there are multiple.
const extrusions =
this.engineCommandManager.artifactMap?.[target?.extrusions?.[0] || '']
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return
const faceInfo = await getFaceDetails(_entity_id)
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return
const { z_axis, y_axis, origin } = faceInfo
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
)
const extrudePathToNode = extrusions?.range
? getNodePathFromSourceRange(kclManager.ast, extrusions.range)
: []
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'extrudeFace',
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
position: [origin.x, origin.y, origin.z].map(
(num) => num / sceneInfra._baseUnitMultiplier
) as [number, number, number],
sketchPathToNode,
extrudePathToNode,
cap:
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: _entity_id,
},
})
return
},
})
}
mouseEnterLeaveCallbacks() {
return {
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
@ -1863,6 +1588,7 @@ export function sketchGroupFromPathToNode({
return result as SketchGroup
}
// TODO delete
function colorSegment(object: any, color: number) {
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
if (segmentHead) {

View File

@ -1,25 +1,25 @@
import {
AmbientLight,
Color,
GridHelper,
LineBasicMaterial,
OrthographicCamera,
PerspectiveCamera,
Scene,
Vector3,
WebGLRenderer,
Raycaster,
Vector2,
Group,
PlaneGeometry,
MeshBasicMaterial,
Mesh,
DoubleSide,
GridHelper,
Group,
Intersection,
LineBasicMaterial,
Mesh,
MeshBasicMaterial,
Object3D,
Object3DEventMap,
TextureLoader,
OrthographicCamera,
PerspectiveCamera,
PlaneGeometry,
Raycaster,
Scene,
Texture,
TextureLoader,
Vector2,
Vector3,
WebGLRenderer,
} from 'three'
import { Coords2d, compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext'
@ -31,6 +31,12 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
import { getAngle, throttle } from 'lib/utils'
import { Themes } from 'lib/theme'
import {
OnClickCallbackArgs,
OnDragCallbackArgs,
OnMouseEnterLeaveArgs,
OnMoveCallbackArgs,
} from './sceneCallbacks'
type SendType = ReturnType<typeof useModelingContext>['send']
@ -55,39 +61,6 @@ export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent
}
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
}
interface OnClickCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint?: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
interface OnMoveCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
// This singleton class is responsible for all of the under the hood setup for the client side scene.
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
@ -618,59 +591,6 @@ export class SceneInfra {
this.onClickCallback({ mouseEvent, intersects })
}
}
showDefaultPlanes() {
const addPlane = (
rotation: { x: number; y: number; z: number }, //
type: DefaultPlane
): Mesh => {
const planeGeometry = new PlaneGeometry(100, 100)
const planeMaterial = new MeshBasicMaterial({
color: defaultPlaneColor(type),
transparent: true,
opacity: 0.0,
side: DoubleSide,
depthTest: false, // needed to avoid transparency issues
})
const plane = new Mesh(planeGeometry, planeMaterial)
plane.rotation.x = rotation.x
plane.rotation.y = rotation.y
plane.rotation.z = rotation.z
plane.userData.type = type
plane.name = type
return plane
}
const planes = [
addPlane({ x: 0, y: Math.PI / 2, z: 0 }, YZ_PLANE),
addPlane({ x: 0, y: 0, z: 0 }, XY_PLANE),
addPlane({ x: -Math.PI / 2, y: 0, z: 0 }, XZ_PLANE),
]
const planesGroup = new Group()
planesGroup.userData.type = DEFAULT_PLANES
planesGroup.name = DEFAULT_PLANES
planesGroup.add(...planes)
planesGroup.traverse((child) => {
if (child instanceof Mesh) {
child.layers.enable(SKETCH_LAYER)
}
})
planesGroup.layers.enable(SKETCH_LAYER)
const sceneScale = getSceneScale(
this.camControls.camera,
this.camControls.target
)
planesGroup.scale.set(
sceneScale / this._baseUnitMultiplier,
sceneScale / this._baseUnitMultiplier,
sceneScale / this._baseUnitMultiplier
)
this.scene.add(planesGroup)
}
removeDefaultPlanes() {
const planesGroup = this.scene.children.find(
({ userData }) => userData.type === DEFAULT_PLANES
)
if (planesGroup) this.scene.remove(planesGroup)
}
updateOtherSelectionColors = (otherSelections: Axis[]) => {
const axisGroup = this.scene.children.find(
({ userData }) => userData?.type === AXIS_GROUP
@ -728,28 +648,3 @@ function baseUnitTomm(baseUnit: BaseUnit) {
return 914.4
}
}
export type DefaultPlane =
| 'xy-default-plane'
| 'xz-default-plane'
| 'yz-default-plane'
export const XY_PLANE: DefaultPlane = 'xy-default-plane'
export const XZ_PLANE: DefaultPlane = 'xz-default-plane'
export const YZ_PLANE: DefaultPlane = 'yz-default-plane'
export function defaultPlaneColor(
plane: DefaultPlane,
lowCh = 0.1,
highCh = 0.7
): Color {
switch (plane) {
case XY_PLANE:
return new Color(highCh, lowCh, lowCh)
case XZ_PLANE:
return new Color(lowCh, lowCh, highCh)
case YZ_PLANE:
return new Color(lowCh, highCh, lowCh)
}
return new Color(lowCh, lowCh, lowCh)
}

View File

@ -549,10 +549,8 @@ export const ModelingMachineProvider = ({
) as [number, number, number],
}
},
'Get horizontal info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get horizontal info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setHorzDistance',
@ -566,11 +564,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -585,10 +580,8 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get vertical info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get vertical info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setVertDistance',
@ -602,11 +595,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -621,10 +611,8 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get angle info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get angle info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const info = angleBetweenInfo({
selectionRanges,
})
@ -647,11 +635,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -666,10 +651,8 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get length info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get length info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleLength({ selectionRanges })
const _modifiedAst = parse(recast(modifiedAst))
@ -680,11 +663,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -699,10 +679,10 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get perpendicular distance info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get perpendicular distance info': async (
context
): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect(
{
selectionRanges,
@ -716,11 +696,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -735,10 +712,8 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get ABS X info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get ABS X info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } =
await applyConstraintAbsDistance({
constraint: 'xAbs',
@ -752,11 +727,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -771,10 +743,8 @@ export const ModelingMachineProvider = ({
updatedPathToNode,
}
},
'Get ABS Y info': async ({
selectionRanges,
sketchDetails,
}): Promise<SetSelections> => {
'Get ABS Y info': async (context): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
const { modifiedAst, pathToNodeMap } =
await applyConstraintAbsDistance({
constraint: 'yAbs',
@ -788,11 +758,8 @@ export const ModelingMachineProvider = ({
pathToNodeMap
)
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(
@ -808,9 +775,10 @@ export const ModelingMachineProvider = ({
}
},
'Get convert to variable info': async (
{ sketchDetails, selectionRanges },
context,
{ data }
): Promise<SetSelections> => {
const { selectionRanges, sketchDetails } = context
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const { variableName } = await getVarNameModal({
@ -834,11 +802,8 @@ export const ModelingMachineProvider = ({
return Promise.reject(new Error('No path to replaced node'))
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
pathToReplacedNode || [],
parsed,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
context
)
if (err(updatedAst)) return Promise.reject(updatedAst)
const selection = updateSelections(

View File

@ -109,7 +109,6 @@ export const Stream = () => {
},
})
if (state.matches('Sketch')) return
if (state.matches('Sketch no face')) return
if (!context.store?.didDragInStream && butName(e).left) {
sendSelectEventToEngine(

View File

@ -4,7 +4,7 @@ import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
export function useEngineConnectionSubscriptions() {
const { send, context } = useModelingContext()
const { send, state } = useModelingContext()
useEffect(() => {
if (!engineCommandManager) return
@ -29,9 +29,7 @@ export function useEngineConnectionSubscriptions() {
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: async (engineEvent) => {
const event = await getEventForSelectWithPoint(engineEvent, {
sketchEnginePathId: context.sketchEnginePathId,
})
const event = await getEventForSelectWithPoint(engineEvent, state)
event && send(event)
},
})
@ -39,5 +37,5 @@ export function useEngineConnectionSubscriptions() {
unSubHover()
unSubClick()
}
}, [engineCommandManager, context?.sketchEnginePathId])
}, [engineCommandManager, state])
}

View File

@ -4,6 +4,7 @@ import {
engineCommandManager,
kclManager,
sceneEntitiesManager,
sceneInfra,
} from 'lib/singletons'
import { CallExpression, SourceRange, Value, parse, recast } from 'lang/wasm'
import { ModelingMachineEvent } from 'machines/modelingMachine'
@ -15,6 +16,7 @@ import { Program } from 'lang/wasm'
import {
doesPipeHaveCallExp,
getNodeFromPath,
getNodePathFromSourceRange,
hasSketchPipeBeenExtruded,
isSingleCursorInPipe,
} from 'lang/queryAst'
@ -24,11 +26,15 @@ import {
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup,
PROFILE_START,
getFaceDetails,
DefaultPlaneStr,
} from 'clientSideScene/sceneEntities'
import { Mesh, Object3D, Object3DEventMap } from 'three'
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
import { PathToNodeMap } from 'lang/std/sketchcombos'
import { err } from 'lib/trap'
import { useModelingContext } from 'hooks/useModelingContext'
import { ArtifactMapCommand } from 'lang/std/engineConnection'
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
@ -62,8 +68,12 @@ export async function getEventForSelectWithPoint(
Models['OkModelingCmdResponse_type'],
{ type: 'select_with_point' }
>,
{ sketchEnginePathId }: { sketchEnginePathId?: string }
state: ReturnType<typeof useModelingContext>['state']
): Promise<ModelingMachineEvent | null> {
if (state.matches('Sketch no face'))
return handleSelectionInSketchNoFace(data?.entity_id || '')
// assumes XState state is `idle` from this point
if (!data?.entity_id) {
return {
type: 'Set selection',
@ -143,6 +153,128 @@ export async function getEventForSelectWithPoint(
}
}
async function handleSelectionInSketchNoFace(
entity_id: string
): Promise<ModelingMachineEvent | null> {
let _entity_id = entity_id
if (!_entity_id) return Promise.resolve(null)
if (
engineCommandManager.defaultPlanes?.xy === _entity_id ||
engineCommandManager.defaultPlanes?.xz === _entity_id ||
engineCommandManager.defaultPlanes?.yz === _entity_id ||
engineCommandManager.defaultPlanes?.negXy === _entity_id ||
engineCommandManager.defaultPlanes?.negXz === _entity_id ||
engineCommandManager.defaultPlanes?.negYz === _entity_id
) {
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[engineCommandManager.defaultPlanes.xy]: 'XY',
[engineCommandManager.defaultPlanes.xz]: 'XZ',
[engineCommandManager.defaultPlanes.yz]: 'YZ',
[engineCommandManager.defaultPlanes.negXy]: '-XY',
[engineCommandManager.defaultPlanes.negXz]: '-XZ',
[engineCommandManager.defaultPlanes.negYz]: '-YZ',
}
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
// get unit vector from camera position to target
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
if (engineCommandManager.defaultPlanes?.xy === _entity_id) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
_entity_id = engineCommandManager.defaultPlanes?.negXy || ''
}
} else if (engineCommandManager.defaultPlanes?.yz === _entity_id) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
_entity_id = engineCommandManager.defaultPlanes?.negYz || ''
}
} else if (engineCommandManager.defaultPlanes?.xz === _entity_id) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
_entity_id = engineCommandManager.defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
_entity_id = engineCommandManager.defaultPlanes?.xz || ''
}
}
return {
type: 'Select default plane',
data: {
type: 'defaultPlane',
planeId: _entity_id,
plane: defaultPlaneStrMap[_entity_id],
zAxis,
yAxis,
},
}
}
const artifact = engineCommandManager.artifactMap[_entity_id]
// If we clicked on an extrude wall, we climb up the parent Id
// to get the sketch profile's face ID. If we clicked on an endcap,
// we already have it.
const targetId =
'additionalData' in artifact && artifact.additionalData?.type === 'cap'
? _entity_id
: artifact.parentId
// tsc cannot infer that target can have extrusions
// from the commandType (why?) so we need to cast it
const target = engineCommandManager.artifactMap?.[
targetId || ''
] as ArtifactMapCommand & { extrusions?: string[] }
// TODO: We get the first extrusion command ID,
// which is fine while backend systems only support one extrusion.
// but we need to more robustly handle resolving to the correct extrusion
// if there are multiple.
const extrusions =
engineCommandManager.artifactMap?.[target?.extrusions?.[0] || '']
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return null
const faceInfo = await getFaceDetails(_entity_id)
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return null
const { z_axis, y_axis, origin } = faceInfo
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
)
const extrudePathToNode = extrusions?.range
? getNodePathFromSourceRange(kclManager.ast, extrusions.range)
: []
return {
type: 'Select default plane',
data: {
type: 'extrudeFace',
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
position: [origin.x, origin.y, origin.z].map(
(num) => num / sceneInfra._baseUnitMultiplier
) as [number, number, number],
sketchPathToNode,
extrudePathToNode,
cap:
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: _entity_id,
},
}
}
export function getEventForSegmentSelection(
obj: Object3D<Object3DEventMap>
): ModelingMachineEvent | null {

File diff suppressed because one or more lines are too long