Refine window corner shape support
This commit is contained in:
@@ -73,6 +73,8 @@ export {
|
||||
type DoorInteractiveState,
|
||||
type ItemInteractiveState,
|
||||
useInteractive,
|
||||
type WindowAnimationState,
|
||||
type WindowInteractiveState,
|
||||
} from './store/use-interactive'
|
||||
export { default as useLiveTransforms, type LiveTransform } from './store/use-live-transforms'
|
||||
export { clearSceneHistory, default as useScene } from './store/use-scene'
|
||||
|
||||
@@ -82,7 +82,7 @@ export {
|
||||
getWallSurfaceMaterialSignature,
|
||||
WallNode,
|
||||
} from './nodes/wall'
|
||||
export { WindowNode } from './nodes/window'
|
||||
export { WindowNode, WindowType } from './nodes/window'
|
||||
export { ZoneNode } from './nodes/zone'
|
||||
export type { AnyNodeId, AnyNodeType } from './types'
|
||||
// Union types
|
||||
|
||||
@@ -3,6 +3,20 @@ import { z } from 'zod'
|
||||
import { BaseNode, nodeType, objectId } from '../base'
|
||||
import { MaterialSchema } from '../material'
|
||||
|
||||
export const WindowType = z.enum([
|
||||
'fixed',
|
||||
'sliding',
|
||||
'casement',
|
||||
'awning',
|
||||
'hopper',
|
||||
'single-hung',
|
||||
'double-hung',
|
||||
'bay',
|
||||
'bow',
|
||||
'louvered',
|
||||
])
|
||||
export type WindowType = z.infer<typeof WindowType>
|
||||
|
||||
export const WindowNode = BaseNode.extend({
|
||||
id: objectId('window'),
|
||||
type: nodeType('window'),
|
||||
@@ -21,6 +35,13 @@ export const WindowNode = BaseNode.extend({
|
||||
|
||||
// Opening mode - when set to "opening", the window is only a shaped cutout
|
||||
openingKind: z.enum(['window', 'opening']).default('window'),
|
||||
|
||||
// Window family
|
||||
windowType: WindowType.default('fixed'),
|
||||
operationState: z.number().min(0).max(1).default(0),
|
||||
awningDirection: z.enum(['up', 'down']).default('up'),
|
||||
casementStyle: z.enum(['single', 'french']).default('single'),
|
||||
hingesSide: z.enum(['left', 'right']).default('left'),
|
||||
openingShape: z.enum(['rectangle', 'rounded', 'arch']).default('rectangle'),
|
||||
openingRadiusMode: z.enum(['all', 'individual']).default('all'),
|
||||
openingCornerRadii: z
|
||||
@@ -50,6 +71,7 @@ export const WindowNode = BaseNode.extend({
|
||||
}).describe(dedent`Window node - a parametric window placed on a wall
|
||||
- position: center of the window in wall-local coordinate system
|
||||
- width/height: overall outer dimensions
|
||||
- windowType: explicit window family, defaulting old windows to fixed
|
||||
- frameThickness: width of the frame members
|
||||
- frameDepth: how deep the frame sits within the wall
|
||||
- columnRatios/rowRatios: pane division ratios
|
||||
|
||||
@@ -26,10 +26,25 @@ export type DoorAnimationState = {
|
||||
persist: boolean
|
||||
}
|
||||
|
||||
export type WindowInteractiveState = {
|
||||
operationState?: number
|
||||
}
|
||||
|
||||
export type WindowAnimationState = {
|
||||
field: keyof WindowInteractiveState
|
||||
from: number
|
||||
to: number
|
||||
startedAt: number | null
|
||||
durationMs: number
|
||||
persist: boolean
|
||||
}
|
||||
|
||||
type InteractiveStore = {
|
||||
items: Record<AnyNodeId, ItemInteractiveState>
|
||||
doors: Record<AnyNodeId, DoorInteractiveState>
|
||||
doorAnimations: Record<AnyNodeId, DoorAnimationState>
|
||||
windows: Record<AnyNodeId, WindowInteractiveState>
|
||||
windowAnimations: Record<AnyNodeId, WindowAnimationState>
|
||||
|
||||
/** Initialize a node's interactive state from its asset definition (idempotent) */
|
||||
initItem: (itemId: AnyNodeId, interactive: Interactive) => void
|
||||
@@ -51,6 +66,18 @@ type InteractiveStore = {
|
||||
|
||||
/** Cancel a queued door animation */
|
||||
cancelDoorAnimation: (doorId: AnyNodeId) => void
|
||||
|
||||
/** Set transient window open state without committing it to the scene node */
|
||||
setWindowOpenState: (windowId: AnyNodeId, value: WindowInteractiveState) => void
|
||||
|
||||
/** Clear transient window open state */
|
||||
removeWindowOpenState: (windowId: AnyNodeId) => void
|
||||
|
||||
/** Queue a window animation for the viewer frame loop */
|
||||
startWindowAnimation: (windowId: AnyNodeId, value: WindowAnimationState) => void
|
||||
|
||||
/** Cancel a queued window animation */
|
||||
cancelWindowAnimation: (windowId: AnyNodeId) => void
|
||||
}
|
||||
|
||||
const defaultControlValue = (interactive: Interactive, index: number): ControlValue => {
|
||||
@@ -70,6 +97,8 @@ export const useInteractive = create<InteractiveStore>((set, get) => ({
|
||||
items: {},
|
||||
doors: {},
|
||||
doorAnimations: {},
|
||||
windows: {},
|
||||
windowAnimations: {},
|
||||
|
||||
initItem: (itemId, interactive) => {
|
||||
const { controls } = interactive
|
||||
@@ -139,4 +168,39 @@ export const useInteractive = create<InteractiveStore>((set, get) => ({
|
||||
return { doorAnimations: rest }
|
||||
})
|
||||
},
|
||||
|
||||
setWindowOpenState: (windowId, value) => {
|
||||
set((state) => ({
|
||||
windows: {
|
||||
...state.windows,
|
||||
[windowId]: {
|
||||
...state.windows[windowId],
|
||||
...value,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
removeWindowOpenState: (windowId) => {
|
||||
set((state) => {
|
||||
const { [windowId]: _, ...rest } = state.windows
|
||||
return { windows: rest }
|
||||
})
|
||||
},
|
||||
|
||||
startWindowAnimation: (windowId, value) => {
|
||||
set((state) => ({
|
||||
windowAnimations: {
|
||||
...state.windowAnimations,
|
||||
[windowId]: value,
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
cancelWindowAnimation: (windowId) => {
|
||||
set((state) => {
|
||||
const { [windowId]: _, ...rest } = state.windowAnimations
|
||||
return { windowAnimations: rest }
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user