diff --git a/e2e/playwright/named-views.spec.ts b/e2e/playwright/named-views.spec.ts new file mode 100644 index 000000000..c24ea6de9 --- /dev/null +++ b/e2e/playwright/named-views.spec.ts @@ -0,0 +1,292 @@ +import { test, expect } from './zoo-test' +import { PROJECT_SETTINGS_FILE_NAME } from 'lib/constants' +import * as fsp from 'fs/promises' +import { join } from 'path' +import { + createProject, + tomlToPerProjectSettings, + perProjectsettingsToToml, +} from './test-utils' +import { NamedView } from '@rust/kcl-lib/bindings/NamedView' + +// Helper function to determine if the file path on disk exists +// Specifically this is used to check if project.toml exists on disk +const fileExists = async (path: string) => { + return !!(await fsp + .stat(path) + .then((_) => true) + .catch((_) => false)) +} + +// Here are a few uuids. +// When created named views rust will auto generate uuids and they will +// never match the snapshots. Overwrite them in memory to these +// values to have them match the snapshots. +const uuid1: string = '0656fb1a-9640-473e-b334-591dc70c0138' +const uuid2: string = 'c810cf04-c6cc-4a4a-8b11-17bf445dcab7' +const uuid3: string = 'cfecbfee-48a6-4561-b96d-ffbe5678bb7d' + +// Look up the named view by name and then rewrite it with the same uuid each time +const nameToUuid: Map = new Map() +nameToUuid.set('uuid1', uuid1) +nameToUuid.set('uuid2', uuid2) +nameToUuid.set('uuid3', uuid3) + +/** + * Given the project.toml string, overwrite the named views to be the constant uuid + * values to match the snapshots. The uuids are randomly generated + */ +function tomlStringOverWriteNamedViewUuids(toml: string): string { + const settings = tomlToPerProjectSettings(toml) + const namedViews = settings.settings?.app?.named_views + if (namedViews) { + const entries = Object.entries(namedViews) + const remappedNamedViews: { [key: string]: NamedView } = {} + entries.forEach(([_, value]) => { + if (value) { + // {name:'uuid1'} -> uuid1 lookup + const staticUuid = nameToUuid.get(value.name) + if (staticUuid) { + remappedNamedViews[staticUuid] = value + } + } + }) + if (settings && settings.settings && settings.settings.app) { + settings.settings.app.named_views = remappedNamedViews + } + } + return perProjectsettingsToToml(settings) +} + +test.describe('Named view tests', () => { + test('Verify project.toml is not created', async ({ page }, testInfo) => { + // Create project and load it + const projectName = 'named-views' + await createProject({ name: projectName, page }) + + // Generate file paths for project.toml + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + const tempProjectSettingsFilePath = join( + projectDirName, + projectName, + PROJECT_SETTINGS_FILE_NAME + ) + + // project.toml should not exist on initial project creation + let exists = await fileExists(tempProjectSettingsFilePath) + expect(exists).toBe(false) + }) + test('Verify named view gets created', async ({ + cmdBar, + scene, + page, + }, testInfo) => { + const projectName = 'named-views' + const myNamedView = 'uuid1' + + // Create and load project + await createProject({ name: projectName, page }) + await scene.waitForExecutionDone() + + // Create named view + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('create named view') + await cmdBar.argumentInput.fill(myNamedView) + await cmdBar.progressCmdBar(false) + + // Generate paths for the project.toml + const tempProjectSettingsFilePath = join( + projectDirName, + projectName, + PROJECT_SETTINGS_FILE_NAME + ) + + // Expect project.toml to be generated on disk since a named view was created + await expect(async () => { + let exists = await fileExists(tempProjectSettingsFilePath) + expect(exists).toBe(true) + }).toPass() + + // Read project.toml into memory + let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8') + // Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break + tomlString = tomlStringOverWriteNamedViewUuids(tomlString) + + // Write the entire tomlString to a snapshot. + // There are many key/value pairs to check this is a safer match. + expect(tomlString).toMatchSnapshot('verify-named-view-gets-created') + }) + test('Verify named view gets deleted', async ({ + cmdBar, + scene, + page, + }, testInfo) => { + const projectName = 'named-views' + const myNamedView1 = 'uuid1' + const myNamedView2 = 'uuid2' + + // Create project and go into the project + await createProject({ name: projectName, page }) + await scene.waitForExecutionDone() + + // Create a new named view + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('create named view') + await cmdBar.argumentInput.fill(myNamedView1) + await cmdBar.progressCmdBar(false) + + // Generate file paths for project.toml + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + const tempProjectSettingsFilePath = join( + projectDirName, + projectName, + PROJECT_SETTINGS_FILE_NAME + ) + + // Except the project.toml to be written to disk since a named view was created + await expect(async () => { + let exists = await fileExists(tempProjectSettingsFilePath) + expect(exists).toBe(true) + }).toPass() + + // Read project.toml into memory + let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8') + // Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break + tomlString = tomlStringOverWriteNamedViewUuids(tomlString) + + // Write the entire tomlString to a snapshot. + // There are many key/value pairs to check this is a safer match. + expect(tomlString).toMatchSnapshot('verify-named-view-gets-created') + + // Delete a named view + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('delete named view') + cmdBar.selectOption({ name: myNamedView2 }) + await cmdBar.progressCmdBar(false) + + // Read project.toml into memory again since we deleted a named view + tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8') + // Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break + tomlString = tomlStringOverWriteNamedViewUuids(tomlString) + + // // Write the entire tomlString to a snapshot. + // // There are many key/value pairs to check this is a safer match. + expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted') + }) + test('Verify named view gets loaded', async ({ + cmdBar, + scene, + page, + }, testInfo) => { + const projectName = 'named-views' + const myNamedView = 'uuid1' + + // Create project and go into the project + await createProject({ name: projectName, page }) + await scene.waitForExecutionDone() + + // Create a new named view + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('create named view') + await cmdBar.argumentInput.fill(myNamedView) + await cmdBar.progressCmdBar(false) + + // Generate file paths for project.toml + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + const tempProjectSettingsFilePath = join( + projectDirName, + projectName, + PROJECT_SETTINGS_FILE_NAME + ) + + // Except the project.toml to be written to disk since a named view was created + await expect(async () => { + let exists = await fileExists(tempProjectSettingsFilePath) + expect(exists).toBe(true) + }).toPass() + + // Read project.toml into memory + let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8') + // Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break + tomlString = tomlStringOverWriteNamedViewUuids(tomlString) + + // Write the entire tomlString to a snapshot. + // There are many key/value pairs to check this is a safer match. + expect(tomlString).toMatchSnapshot('verify-named-view-gets-created') + + // Create a load a named view + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('load named view') + await cmdBar.argumentInput.fill(myNamedView) + await cmdBar.progressCmdBar(false) + + // Check the toast appeared + await expect( + page.getByText(`Named view ${myNamedView} loaded.`) + ).toBeVisible() + }) + test('Verify two named views get created', async ({ + cmdBar, + scene, + page, + }, testInfo) => { + const projectName = 'named-views' + const myNamedView1 = 'uuid1' + const myNamedView2 = 'uuid2' + + // Create and load project + await createProject({ name: projectName, page }) + await scene.waitForExecutionDone() + + // Create named view + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('create named view') + await cmdBar.argumentInput.fill(myNamedView1) + await cmdBar.progressCmdBar(false) + + await page.waitForTimeout(1000) + + const orbitMouseStart = { x: 800, y: 130 } + const orbitMouseEnd = { x: 0, y: 130 } + await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y) + await page.mouse.down({ button: 'middle' }) + await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, { + steps: 3, + }) + await page.mouse.up({ button: 'middle' }) + + await page.waitForTimeout(1000) + + await cmdBar.openCmdBar() + await cmdBar.chooseCommand('create named view') + await cmdBar.argumentInput.fill(myNamedView2) + await cmdBar.progressCmdBar(false) + + // Wait a moment for the project.toml to get written to disk with the new view point + await page.waitForTimeout(1000) + + // Generate paths for the project.toml + const tempProjectSettingsFilePath = join( + projectDirName, + projectName, + PROJECT_SETTINGS_FILE_NAME + ) + + // Expect project.toml to be generated on disk since a named view was created + await expect(async () => { + let exists = await fileExists(tempProjectSettingsFilePath) + expect(exists).toBe(true) + }).toPass() + + // Read project.toml into memory + let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8') + // Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break + tomlString = tomlStringOverWriteNamedViewUuids(tomlString) + + // Write the entire tomlString to a snapshot. + // There are many key/value pairs to check this is a safer match. + expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created') + }) +}) diff --git a/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-created-chromium-linux b/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-created-chromium-linux new file mode 100644 index 000000000..04d37214a --- /dev/null +++ b/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-created-chromium-linux @@ -0,0 +1,16 @@ +[settings] +modeling = { } +text_editor = { } +command_bar = { } + +[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138] +name = "uuid1" +eye_offset = 1_378.0059 +fov_y = 45 +is_ortho = false +ortho_scale_enabled = true +ortho_scale_factor = 1.6 +pivot_position = [ 0, 0, 0 ] +pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ] +world_coord_system = "right_handed_up_z" +version = 1 diff --git a/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-deleted-chromium-linux b/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-deleted-chromium-linux new file mode 100644 index 000000000..04d37214a --- /dev/null +++ b/e2e/playwright/named-views.spec.ts-snapshots/verify-named-view-gets-deleted-chromium-linux @@ -0,0 +1,16 @@ +[settings] +modeling = { } +text_editor = { } +command_bar = { } + +[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138] +name = "uuid1" +eye_offset = 1_378.0059 +fov_y = 45 +is_ortho = false +ortho_scale_enabled = true +ortho_scale_factor = 1.6 +pivot_position = [ 0, 0, 0 ] +pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ] +world_coord_system = "right_handed_up_z" +version = 1 diff --git a/e2e/playwright/named-views.spec.ts-snapshots/verify-two-named-view-gets-created-chromium-linux b/e2e/playwright/named-views.spec.ts-snapshots/verify-two-named-view-gets-created-chromium-linux new file mode 100644 index 000000000..555dbad81 --- /dev/null +++ b/e2e/playwright/named-views.spec.ts-snapshots/verify-two-named-view-gets-created-chromium-linux @@ -0,0 +1,28 @@ +[settings] +modeling = { } +text_editor = { } +command_bar = { } + +[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138] +name = "uuid1" +eye_offset = 1_378.0059 +fov_y = 45 +is_ortho = false +ortho_scale_enabled = true +ortho_scale_factor = 1.6 +pivot_position = [ 0, 0, 0 ] +pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ] +world_coord_system = "right_handed_up_z" +version = 1 + +[settings.app.named_views.c810cf04-c6cc-4a4a-8b11-17bf445dcab7] +name = "uuid2" +eye_offset = 1_378.0059 +fov_y = 45 +is_ortho = false +ortho_scale_enabled = true +ortho_scale_factor = 1.6 +pivot_position = [ 1_826.5239, 0.0, 0.0 ] +pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ] +world_coord_system = "right_handed_up_z" +version = 1 diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 226dc887d..6f266ad4d 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -26,6 +26,7 @@ import { isArray } from 'lib/utils' import { reportRejection } from 'lib/trap' import { DeepPartial } from 'lib/types' import { Configuration } from 'lang/wasm' +import { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' const toNormalizedCode = (text: string) => { return text.replace(/\s+/g, '') @@ -761,7 +762,7 @@ export interface Paths { } export const doExport = async ( - output: Models['OutputFormat_type'], + output: Models['OutputFormat3d_type'], rootDir: string, page: Page, exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' @@ -1125,3 +1126,15 @@ export function settingsToToml(settings: DeepPartial) { export function tomlToSettings(toml: string): DeepPartial { return TOML.parse(toml) } + +export function tomlToPerProjectSettings( + toml: string +): DeepPartial { + return TOML.parse(toml) +} + +export function perProjectsettingsToToml( + settings: DeepPartial +) { + return TOML.stringify(settings as any) +} diff --git a/package.json b/package.json index 4d0352411..64231e40e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.19", "@headlessui/tailwindcss": "^0.2.0", - "@kittycad/lib": "2.0.17", + "@kittycad/lib": "2.0.21", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.1", "@react-hook/resize-observer": "^2.0.1", diff --git a/rust/kcl-lib/src/settings/types/project.rs b/rust/kcl-lib/src/settings/types/project.rs index b418f3a7f..d8293114f 100644 --- a/rust/kcl-lib/src/settings/types/project.rs +++ b/rust/kcl-lib/src/settings/types/project.rs @@ -158,13 +158,13 @@ fn named_view_point_version_one() -> f64 { #[ts(export)] pub struct NamedView { /// User defined name to identify the named view. A label. - #[serde(default, alias = "name", skip_serializing_if = "is_default")] + #[serde(default, alias = "name")] pub name: String, /// Engine camera eye off set - #[serde(default, alias = "eyeOffset", skip_serializing_if = "is_default")] + #[serde(default, alias = "eyeOffset")] pub eye_offset: f64, /// Engine camera vertical FOV - #[serde(default, alias = "fovY", skip_serializing_if = "is_default")] + #[serde(default, alias = "fovY")] pub fov_y: f64, // Engine camera is orthographic or perspective projection #[serde(default, alias = "isOrtho")] @@ -173,16 +173,16 @@ pub struct NamedView { #[serde(default, alias = "orthoScaleEnabled")] pub ortho_scale_enabled: bool, /// Engine camera orthographic scaling factor - #[serde(default, alias = "orthoScaleFactor", skip_serializing_if = "is_default")] + #[serde(default, alias = "orthoScaleFactor")] pub ortho_scale_factor: f64, /// Engine camera position that the camera pivots around - #[serde(default, alias = "pivotPosition", skip_serializing_if = "is_default")] + #[serde(default, alias = "pivotPosition")] pub pivot_position: [f64; 3], /// Engine camera orientation in relation to the pivot position - #[serde(default, alias = "pivotRotation", skip_serializing_if = "is_default")] + #[serde(default, alias = "pivotRotation")] pub pivot_rotation: [f64; 4], /// Engine camera world coordinate system orientation - #[serde(default, alias = "worldCoordSystem", skip_serializing_if = "is_default")] + #[serde(default, alias = "worldCoordSystem")] pub world_coord_system: String, /// Version number of the view point if the engine camera API changes #[serde(default = "named_view_point_version_one")] diff --git a/src/components/FileMachineProvider.tsx b/src/components/FileMachineProvider.tsx index 59c5ba45c..2d9263198 100644 --- a/src/components/FileMachineProvider.tsx +++ b/src/components/FileMachineProvider.tsx @@ -4,7 +4,6 @@ import { type IndexLoaderData } from 'lib/types' import { BROWSER_PATH, PATHS } from 'lib/paths' import React, { createContext, useEffect, useMemo } from 'react' import { toast } from 'react-hot-toast' -import { DEV } from 'env' import { Actor, AnyStateMachine, @@ -61,34 +60,31 @@ export const FileMachineProvider = ({ ) useEffect(() => { - // TODO: Engine feature is not deployed - if (DEV) { - const { - createNamedViewCommand, - deleteNamedViewCommand, - loadNamedViewCommand, - } = createNamedViewsCommand() + const { + createNamedViewCommand, + deleteNamedViewCommand, + loadNamedViewCommand, + } = createNamedViewsCommand() - const commands = [ - createNamedViewCommand, - deleteNamedViewCommand, - loadNamedViewCommand, - ] + const commands = [ + createNamedViewCommand, + deleteNamedViewCommand, + loadNamedViewCommand, + ] + commandBarActor.send({ + type: 'Add commands', + data: { + commands, + }, + }) + return () => { + // Remove commands if you go to the home page commandBarActor.send({ - type: 'Add commands', + type: 'Remove commands', data: { commands, }, }) - return () => { - // Remove commands if you go to the home page - commandBarActor.send({ - type: 'Remove commands', - data: { - commands, - }, - }) - } } }, []) diff --git a/src/lib/commandBarConfigs/modelingCommandConfig.ts b/src/lib/commandBarConfigs/modelingCommandConfig.ts index a38c982ce..885cfe401 100644 --- a/src/lib/commandBarConfigs/modelingCommandConfig.ts +++ b/src/lib/commandBarConfigs/modelingCommandConfig.ts @@ -24,7 +24,7 @@ import { getVariableDeclaration } from 'lang/queryAst/getVariableDeclaration' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { getNodeFromPath } from 'lang/queryAst' -type OutputFormat = Models['OutputFormat_type'] +type OutputFormat = Models['OutputFormat3d_type'] type OutputTypeKey = OutputFormat['type'] type ExtractStorageTypes = T extends { storage: infer U } ? U : never type StorageUnion = ExtractStorageTypes diff --git a/src/lib/commandBarConfigs/namedViewsConfig.ts b/src/lib/commandBarConfigs/namedViewsConfig.ts index c7db063c2..a22313366 100644 --- a/src/lib/commandBarConfigs/namedViewsConfig.ts +++ b/src/lib/commandBarConfigs/namedViewsConfig.ts @@ -3,8 +3,88 @@ import { Command, CommandArgumentOption } from '../commandTypes' import toast from 'react-hot-toast' import { engineCommandManager } from 'lib/singletons' import { uuidv4 } from 'lib/utils' -import { settingsActor } from 'machines/appMachine' -import { reportRejection } from 'lib/trap' +import { settingsActor, getSettings } from 'machines/appMachine' +import { err, reportRejection } from 'lib/trap' +import { + CameraViewState_type, + WorldCoordinateSystem_type, +} from '@kittycad/lib/dist/types/src/models' + +function isWorldCoordinateSystemType( + x: string +): x is WorldCoordinateSystem_type { + return x === 'right_handed_up_z' || x === 'right_handed_up_y' +} + +function namedViewToCameraViewState( + namedView: NamedView +): CameraViewState_type | Error { + const worldCoordinateSystem: string = namedView.world_coord_system + + if (!isWorldCoordinateSystemType(worldCoordinateSystem)) { + return new Error('world coordinate system is not typed') + } + + const cameraViewState: CameraViewState_type = { + eye_offset: namedView.eye_offset, + fov_y: namedView.fov_y, + ortho_scale_enabled: namedView.ortho_scale_enabled, + ortho_scale_factor: namedView.ortho_scale_factor, + world_coord_system: worldCoordinateSystem, + is_ortho: namedView.is_ortho, + pivot_position: namedView.pivot_position, + pivot_rotation: namedView.pivot_rotation, + } + + return cameraViewState +} + +function cameraViewStateToNamedView( + name: string, + cameraViewState: CameraViewState_type +): NamedView | Error { + let pivot_position: [number, number, number] | null = null + let pivot_rotation: [number, number, number, number] | null = null + + if (cameraViewState.pivot_position.length === 3) { + pivot_position = [ + cameraViewState.pivot_position[0], + cameraViewState.pivot_position[1], + cameraViewState.pivot_position[2], + ] + } else { + return new Error(`invalid pivot position ${cameraViewState.pivot_position}`) + } + + if (cameraViewState.pivot_rotation.length === 4) { + pivot_rotation = [ + cameraViewState.pivot_rotation[0], + cameraViewState.pivot_rotation[1], + cameraViewState.pivot_rotation[2], + cameraViewState.pivot_rotation[3], + ] + } else { + return new Error(`invalid pivot rotation ${cameraViewState.pivot_rotation}`) + } + + // Create a new named view + const requestedView: NamedView = { + name, + eye_offset: cameraViewState.eye_offset, + fov_y: cameraViewState.fov_y, + ortho_scale_enabled: cameraViewState.ortho_scale_enabled, + ortho_scale_factor: cameraViewState.ortho_scale_factor, + world_coord_system: cameraViewState.world_coord_system, + is_ortho: cameraViewState.is_ortho, + pivot_position, + pivot_rotation, + // TS side knows about the version for the time being the version is not used for anything for now. + // Can be detected and cleaned up later if we have new version. + version: 1.0, + } + + return requestedView +} export function createNamedViewsCommand() { // Creates a command to be registered in the command bar. @@ -30,7 +110,6 @@ export function createNamedViewsCommand() { await engineCommandManager.sendSceneCommand({ type: 'modeling_cmd_req', cmd_id: uuidv4(), - // @ts-ignore TODO: Not in production yet. cmd: { type: 'default_camera_get_view' }, }) @@ -39,32 +118,43 @@ export function createNamedViewsCommand() { } if ('modeling_response' in cameraGetViewResponse.resp.data) { - // @ts-ignore TODO: Not in production yet. - const view = cameraGetViewResponse.resp.data.modeling_response.data - // Create a new named view - const requestedView: NamedView = { - name: data.name, - ...view.view, - } - // Retrieve application state for namedViews - const namedViews = { - ...settingsActor.getSnapshot().context.app.namedViews.current, - } + if (cameraGetViewResponse.success) { + if ( + cameraGetViewResponse.resp.data.modeling_response.type === + 'default_camera_get_view' + ) { + const view = + cameraGetViewResponse.resp.data.modeling_response.data + const requestedView = cameraViewStateToNamedView( + data.name, + view.view + ) + if (err(requestedView)) { + toast.error('Unable to create named view.') + return + } + // Retrieve application state for namedViews + const namedViews = { + ...settingsActor.getSnapshot().context.app.namedViews.current, + } - // Create and set namedViews application state - const uniqueUuidV4 = uuidv4() - const requestedNamedViews = { - ...namedViews, - [uniqueUuidV4]: requestedView, + // Create and set namedViews application state + const uniqueUuidV4 = uuidv4() + const requestedNamedViews = { + ...namedViews, + [uniqueUuidV4]: requestedView, + } + settingsActor.send({ + type: `set.app.namedViews`, + data: { + level: 'project', + value: requestedNamedViews, + }, + }) + + toast.success(`Named view ${requestedView.name} created.`) + } } - settingsActor.send({ - type: `set.app.namedViews`, - data: { - level: 'project', - value: requestedNamedViews, - }, - }) - toast.success(`Named view ${requestedView.name} created.`) } } invokeAndForgetCreateNamedView().catch(reportRejection) @@ -120,9 +210,10 @@ export function createNamedViewsCommand() { name: { required: true, inputType: 'options', - options: () => { + options: (commandBar, machineContext) => { + const settings = getSettings() const namedViews = { - ...settingsActor.getSnapshot().context.app.namedViews.current, + ...settings.app.namedViews.current, } const options: CommandArgumentOption[] = [] Object.entries(namedViews).forEach(([key, view]) => { @@ -164,6 +255,12 @@ export function createNamedViewsCommand() { if (viewToLoad) { // Split into the name and the engine data const { name, version, ...engineViewData } = viewToLoad + const cameraViewState = namedViewToCameraViewState(viewToLoad) + + if (err(cameraViewState)) { + toast.error(`Unable to load named view ${data.name}`) + return + } // Only send the specific camera information, the NamedView itself // is not directly compatible with the engine API @@ -171,10 +268,9 @@ export function createNamedViewsCommand() { type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { - // @ts-ignore TODO: Not in production yet. type: 'default_camera_set_view', view: { - ...engineViewData, + ...cameraViewState, }, }, }) @@ -190,6 +286,16 @@ export function createNamedViewsCommand() { }, }) + // Update the camera by triggering the callback workflow to get the camera settings + // Setting the view won't update the client side camera. + // Asking for the default camera settings after setting the view will internally sync the camera + await engineCommandManager.sendSceneCommand({ + type: 'modeling_cmd_req', + cmd_id: uuidv4(), + cmd: { + type: 'default_camera_get_settings', + }, + }) toast.success(`Named view ${name} loaded.`) } else { toast.error(`Unable to load named view, could not find named view`) @@ -202,8 +308,9 @@ export function createNamedViewsCommand() { required: true, inputType: 'options', options: () => { + const settings = getSettings() const namedViews = { - ...settingsActor.getSnapshot().context.app.namedViews.current, + ...settings.app.namedViews.current, } const options: CommandArgumentOption[] = [] Object.entries(namedViews).forEach(([key, view]) => { diff --git a/src/lib/settings/settingsUtils.ts b/src/lib/settings/settingsUtils.ts index 5b444ffd8..a3cf9f6f6 100644 --- a/src/lib/settings/settingsUtils.ts +++ b/src/lib/settings/settingsUtils.ts @@ -89,7 +89,7 @@ export function isNamedView( ] as const return namedViewKeys.every((key) => { - return namedView && namedView[key] + return namedView && key in namedView }) } diff --git a/yarn.lock b/yarn.lock index fe21ead71..d94de67f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1844,10 +1844,10 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@kittycad/lib@2.0.17": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.17.tgz#26e74f8f6ef534d1987161dfccf62ac03d3b3ed1" - integrity sha512-W4YcGvLfbeA2drjmAHDe1x6v2OXbvhB7nqtdjtKtPK39pGtMC41btcTx5MNvfz7pdnd32MIM02TCRqN6YV0RMw== +"@kittycad/lib@2.0.21": + version "2.0.21" + resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-2.0.21.tgz#b5ccb03367f4478896e5ef14221a8512d15ea4d4" + integrity sha512-JK2lAJm22GEVKX1Q57M2Pbnqzt8vmaXHec/9MDGIodnzWB36QEEs4VVVTIlJNbjoYoa+au5feakjTXUiuLu2cg== dependencies: openapi-types "^12.0.0" ts-node "^10.9.1"