Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Named views #5532

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f752215
feature: building skeleton for adding a viewpoint in frontend as well…
nadr0 Jan 15, 2025
623de50
fix: merged main
nadr0 Jan 16, 2025
227e31a
chore: named views loaded into memory
nadr0 Jan 16, 2025
c574274
fix: testing code
nadr0 Jan 16, 2025
ec883c4
chore: saving off progress, skeleton for listing and deleting named v…
nadr0 Jan 16, 2025
28d740b
fix: fixed state stale dereferencing issue
nadr0 Jan 17, 2025
35affa9
feat: initial skeleton for loading view points
nadr0 Jan 17, 2025
0094bc9
fix: pushing bug
nadr0 Jan 17, 2025
ab95c72
fix: saving off progress
nadr0 Jan 29, 2025
421189e
fix: merge main
nadr0 Feb 25, 2025
154d145
fix: trying to update to main?
nadr0 Feb 25, 2025
470a272
fix: main fixes, API fixes
nadr0 Feb 25, 2025
129d505
fix: what is happening
nadr0 Feb 26, 2025
a7e2660
fix: ope
nadr0 Feb 26, 2025
18e3374
fix: implemented default values on serde
nadr0 Feb 26, 2025
81bbf4a
fix: pushing working dev code... need to clean it up
nadr0 Feb 26, 2025
36fdbee
feature: adding no results found on filteroptions within an options i…
nadr0 Feb 26, 2025
1b288b7
fix: initial PR cleanup pass of junky code
nadr0 Feb 26, 2025
b352700
fix: addressing comments in initial pass
nadr0 Feb 27, 2025
27bbc4f
fix: addressing PR comments
nadr0 Feb 27, 2025
324db3c
fix: moved modeling.namedViews to app.namedViews as per request
nadr0 Feb 27, 2025
b10f559
fix: _id and _version are now id and version.
nadr0 Feb 27, 2025
95d65e4
fix: python codespell, perspective
nadr0 Feb 27, 2025
bfd2bb6
fix: cargo fmt
nadr0 Feb 27, 2025
091b3e7
fix: updating description of the named view commands
nadr0 Feb 27, 2025
2655d14
fix: removing testing code
nadr0 Feb 27, 2025
c06446b
fix: feature flag this to DEV only
nadr0 Feb 27, 2025
8138ff2
fix: ts ignore for production engine api
nadr0 Feb 27, 2025
998e651
fix: deep parital fights arrays and objects within settings, doing a …
nadr0 Feb 27, 2025
6f56d17
fix: auto fixes
nadr0 Feb 27, 2025
e1558b8
Merge branch 'main' into nadro/gh-4217/named-views
lf94 Feb 28, 2025
b70d751
Remove unnecessary alias
lf94 Feb 28, 2025
ab24f92
Reword toast messages (more consistency)
lf94 Feb 28, 2025
889a7a5
fmt
lf94 Feb 28, 2025
99f983a
cargo clippy
lf94 Feb 28, 2025
c119de8
Fix Set appearance flakes
lf94 Feb 28, 2025
32243dd
cargo test
lf94 Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions e2e/playwright/point-click.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2864,7 +2864,7 @@ extrude001 = extrude(profile001, length = 100)

// One dumb hardcoded screen pixel value
const testPoint = { x: 500, y: 250 }
const initialColor: [number, number, number] = [135, 135, 135]
const initialColor: [number, number, number] = [123, 123, 123]

await test.step(`Confirm extrude exists with default appearance`, async () => {
await toolbar.closePane('code')
Expand Down Expand Up @@ -2905,7 +2905,7 @@ extrude001 = extrude(profile001, length = 100)
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await scene.expectPixelColor(shapeColor, testPoint, 40)
await scene.expectPixelColor(shapeColor, testPoint, 10)
await toolbar.openPane('code')
if (hex === 'default') {
const anyAppearanceDeclaration = `|> appearance(`
Expand All @@ -2931,9 +2931,9 @@ extrude001 = extrude(profile001, length = 100)
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
await setApperanceAndCheck('Dark Grey', '#080808', [0x33, 0x33, 0x33])
await setApperanceAndCheck('Light Grey', '#D3D3D3', [176, 176, 176])
await setApperanceAndCheck('White', '#FFFFFF', [184, 184, 184])
await setApperanceAndCheck(
'Default (clear appearance)',
'default',
Expand Down
2 changes: 2 additions & 0 deletions src/clientSideScene/CameraControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,14 @@ export class CameraControls {
camSettings.up.y,
camSettings.up.z
)

this.camera.quaternion.set(
orientation.x,
orientation.y,
orientation.z,
orientation.w
)

this.camera.up.copy(newUp)
this.camera.updateProjectionMatrix()
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
Expand Down
66 changes: 36 additions & 30 deletions src/components/CommandBar/CommandArgOptionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,37 +168,43 @@ function CommandArgOptionInput({
autoFocus
/>
</div>
<Combobox.Options
static
className="overflow-y-auto max-h-96 cursor-pointer"
onMouseDown={() => {
setShouldSubmitOnChange(true)
}}
>
{filteredOptions?.map((option) => (
<Combobox.Option
key={option.name}
value={option}
disabled={option.disabled}
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
>
<p
className={`flex-grow ${
(option.disabled &&
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
''
}`}
{filteredOptions?.length ? (
<Combobox.Options
static
className="overflow-y-auto max-h-96 cursor-pointer"
onMouseDown={() => {
setShouldSubmitOnChange(true)
}}
>
{filteredOptions?.map((option) => (
<Combobox.Option
key={option.name}
value={option}
disabled={option.disabled}
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
>
{option.name}
</p>
{option.value === currentOption?.value && (
<small className="text-chalkboard-70 dark:text-chalkboard-50">
current
</small>
)}
</Combobox.Option>
))}
</Combobox.Options>
<p
className={`flex-grow ${
(option.disabled &&
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
''
}`}
>
{option.name}
</p>
{option.value === currentOption?.value && (
<small className="text-chalkboard-70 dark:text-chalkboard-50">
current
</small>
)}
</Combobox.Option>
))}
</Combobox.Options>
) : (
<p className="px-4 pt-2 text-chalkboard-60 dark:text-chalkboard-50">
No results found
</p>
)}
</Combobox>
</form>
)
Expand Down
34 changes: 34 additions & 0 deletions src/components/FileMachineProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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,
Expand Down Expand Up @@ -32,6 +33,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { settingsActor, useSettings } from 'machines/appMachine'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
import { useToken } from 'machines/appMachine'
import { createNamedViewsCommand } from 'lib/commandBarConfigs/namedViewsConfig'

type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
Expand All @@ -58,6 +60,38 @@ export const FileMachineProvider = ({
[]
)

useEffect(() => {
// TODO: Engine feature is not deployed
if (DEV) {
const {
createNamedViewCommand,
deleteNamedViewCommand,
loadNamedViewCommand,
} = createNamedViewsCommand()

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: 'Remove commands',
data: {
commands,
},
})
}
}
}, [])

// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
// This will register the commands to route to Telemetry, Home, and Settings.
useEffect(() => {
Expand Down
220 changes: 220 additions & 0 deletions src/lib/commandBarConfigs/namedViewsConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { NamedView } from 'wasm-lib/kcl/bindings/NamedView'
import { Command } 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'

export function createNamedViewsCommand() {
// Creates a command to be registered in the command bar.
// The createNamedViewsCommand will prompt the user for a name and then
// hit the engine for the camera properties and write them back to disk
// in project.toml.
const createNamedViewCommand: Command = {
name: 'Create named view',
displayName: `Create named view`,
description:
'Saves a named view based on your current view to load again later',
groupId: 'namedViews',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
const invokeAndForgetCreateNamedView = async () => {
if (!data) {
return toast.error('Unable to create named view, missing name')
}

// Retrieve camera view state from the engine
const cameraGetViewResponse =
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
// @ts-ignore TODO: Not in production yet.
cmd: { type: 'default_camera_get_view' },
})

if (!(cameraGetViewResponse && 'resp' in cameraGetViewResponse)) {
return toast.error('Unable to create named view, websocket failure')
}

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,
]

// Create and set namedViews application state
const requestedNamedViews = [...namedViews, requestedView]
settingsActor.send({
type: `set.app.namedViews`,
data: {
level: 'project',
value: requestedNamedViews,
},
})
toast.success(`Named view ${requestedView.name} created.`)
}
}
invokeAndForgetCreateNamedView().catch(reportRejection)
},
args: {
name: {
required: true,
inputType: 'string',
},
},
}

// Given a named view selection from the command bar, this will
// find it in the setting state, remove it from the array and
// rewrite the project.toml settings to disk to delete the named view
const deleteNamedViewCommand: Command = {
name: 'Delete named view',
displayName: `Delete named view`,
description: 'Deletes the named view from settings',
groupId: 'namedViews',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
if (!data) {
return toast.error('Unable to delete named view, missing name')
}
const nameToDelete = data.name
// Retrieve application state for namedViews
const namedViews = [
...settingsActor.getSnapshot().context.app.namedViews.current,
]

// Find the named view in the array
const indexToDelete = namedViews.findIndex(
(view) => view.name === nameToDelete
)
if (indexToDelete >= 0) {
const name = namedViews[indexToDelete].name

// Remove the named view from the array
namedViews.splice(indexToDelete, 1)

// Update global state with the new computed state
settingsActor.send({
type: `set.app.namedViews`,
data: {
level: 'project',
value: namedViews,
},
})
toast.success(`Named view ${name} removed.`)
} else {
toast.error(`Unable to delete, could not find the named view`)
}
},
args: {
name: {
required: true,
inputType: 'options',
options: () => {
const namedViews = [
...settingsActor.getSnapshot().context.app.namedViews.current,
]
return namedViews.map((view, index) => {
return {
name: view.name,
isCurrent: false,
value: view.name,
}
})
},
},
},
}

// Read the named view from settings state and pass that camera information to the engine command to set the view of the engine camera
const loadNamedViewCommand: Command = {
name: 'Load named view',
displayName: `Load named view`,
description: 'Loads your camera to the named view',
groupId: 'namedViews',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
const invokeAndForgetLoadNamedView = async () => {
if (!data) {
return toast.error('Unable to load named view')
}

// Retrieve application state for namedViews
const namedViews = [
...settingsActor.getSnapshot().context.app.namedViews.current,
]
const _idToLoad = data.name
const viewToLoad = namedViews.find((view) => view.id === _idToLoad)
if (viewToLoad) {
// Split into the name and the engine data
const { name, id, version, ...engineViewData } = viewToLoad

// Only send the specific camera information, the NamedView itself
// is not directly compatible with the engine API
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
// @ts-ignore TODO: Not in production yet.
type: 'default_camera_set_view',
view: {
...engineViewData,
},
},
})

const isPerpsective = !engineViewData.is_ortho

// Update the GUI for orthographic and projection
settingsActor.send({
type: 'set.modeling.cameraProjection',
data: {
level: 'user',
value: isPerpsective ? 'perspective' : 'orthographic',
},
})

toast.success(`Named view ${name} loaded.`)
} else {
toast.error(`Unable to load named view, could not find named view`)
}
}
invokeAndForgetLoadNamedView().catch(reportRejection)
},
args: {
name: {
required: true,
inputType: 'options',
options: () => {
const namedViews = [
...settingsActor.getSnapshot().context.app.namedViews.current,
]
return namedViews.map((view) => {
return {
name: view.name,
isCurrent: false,
value: view.id,
}
})
},
},
},
}

return {
createNamedViewCommand,
deleteNamedViewCommand,
loadNamedViewCommand,
}
}
Loading
Loading