* add first version of DefaultPlanes to FeatureTreePane * fix lint issues * don't show default planes UI in sketch mode * lint * toggling default planes: implementation in xstate * revert malformed modelingMachine.ts * lint * save and restore default plane visibility when returning to modeling mode * fmt * tsc * introduce new cleanup state with actor when exiting sketch mode * temp remove restore default plane visibility - causes error on starting up a project * set selection filter after executeAst - this is a wip hacky fix * remove unused early return: this also caused plane selection to only work with double click * lint * no need to set selection filter to curves only, we want faces to be selectable in modeling mode, even though this means default planes are also selectable * tightening types for visibility map * lint * cleanups * fix border issue when visibility toggle is not active and props.visible === true * ui updates on FeatureTreePane/default planes * no pointer cursor for unselectable default planes * show default planes initially even for non-empty projects * dont show default planes initially when project is not empty * fix test: Only show axis planes when there are no errors * fixes for sketch tests * better initialize for planes * lint * fix uneccessary 'reset camera position' in sketch entry * revert hiding/showing content depending on artifact graph for tests * only show default planes when there are no errors * disable Restore default plane visibility, was causing temporary flashing of default planes when exiting sketch mode * Always show default plane visibility toggles, regardless of being on/off * revert modelingMachine to original idle states to avoid 'zoom_to_fit' test regression - probably racing condition * fmt
This commit is contained in:
@ -19,7 +19,6 @@ import { err, reportRejection, trap } from '@src/lib/trap'
|
|||||||
import type { IndexLoaderData } from '@src/lib/types'
|
import type { IndexLoaderData } from '@src/lib/types'
|
||||||
import { uuidv4 } from '@src/lib/utils'
|
import { uuidv4 } from '@src/lib/utils'
|
||||||
import { engineStreamActor, useSettings } from '@src/lib/singletons'
|
import { engineStreamActor, useSettings } from '@src/lib/singletons'
|
||||||
import { useCommandBarState } from '@src/lib/singletons'
|
|
||||||
import {
|
import {
|
||||||
EngineStreamState,
|
EngineStreamState,
|
||||||
EngineStreamTransition,
|
EngineStreamTransition,
|
||||||
@ -58,8 +57,6 @@ export const EngineStream = (props: {
|
|||||||
const { state: modelingMachineState, send: modelingMachineActorSend } =
|
const { state: modelingMachineState, send: modelingMachineActorSend } =
|
||||||
useModelingContext()
|
useModelingContext()
|
||||||
|
|
||||||
const commandBarState = useCommandBarState()
|
|
||||||
|
|
||||||
const streamIdleMode = settings.app.streamIdleMode.current
|
const streamIdleMode = settings.app.streamIdleMode.current
|
||||||
|
|
||||||
const startOrReconfigureEngine = () => {
|
const startOrReconfigureEngine = () => {
|
||||||
@ -332,15 +329,7 @@ export const EngineStream = (props: {
|
|||||||
if (!engineStreamState.context.videoRef.current) return
|
if (!engineStreamState.context.videoRef.current) return
|
||||||
// If we're in sketch mode, don't send a engine-side select event
|
// If we're in sketch mode, don't send a engine-side select event
|
||||||
if (modelingMachineState.matches('Sketch')) return
|
if (modelingMachineState.matches('Sketch')) return
|
||||||
// Only respect default plane selection if we're on a selection command argument
|
|
||||||
if (
|
|
||||||
modelingMachineState.matches({ idle: 'showPlanes' }) &&
|
|
||||||
!(
|
|
||||||
commandBarState.matches('Gathering arguments') &&
|
|
||||||
commandBarState.context.currentArgument?.inputType === 'selection'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
// If we're mousing up from a camera drag, don't send a select event
|
// If we're mousing up from a camera drag, don't send a select event
|
||||||
if (sceneInfra.camControls.wasDragging === true) return
|
if (sceneInfra.camControls.wasDragging === true) return
|
||||||
|
|
||||||
@ -361,7 +350,6 @@ export const EngineStream = (props: {
|
|||||||
!isNetworkOkay ||
|
!isNetworkOkay ||
|
||||||
!engineStreamState.context.videoRef.current ||
|
!engineStreamState.context.videoRef.current ||
|
||||||
modelingMachineState.matches('Sketch') ||
|
modelingMachineState.matches('Sketch') ||
|
||||||
modelingMachineState.matches({ idle: 'showPlanes' }) ||
|
|
||||||
sceneInfra.camControls.wasDragging === true ||
|
sceneInfra.camControls.wasDragging === true ||
|
||||||
!btnName(e.nativeEvent).left
|
!btnName(e.nativeEvent).left
|
||||||
) {
|
) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { Diagnostic } from '@codemirror/lint'
|
import type { Diagnostic } from '@codemirror/lint'
|
||||||
import { useMachine, useSelector } from '@xstate/react'
|
import { useMachine, useSelector } from '@xstate/react'
|
||||||
import type { ComponentProps } from 'react'
|
import type { ComponentProps } from 'react'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import type { Actor, Prop } from 'xstate'
|
import type { Actor, Prop } from 'xstate'
|
||||||
|
|
||||||
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
||||||
@ -23,7 +23,7 @@ import {
|
|||||||
getOperationLabel,
|
getOperationLabel,
|
||||||
stdLibMap,
|
stdLibMap,
|
||||||
} from '@src/lib/operations'
|
} from '@src/lib/operations'
|
||||||
import { editorManager, kclManager } from '@src/lib/singletons'
|
import { editorManager, kclManager, rustContext } from '@src/lib/singletons'
|
||||||
import {
|
import {
|
||||||
featureTreeMachine,
|
featureTreeMachine,
|
||||||
featureTreeMachineDefaultContext,
|
featureTreeMachineDefaultContext,
|
||||||
@ -160,6 +160,7 @@ export const FeatureTreePane = () => {
|
|||||||
<Loading className="h-full">Building feature tree...</Loading>
|
<Loading className="h-full">Building feature tree...</Loading>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{!modelingState.matches('Sketch') && <DefaultPlanes />}
|
||||||
{parseErrors.length > 0 && (
|
{parseErrors.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute inset-0 rounded-lg p-2 ${
|
className={`absolute inset-0 rounded-lg p-2 ${
|
||||||
@ -204,41 +205,27 @@ export const FeatureTreePane = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const visibilityMap = new Map<string, boolean>()
|
|
||||||
|
|
||||||
interface VisibilityToggleProps {
|
interface VisibilityToggleProps {
|
||||||
entityId: string
|
visible: boolean
|
||||||
initialVisibility: boolean
|
onVisibilityChange: () => unknown
|
||||||
onVisibilityChange?: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A button that toggles the visibility of an entity
|
* A button that toggles the visibility of an entity
|
||||||
* tied to an artifact in the feature tree.
|
* tied to an artifact in the feature tree.
|
||||||
* TODO: this is unimplemented and will be used for
|
* For now just used for default planes.
|
||||||
* default planes after we fix them and add them to the artifact graph / feature tree
|
|
||||||
*/
|
*/
|
||||||
const VisibilityToggle = (props: VisibilityToggleProps) => {
|
const VisibilityToggle = (props: VisibilityToggleProps) => {
|
||||||
const [visible, setVisible] = useState(props.initialVisibility)
|
const visible = props.visible
|
||||||
|
const handleToggleVisible = useCallback(() => {
|
||||||
function handleToggleVisible() {
|
props.onVisibilityChange()
|
||||||
setVisible(!visible)
|
}, [props.onVisibilityChange])
|
||||||
visibilityMap.set(props.entityId, !visible)
|
|
||||||
props.onVisibilityChange?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button onClick={handleToggleVisible} className="p-0 m-0">
|
||||||
onClick={handleToggleVisible}
|
|
||||||
className="border-transparent p-0 m-0"
|
|
||||||
>
|
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name={visible ? 'eyeOpen' : 'eyeCrossedOut'}
|
name={visible ? 'eyeOpen' : 'eyeCrossedOut'}
|
||||||
className={`w-5 h-5 ${
|
className="w-5 h-5"
|
||||||
visible
|
|
||||||
? 'hidden group-hover/item:block group-focus-within/item:block'
|
|
||||||
: 'text-chalkboard-50'
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@ -256,6 +243,7 @@ const OperationItemWrapper = ({
|
|||||||
menuItems,
|
menuItems,
|
||||||
errors,
|
errors,
|
||||||
className,
|
className,
|
||||||
|
selectable = true,
|
||||||
...props
|
...props
|
||||||
}: React.HTMLAttributes<HTMLButtonElement> & {
|
}: React.HTMLAttributes<HTMLButtonElement> & {
|
||||||
icon: CustomIconName
|
icon: CustomIconName
|
||||||
@ -263,17 +251,18 @@ const OperationItemWrapper = ({
|
|||||||
visibilityToggle?: VisibilityToggleProps
|
visibilityToggle?: VisibilityToggleProps
|
||||||
menuItems?: ComponentProps<typeof ContextMenu>['items']
|
menuItems?: ComponentProps<typeof ContextMenu>['items']
|
||||||
errors?: Diagnostic[]
|
errors?: Diagnostic[]
|
||||||
|
selectable?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const menuRef = useRef<HTMLDivElement>(null)
|
const menuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className="flex select-none items-center group/item my-0 py-0.5 px-1 focus-within:bg-primary/10 hover:bg-primary/5"
|
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
className={`reset flex-1 flex items-center gap-2 border-transparent dark:border-transparent text-left text-base ${className}`}
|
className={`reset flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`}
|
||||||
>
|
>
|
||||||
<CustomIcon name={icon} className="w-5 h-5 block" />
|
<CustomIcon name={icon} className="w-5 h-5 block" />
|
||||||
{name}
|
{name}
|
||||||
@ -514,3 +503,40 @@ const OperationItem = (props: {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DefaultPlanes = () => {
|
||||||
|
const { state: modelingState, send } = useModelingContext()
|
||||||
|
|
||||||
|
const defaultPlanes = rustContext.defaultPlanes
|
||||||
|
if (!defaultPlanes) return null
|
||||||
|
|
||||||
|
const planes = [
|
||||||
|
{ name: 'Front plane', id: defaultPlanes.xz, key: 'xz' },
|
||||||
|
{ name: 'Top plane', id: defaultPlanes.xy, key: 'xy' },
|
||||||
|
{ name: 'Side plane', id: defaultPlanes.yz, key: 'yz' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-2">
|
||||||
|
{planes.map((plane) => (
|
||||||
|
<OperationItemWrapper
|
||||||
|
key={plane.key}
|
||||||
|
icon={'plane'}
|
||||||
|
name={plane.name}
|
||||||
|
selectable={false}
|
||||||
|
visibilityToggle={{
|
||||||
|
visible: modelingState.context.defaultPlaneVisibility[plane.key],
|
||||||
|
onVisibilityChange: () => {
|
||||||
|
send({
|
||||||
|
type: 'Toggle default plane visibility',
|
||||||
|
planeId: plane.id,
|
||||||
|
planeKey: plane.key,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div className="h-px bg-chalkboard-50/20 my-2" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -48,6 +48,7 @@ import { jsAppSettings } from '@src/lib/settings/settingsUtils'
|
|||||||
|
|
||||||
import { err, reportRejection } from '@src/lib/trap'
|
import { err, reportRejection } from '@src/lib/trap'
|
||||||
import { deferExecution, uuidv4 } from '@src/lib/utils'
|
import { deferExecution, uuidv4 } from '@src/lib/utils'
|
||||||
|
import type { PlaneVisibilityMap } from '@src/machines/modelingMachine'
|
||||||
|
|
||||||
interface ExecuteArgs {
|
interface ExecuteArgs {
|
||||||
ast?: Node<Program>
|
ast?: Node<Program>
|
||||||
@ -70,7 +71,6 @@ export class KclManager {
|
|||||||
*/
|
*/
|
||||||
artifactGraph: ArtifactGraph = new Map()
|
artifactGraph: ArtifactGraph = new Map()
|
||||||
artifactIndex: ArtifactIndex = []
|
artifactIndex: ArtifactIndex = []
|
||||||
defaultPlanesShown: boolean = false
|
|
||||||
|
|
||||||
private _ast: Node<Program> = {
|
private _ast: Node<Program> = {
|
||||||
body: [],
|
body: [],
|
||||||
@ -354,6 +354,15 @@ export class KclManager {
|
|||||||
})
|
})
|
||||||
}, 200)(null)
|
}, 200)(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the 'artifact graph initialized' event for modelingMachine, only once, when default planes are also initialized.
|
||||||
|
deferExecution((a?: null) => {
|
||||||
|
if (this.defaultPlanes) {
|
||||||
|
this.engineCommandManager.modelingSend({
|
||||||
|
type: 'Artifact graph initialized',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 200)(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
async safeParse(code: string): Promise<Node<Program> | null> {
|
async safeParse(code: string): Promise<Node<Program> | null> {
|
||||||
@ -702,6 +711,19 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
return Promise.all(thePromises)
|
return Promise.all(thePromises)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPlaneVisibilityByKey(
|
||||||
|
planeKey: keyof PlaneVisibilityMap,
|
||||||
|
visible: boolean
|
||||||
|
) {
|
||||||
|
const planeId = this.defaultPlanes?.[planeKey]
|
||||||
|
if (!planeId) {
|
||||||
|
console.warn(`Plane ${planeKey} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return this.engineCommandManager.setPlaneHidden(planeId, !visible)
|
||||||
|
}
|
||||||
|
|
||||||
/** TODO: this function is hiding unawaited asynchronous work */
|
/** TODO: this function is hiding unawaited asynchronous work */
|
||||||
defaultSelectionFilter(selectionsToRestore?: Selections) {
|
defaultSelectionFilter(selectionsToRestore?: Selections) {
|
||||||
setSelectionFilterToDefault(this.engineCommandManager, selectionsToRestore)
|
setSelectionFilterToDefault(this.engineCommandManager, selectionsToRestore)
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user