|
|
|
|
@@ -3,9 +3,11 @@ import type {
|
|
|
|
|
BitBybitWebCadFacade,
|
|
|
|
|
CommandState,
|
|
|
|
|
DocumentSnapshot,
|
|
|
|
|
Diagnostic,
|
|
|
|
|
ExecuteCommandInput,
|
|
|
|
|
FacadeEvent,
|
|
|
|
|
FacadeListener,
|
|
|
|
|
FacadeRequestContext,
|
|
|
|
|
FacadeState,
|
|
|
|
|
ModelTreeItem,
|
|
|
|
|
TaskSnapshot,
|
|
|
|
|
@@ -27,41 +29,49 @@ const createDocument = (label = 'Pump Housing'): DocumentSnapshot => ({
|
|
|
|
|
id: 'doc-pump-housing', label, version: 18, dirty: true, readOnly: false, units: 'mm', tree: initialTree.map((item) => ({ ...item, children: item.children ? [...item.children] : undefined })),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const commandState = (commandId: string, activeWorkbench: WorkbenchId): CommandState => {
|
|
|
|
|
const selectionRequired = new Set(['pad', 'pocket', 'revolution', 'fillet', 'chamfer', 'hole', 'linear-pattern', 'polar-pattern', 'measure-distance', 'measure-angle', 'measure-area'])
|
|
|
|
|
|
|
|
|
|
const commandState = (commandId: string, activeWorkbench: WorkbenchId, selectedObjectId: string): CommandState => {
|
|
|
|
|
const known = Object.values(workbenchDefinitions).some((definition) => definition.groups.some((group) => group.commands.some((command) => command.id === commandId)))
|
|
|
|
|
if (!known) return { id: commandId, status: 'disabled', reason: 'Command is not registered in the active manifest.' }
|
|
|
|
|
if (commandId === 'pad' && activeWorkbench !== 'Part Design') return { id: commandId, status: 'disabled', reason: 'Switch to Part Design to use Pad.' }
|
|
|
|
|
if (selectionRequired.has(commandId) && !selectedObjectId) return { id: commandId, status: 'disabled', reason: 'Select a compatible object or sub-shape first.' }
|
|
|
|
|
return { id: commandId, status: 'enabled' }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createMockFacade(): BitBybitWebCadFacade {
|
|
|
|
|
let state: FacadeState = { apiVersion: '0.1', activeWorkbench: 'Part Design', selectedObjectId: 'pad', document: createDocument(), task: null, lastNotice: '' }
|
|
|
|
|
let state: FacadeState = { apiVersion: '0.1', activeWorkbench: 'Part Design', selectedObjectId: 'pad', document: createDocument(), task: null, lastNotice: '', diagnostics: [] }
|
|
|
|
|
const listeners = new Set<FacadeListener>()
|
|
|
|
|
let requestSequence = 0
|
|
|
|
|
|
|
|
|
|
const emit = (event: FacadeEvent) => listeners.forEach((listener) => listener(event))
|
|
|
|
|
const emitState = () => emit({ type: 'state.changed', state: getState() })
|
|
|
|
|
const getState = () => ({ ...state, document: { ...state.document, tree: state.document.tree.map((item) => ({ ...item, children: item.children ? [...item.children] : undefined })) }, task: state.task ? { ...state.task, draft: { ...state.task.draft } } : null })
|
|
|
|
|
const getState = () => ({ ...state, diagnostics: state.diagnostics.map((diagnostic) => ({ ...diagnostic })), document: { ...state.document, tree: state.document.tree.map((item) => ({ ...item, children: item.children ? [...item.children] : undefined })) }, task: state.task ? { ...state.task, draft: { ...state.task.draft } } : null })
|
|
|
|
|
const notify = (message: string) => { state = { ...state, lastNotice: message }; emit({ type: 'notice', message }); emitState() }
|
|
|
|
|
const setActive = (id: WorkbenchId) => { state = { ...state, activeWorkbench: id }; emitState(); notify(`${id} workbench loaded`) }
|
|
|
|
|
const select = (objectId: string) => { state = { ...state, selectedObjectId: objectId }; emitState() }
|
|
|
|
|
const beginTask = (commandId: string, draft: Record<string, unknown> = {}) => { const task: TaskSnapshot = { id: `task-${++requestSequence}`, commandId, title: workbenchDefinitions[state.activeWorkbench].taskTitle, status: 'preview', draft }; state = { ...state, task }; emitState(); return task }
|
|
|
|
|
const execute = ({ commandId, payload }: ExecuteCommandInput) => {
|
|
|
|
|
const requestId = `req-${++requestSequence}`
|
|
|
|
|
const status = commandState(commandId, state.activeWorkbench)
|
|
|
|
|
if (status.status === 'disabled') { emit({ type: 'command.failed', commandId, requestId, message: status.reason }); notify(status.reason || 'Command is disabled'); return requestId }
|
|
|
|
|
emit({ type: 'command.started', commandId, requestId })
|
|
|
|
|
const context: FacadeRequestContext = { apiVersion: state.apiVersion, requestId, documentId: state.document.id, documentVersion: state.document.version, workbench: state.activeWorkbench }
|
|
|
|
|
const status = commandState(commandId, state.activeWorkbench, state.selectedObjectId)
|
|
|
|
|
if (status.status === 'disabled') {
|
|
|
|
|
const diagnostic: Diagnostic = { id: `diag-${++requestSequence}`, severity: 'warning', code: 'COMMAND_DISABLED', message: status.reason || 'Command is disabled', objectId: state.selectedObjectId || undefined, requestId }
|
|
|
|
|
state = { ...state, diagnostics: [...state.diagnostics, diagnostic] }
|
|
|
|
|
emit({ type: 'diagnostic.added', diagnostic, context }); emit({ type: 'command.failed', commandId, context, message: status.reason }); notify(status.reason || 'Command is disabled'); return requestId
|
|
|
|
|
}
|
|
|
|
|
emit({ type: 'command.started', commandId, context })
|
|
|
|
|
if (commandId === 'new-document') state = { ...state, document: createDocument('Untitled document') }
|
|
|
|
|
else if (commandId === 'save') state = { ...state, document: { ...state.document, dirty: false, version: state.document.version + 1 } }
|
|
|
|
|
else if (commandId === 'select-object' && typeof payload?.objectId === 'string') select(payload.objectId)
|
|
|
|
|
else if (commandId === 'create-body') beginTask('create-body')
|
|
|
|
|
else if (commandId === 'create-sketch') beginTask('create-sketch')
|
|
|
|
|
emit({ type: 'command.completed', commandId, requestId }); emitState(); return requestId
|
|
|
|
|
emit({ type: 'command.completed', commandId, context }); emitState(); return requestId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const facade: BitBybitWebCadFacade = {
|
|
|
|
|
app: { document: { getActive: () => getState().document, create: (label) => { state = { ...state, document: createDocument(label), selectedObjectId: '' }; emitState(); return getState().document }, markDirty: () => { state = { ...state, document: { ...state.document, dirty: true } }; emitState() } } },
|
|
|
|
|
gui: { workbench: { list: () => Object.keys(workbenchDefinitions) as WorkbenchId[], getActive: () => state.activeWorkbench, setActive }, command: { getState: (commandId) => commandState(commandId, state.activeWorkbench), list: (workbench) => workbenchDefinitions[workbench].groups.flatMap((group) => group.commands), execute } },
|
|
|
|
|
gui: { workbench: { list: () => Object.keys(workbenchDefinitions) as WorkbenchId[], getActive: () => state.activeWorkbench, setActive }, command: { getState: (commandId) => commandState(commandId, state.activeWorkbench, state.selectedObjectId), list: (workbench) => workbenchDefinitions[workbench].groups.flatMap((group) => group.commands), execute } },
|
|
|
|
|
selection: { getObjectId: () => state.selectedObjectId, select, clear: () => select('') },
|
|
|
|
|
task: { getActive: () => getState().task, begin: beginTask, update: (draft) => { if (state.task) state = { ...state, task: { ...state.task, draft: { ...state.task.draft, ...draft } } }; emitState() }, apply: () => { if (state.task) state = { ...state, task: { ...state.task, status: 'completed' } }; emitState() }, cancel: () => { if (state.task) state = { ...state, task: { ...state.task, status: 'cancelled' } }; emitState() } },
|
|
|
|
|
viewport: { createAdapter: () => new ThreeViewportAdapter() },
|
|
|
|
|
|