[Feature]: Initialize engine with view_isometric as a user. (#6604)

* feature: initialize system to view_isometric, playwright(e2e) does zoom_to_fit

* fix: PR fixes

* fix: removing testing code

* fix: I definitely bricked my .spacemacs config and it is using the wrong eslint when I save files :(

* fix: typo

* fix: typoos

* fix: fuking hack

* chore: move exported var from e2e to src then reimport

* fix: got em

* fix: remove console log

* fix: how did this get in that file?? moved it

* fix: location for scene empty check zzz:

* fix: removed debugging code

* fix: forgot the hack for the load view without geometry workflow

* fix: copy
This commit is contained in:
Kevin Nadro
2025-05-01 12:42:44 -05:00
committed by GitHub
parent 89bae66257
commit ed406734a7
11 changed files with 210 additions and 35 deletions

View File

@ -3342,7 +3342,7 @@ profile001 = startProfile(sketch001, at = [-20, 20])
const testPoint = { x: 590, y: 400 }
const extrudeColor: [number, number, number] = [100, 100, 100]
const sketchColor: [number, number, number] = [140, 140, 140]
const defaultPlaneColor: [number, number, number] = [50, 50, 100]
const defaultPlaneColor: [number, number, number] = [88, 44, 45]
const deleteOperation = async (operationButton: Locator) => {
if (shouldUseKeyboard) {
@ -3386,7 +3386,11 @@ profile001 = startProfile(sketch001, at = [-20, 20])
)
await deleteOperation(operationButton)
await editor.expectEditor.toContain('')
await scene.expectPixelColor(defaultPlaneColor, testPoint, 20)
// Cannot use test point anymore because the camera's position has been
// reset and the rest of the test doesn't need to change just to check
// if the scene is cleared.
// Check that the scene is cleared
await scene.expectPixelColor(defaultPlaneColor, { x: 574, y: 342 }, 20)
})
})
})

View File

@ -5,8 +5,6 @@ import { onboardingPaths } from '@src/routes/Onboarding/paths'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
export const IS_PLAYWRIGHT_KEY = 'playwright'
export const TEST_SETTINGS_KEY = '/settings.toml'
export const TEST_SETTINGS: DeepPartial<Settings> = {
app: {

View File

@ -18,11 +18,8 @@ import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfigu
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { secrets } from '@e2e/playwright/secrets'
import {
IS_PLAYWRIGHT_KEY,
TEST_SETTINGS,
TEST_SETTINGS_KEY,
} from '@e2e/playwright/storageStates'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants'
import { test } from '@e2e/playwright/zoo-test'
const toNormalizedCode = (text: string) => {

View File

@ -29,6 +29,13 @@ import { useSelector } from '@xstate/react'
import type { MouseEventHandler } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useRouteLoaderData } from 'react-router-dom'
import { isPlaywright } from '@src/lib/isPlaywright'
import {
engineStreamZoomToFit,
engineViewIsometricWithGeometryPresent,
engineViewIsometricWithoutGeometryPresent,
} from '@src/lib/utils'
import { DEFAULT_DEFAULT_LENGTH_UNIT } from '@src/lib/constants'
export const EngineStream = (props: {
pool: string | null
@ -89,21 +96,33 @@ export const EngineStream = (props: {
console.log('scene is ready, fire!')
kmp
.then(() =>
// It makes sense to also call zoom to fit here, when a new file is
// loaded for the first time, but not overtaking the work kevin did
// so the camera isn't moving all the time.
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
animated: false, // don't animate the zoom for now
},
.then(async () => {
// Gotcha: Playwright E2E tests will be zoom_to_fit, when you try to recreate the e2e test manually
// your localhost will do view_isometric. Turn this boolean on to have the same experience when manually
// debugging e2e tests
// We need a padding of 0.1 for zoom_to_fit for all E2E tests since they were originally
// written with zoom_to_fit with padding 0.1
const padding = 0.1
if (isPlaywright()) {
await engineStreamZoomToFit({ engineCommandManager, padding })
} else {
// If the scene is empty you cannot use view_isometric, it will not move the camera
if (kclManager.isAstBodyEmpty(kclManager.ast)) {
await engineViewIsometricWithoutGeometryPresent({
engineCommandManager,
unit:
kclManager.fileSettings.defaultLengthUnit ||
DEFAULT_DEFAULT_LENGTH_UNIT,
})
} else {
await engineViewIsometricWithGeometryPresent({
engineCommandManager,
padding,
})
}
}
})
)
.catch(trap)
}

View File

@ -738,6 +738,18 @@ export class KclManager {
return ast.start === 0 && ast.end === 0 && ast.body.length === 0
}
/**
* Determines if there is no code to execute. If there is a @settings annotation
* that adds to the overall ast.start and ast.end but not the body which is the program
*
*
* If you need to know if there is any program code or not, use this function otherwise
* use _isAstEmpty
*/
isAstBodyEmpty(ast: Node<Program>) {
return ast.body.length === 0
}
get fileSettings() {
return this._fileSettings
}

View File

@ -187,3 +187,6 @@ export type ExecutionType =
| typeof EXECUTION_TYPE_REAL
| typeof EXECUTION_TYPE_MOCK
| typeof EXECUTION_TYPE_NONE
/** Key for setting window.localStorage.setItem and .getItem to determine if the runtime is playwright for browsers */
export const IS_PLAYWRIGHT_KEY = 'playwright'

10
src/lib/isPlaywright.ts Normal file
View File

@ -0,0 +1,10 @@
import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants'
export function isPlaywright(): boolean {
// ? checks for browser
const electronRunTimePlaywright =
window?.electron?.process?.env.IS_PLAYWRIGHT == 'true'
const browserRuntimePlaywright =
localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
return electronRunTimePlaywright || browserRuntimePlaywright
}

View File

@ -1,8 +1,6 @@
import type { PlatformPath } from 'path'
import type { Configuration } from '@rust/kcl-lib/bindings/Configuration'
import { IS_PLAYWRIGHT_KEY } from '@e2e/playwright/storageStates'
import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants'
import {
BROWSER_FILE_NAME,

View File

@ -6,6 +6,14 @@ import type { CallExpressionKw, SourceRange } from '@src/lang/wasm'
import { isDesktop } from '@src/lib/isDesktop'
import type { AsyncFn } from '@src/lib/types'
import * as THREE from 'three'
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
import type {
CameraViewState_type,
UnitLength_type,
} from '@kittycad/lib/dist/types/src/models'
export const uuidv4 = v4
/**
@ -533,3 +541,132 @@ export function getInVariableCase(name: string, prefixIfDigit = 'm') {
return likelyPascalCase.slice(0, 1).toLowerCase() + likelyPascalCase.slice(1)
}
export function computeIsometricQuaternionForEmptyScene() {
// Create the direction vector you want to look from
const isoDir = new THREE.Vector3(1, 1, 1).normalize() // isometric look direction
// Target is the point you want to look at (e.g., origin)
const target = new THREE.Vector3(0, 0, 0)
// Compute quaternion for isometric view
const up = new THREE.Vector3(0, 0, 1) // default up direction
const quaternion = new THREE.Quaternion()
quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), isoDir) // align -Z with isoDir
// Align up vector using a lookAt matrix
const m = new THREE.Matrix4()
m.lookAt(new THREE.Vector3().addVectors(target, isoDir), target, up)
quaternion.setFromRotationMatrix(m)
return quaternion
}
export async function engineStreamZoomToFit({
engineCommandManager,
padding,
}: {
engineCommandManager: EngineCommandManager
padding: number
}) {
// It makes sense to also call zoom to fit here, when a new file is
// loaded for the first time, but not overtaking the work kevin did
// so the camera isn't moving all the time.
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}
export async function engineViewIsometricWithGeometryPresent({
engineCommandManager,
padding,
}: {
engineCommandManager: EngineCommandManager
padding: number
}) {
/**
* Default all users to view_isometric when loading into the engine.
* This works for perspective projection and orthographic projection
* This does not change the projection of the camera only the view direction which makes
* it safe to use with either projection defaulted
*/
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'view_isometric',
padding, // padding around the objects
},
})
/**
* HACK: We need to update the gizmo, the command above doesn't trigger gizmo
* to render which makes the axis point in an old direction.
*/
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}
export async function engineViewIsometricWithoutGeometryPresent({
engineCommandManager,
unit,
}: {
engineCommandManager: EngineCommandManager
unit?: UnitLength_type
}) {
// If you load an empty scene with any file unit it will have an eye offset of this
const MAGIC_ENGINE_EYE_OFFSET = 1378.0057
const quat = computeIsometricQuaternionForEmptyScene()
const isometricView: CameraViewState_type = {
pivot_rotation: {
x: quat.x,
y: quat.y,
z: quat.z,
w: quat.w,
},
pivot_position: {
x: 0,
y: 0,
z: 0,
},
eye_offset: MAGIC_ENGINE_EYE_OFFSET,
fov_y: 45,
ortho_scale_factor: 1.4063792,
is_ortho: true,
ortho_scale_enabled: true,
world_coord_system: 'right_handed_up_z',
}
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_view',
view: {
...isometricView,
},
},
})
/**
* HACK: We need to update the gizmo, the command above doesn't trigger gizmo
* to render which makes the axis point in an old direction.
*/
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}

View File

@ -2435,7 +2435,9 @@ export const modelingMachine = setup({
'VariableDeclarator'
)
if (err(extrudeNode)) {
return new Error("Couldn't find extrude node", { cause: extrudeNode })
return new Error("Couldn't find extrude node", {
cause: extrudeNode,
})
}
// Perform the shell op
@ -3326,11 +3328,7 @@ export const modelingMachine = setup({
'Artifact graph emptied': 'hidePlanes',
},
entry: [
'show default planes',
'reset camera position',
'set selection filter to curves only',
],
entry: ['show default planes', 'set selection filter to curves only'],
description: `We want to disable selections and hover highlights here, because users can't do anything with that information until they actually add something to the scene. The planes are just for orientation here.`,
exit: 'set selection filter to defaults',
},

View File

@ -1,7 +1,6 @@
import { NODE_ENV } from '@src/env'
import { isDesktop } from '@src/lib/isDesktop'
import { IS_PLAYWRIGHT_KEY } from '@e2e/playwright/storageStates'
import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants'
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'