Skip to content

Commit

Permalink
project global origin for sketches and use engine animations (#2113)
Browse files Browse the repository at this point in the history
* use engine animations for sketch on face, but not default planes

* massage things

* fix type issue

* small problem in playwright test<

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

* some tests fixes

* more test tweaks

* more test tweaks

* clean up

* more tidy

* tests are a pain

* more test stuff

* test stuff again

* fix micro think axes in sketch mode

* more test shit

* more test shit more

* more test tweaks

* more test tweaks

* more test stuff

* trigger ci

* clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Irev-Dev and github-actions[bot] authored Apr 22, 2024
1 parent 0b235dc commit 3d73b82
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 114 deletions.
145 changes: 99 additions & 46 deletions e2e/playwright/flow-tests.spec.ts

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion e2e/playwright/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PNG } from 'pngjs'

async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page.getByTestId('loading').waitFor({ state: 'detached' })

Expand Down
85 changes: 81 additions & 4 deletions src/clientSideScene/CameraControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,31 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
this.camera.up.set(camSettings.up.x, camSettings.up.y, camSettings.up.z)
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
if (this.camera instanceof OrthographicCamera && !camSettings.ortho) {
this.usePerspectiveCamera()
}
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
this.camera.fov = camSettings.fov_y
} else if (
this.camera instanceof OrthographicCamera &&
camSettings.ortho_scale
) {
this.camera.zoom = camSettings.ortho_scale
const distanceToTarget = new Vector3(
camSettings.pos.x,
camSettings.pos.y,
camSettings.pos.z
).distanceTo(
new Vector3(
camSettings.center.x,
camSettings.center.y,
camSettings.center.z
)
)
this.camera.zoom = (camSettings.ortho_scale * 40) / distanceToTarget
}
this.onCameraChange()
}
Expand Down Expand Up @@ -965,10 +983,10 @@ export class CameraControls {
// Pure function helpers

function calculateNearFarFromFOV(fov: number) {
const nearFarRatio = (fov - 3) / (45 - 3)
// const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far }
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far: 1000 }
}

function convertThreeCamValuesToEngineCam({
Expand Down Expand Up @@ -1043,3 +1061,62 @@ function _getInteractionType(
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
return state
}

/**
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
* to ensure the client-side camera is synchronized with the engine's camera state.
*
* @param engineCommandManager Our websocket singleton
* @param entityId - The ID of face or sketchPlane.
*/

export async function letEngineAnimateAndSyncCamAfter(
engineCommandManager: EngineCommandManager,
entityId: string
) {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: !isReducedMotion(),
ortho: false,
entity_id: entityId,
},
})
// wait 600ms (animation takes 500, + 100 for safety)
await new Promise((resolve) =>
setTimeout(resolve, isReducedMotion() ? 100 : 600)
)
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: false,
ortho: true,
entity_id: entityId,
},
})
await new Promise((resolve) => setTimeout(resolve, 50))
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}
100 changes: 70 additions & 30 deletions src/clientSideScene/sceneEntities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,9 @@ export class SceneEntities {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
const yAxisGeometry = new BoxGeometry(0.3, 100000, 0.01)
const axisPixelWidth = 1.6
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
const xAxisMaterial = new MeshBasicMaterial({
color: baseXColor,
depthTest: false,
Expand Down Expand Up @@ -1324,30 +1325,31 @@ export class SceneEntities {
selected.material.color = defaultPlaneColor(type)
},
onClick: async (args) => {
const checkExtrudeFaceClick = async (): Promise<boolean> => {
const checkExtrudeFaceClick = async (): Promise<
['face' | 'plane' | 'other', string]
> => {
const { streamDimensions } = useStore.getState()
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
streamDimensions
)
if (!entity_id) return false
if (!entity_id) return ['other', '']
if (
engineCommandManager.defaultPlanes?.xy === entity_id ||
engineCommandManager.defaultPlanes?.xz === entity_id ||
engineCommandManager.defaultPlanes?.yz === entity_id
) {
return ['plane', entity_id]
}
const artifact = this.engineCommandManager.artifactMap[entity_id]
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
return false
const faceInfo: Models['FaceIsPlanar_type'] = (
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: entity_id,
},
})
)?.data?.data
return ['other', entity_id]

const faceInfo = await getFaceDetails(entity_id)
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return false
const { z_axis, origin, y_axis } = faceInfo
return ['other', entity_id]
const { z_axis, y_axis, origin } = faceInfo
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
Expand All @@ -1367,12 +1369,15 @@ export class SceneEntities {
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: entity_id,
},
})
return true
return ['face', entity_id]
}

if (await checkExtrudeFaceClick()) return
const faceResult = await checkExtrudeFaceClick()
console.log('faceResult', faceResult)
if (faceResult[0] === 'face') return

if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
Expand All @@ -1398,6 +1403,7 @@ export class SceneEntities {
plane: planeString,
zAxis,
yAxis,
planeId: faceResult[1],
},
})
},
Expand Down Expand Up @@ -1681,7 +1687,7 @@ export async function getSketchOrientationDetails(
sketchPathToNode: PathToNode
): Promise<{
quat: Quaternion
sketchDetails: SketchDetails
sketchDetails: SketchDetails & { faceId?: string }
}> {
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
Expand All @@ -1697,20 +1703,13 @@ export async function getSketchOrientationDetails(
zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
origin: [0, 0, 0],
faceId: sketchGroup.on.id,
},
}
}
if (sketchGroup.on.type === 'face') {
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: sketchGroup.on.faceId,
},
})
)?.data?.data
const faceInfo = await getFaceDetails(sketchGroup.on.faceId)

if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
throw new Error('faceInfo')
const { z_axis, y_axis, origin } = faceInfo
Expand All @@ -1725,6 +1724,7 @@ export async function getSketchOrientationDetails(
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
origin: [origin.x, origin.y, origin.z],
faceId: sketchGroup.on.faceId,
},
}
}
Expand All @@ -1733,6 +1733,46 @@ export async function getSketchOrientationDetails(
)
}

/**
* Retrieves orientation details for a given entity representing a face (brep face or default plane).
* This function asynchronously fetches and returns the origin, x-axis, y-axis, and z-axis details
* for a specified entity ID. It is primarily used to obtain the orientation of a face in the scene,
* which is essential for calculating the correct positioning and alignment of the client side sketch.
*
* @param entityId - The ID of the entity for which orientation details are being fetched.
* @returns A promise that resolves with the orientation details of the face.
*/
async function getFaceDetails(
entityId: string
): Promise<Models['FaceIsPlanar_type']> {
// TODO mode engine connection to allow batching returns and batch the following
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: false,
animated: false,
ortho: false,
entity_id: entityId,
},
})
// TODO change typing to get_sketch_mode_plane once lib is updated
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
)?.data?.data
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
return faceInfo
}

export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
const dummyCam = new PerspectiveCamera()
dummyCam.up.set(0, 0, 1)
Expand Down
23 changes: 8 additions & 15 deletions src/components/ModelingMachineProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,9 @@ import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'

type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
Expand Down Expand Up @@ -319,16 +318,9 @@ export const ModelingMachineProvider = ({
)
await kclManager.executeAstMock(modifiedAst)

const forward = new Vector3(...data.zAxis)
const up = new Vector3(...data.yAxis)

let target = new Vector3(...data.position).multiplyScalar(
sceneInfra._baseUnitMultiplier
)
const quaternion = quaternionFromUpNForward(up, forward)
await sceneInfra.camControls.tweenCameraToQuaternion(
quaternion,
target
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
data.faceId
)

return {
Expand All @@ -343,6 +335,7 @@ export const ModelingMachineProvider = ({
data.plane
)
await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.syncDirection = 'clientToEngine'
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
Expand All @@ -359,9 +352,9 @@ export const ModelingMachineProvider = ({
sourceRange
)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
info?.sketchDetails?.faceId || ''
)
return {
sketchPathToNode: sketchPathToNode || [],
Expand Down
10 changes: 10 additions & 0 deletions src/lang/KclSingleton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,16 @@ export class KclManager {
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
enterEditMode() {
enterEditMode(this.programMemory, this.engineCommandManager)
}
exitEditMode() {
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
}
}

function enterEditMode(
Expand Down
5 changes: 4 additions & 1 deletion src/lang/std/engineConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,10 @@ export class EngineCommandManager {
type: 'receive-reliable',
data: message,
id,
cmd_type: command?.commandType || this.lastArtifactMap[id]?.commandType,
cmd_type:
command?.commandType ||
this.lastArtifactMap[id]?.commandType ||
sceneCommand?.commandType,
})
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
(callback) => callback(modelingResponse)
Expand Down
12 changes: 12 additions & 0 deletions src/lib/singletons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,15 @@ export const sceneEntitiesManager = new SceneEntities(engineCommandManager)

// This needs to be after sceneInfra and engineCommandManager are is created.
export const editorManager = new EditorManager()

if (typeof window !== 'undefined') {
;(window as any).engineCommandManager = engineCommandManager
;(window as any).kclManager = kclManager
;(window as any).sceneInfra = sceneInfra
;(window as any).sceneEntitiesManager = sceneEntitiesManager
;(window as any).editorManager = editorManager
;(window as any).enableMousePositionLogs = () =>
document.addEventListener('mousemove', (e) =>
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
)
}
Loading

0 comments on commit 3d73b82

Please sign in to comment.