Feature: Release named views to all users (#5814)
* chore: cleanup to get named views released! * fix: fixed gizmo, client side camera sync and remove DEV flag * yarp * chore: implementing E2E tests for creating a named view * fix: cleaning up and commenting E2E tests for named views * fix: we did it bois, the skip ceral i zation bricked my E2E test :( * fix: auto formatter * fix: snapshot uuid matching because rust will randomly generate thme * fix: auto fmt * fix: trying to resolve typescript issues * fix: handling NamedView vs CameraViewState type checking * fix: no idea I just mapped export to 3d export because we have no 2d export yet... * fix: random file I wrote because my editor was too slow * fix: git merge did not do what I wanted * A snapshot a day keeps the bugs away! 📷🐛 * fix: linter errors * A snapshot a day keeps the bugs away! 📷🐛 --------- Co-authored-by: 49fl <ircsurfer33@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
This commit is contained in:
292
e2e/playwright/named-views.spec.ts
Normal file
292
e2e/playwright/named-views.spec.ts
Normal file
@ -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<string, string> = 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')
|
||||
})
|
||||
})
|
@ -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
|
@ -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
|
@ -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
|
@ -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<Configuration>) {
|
||||
export function tomlToSettings(toml: string): DeepPartial<Configuration> {
|
||||
return TOML.parse(toml)
|
||||
}
|
||||
|
||||
export function tomlToPerProjectSettings(
|
||||
toml: string
|
||||
): DeepPartial<ProjectConfiguration> {
|
||||
return TOML.parse(toml)
|
||||
}
|
||||
|
||||
export function perProjectsettingsToToml(
|
||||
settings: DeepPartial<ProjectConfiguration>
|
||||
) {
|
||||
return TOML.stringify(settings as any)
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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")]
|
||||
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -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> = T extends { storage: infer U } ? U : never
|
||||
type StorageUnion = ExtractStorageTypes<OutputFormat>
|
||||
|
@ -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<any>[] = []
|
||||
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<any>[] = []
|
||||
Object.entries(namedViews).forEach(([key, view]) => {
|
||||
|
@ -89,7 +89,7 @@ export function isNamedView(
|
||||
] as const
|
||||
|
||||
return namedViewKeys.every((key) => {
|
||||
return namedView && namedView[key]
|
||||
return namedView && key in namedView
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
Reference in New Issue
Block a user