import Viewport from '@/core/engine/Viewport.ts' import { compileTypeScript, executeTypeScript } from '@/core/script/ModelScript.ts' import * as THREE from 'three' import { getMatrixFromTf } from '@/core/ModelUtils.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' import TaskManager from '../manager/TaskManager.ts' import Cl2Entity from '@/modules/amr/ptr/cl2/Cl2Entity.ts' import ClxEntity from '@/modules/amr/ptr/clx/ClxEntity.ts' import { getRenderer } from '@/core/manager/ModuleManager.ts' import { markRaw } from 'vue' import ToolProxyManager from '@/runtime/ToolProxyManager.ts' export default class ModelManager implements IControls, Model { private viewport: Viewport getCl2(id: string): Cl2If { return new Cl2Entity(this.viewport, id) } getClx(id: string): ClxIf { return new ClxEntity(this.viewport, id) } createTask(agv: object): TaskManager { if (TaskManager.taskMap.has(agv)) { return TaskManager.taskMap.get(agv) } return new TaskManager(agv, this.viewport) } get selectedObject(): Object3DLike | undefined { return this.viewport.state.selectedObject } get selectedEntityId(): string | undefined { return this.viewport.state.selectedEntityId } get selectedItem(): ItemJson | undefined { return this.viewport.state.selectedItem } get multiSelectedObjects(): Object3DLike[] { return this.viewport.state.multiSelectedObjects } get multiSelectedItems(): ItemJson[] { return this.viewport.state.multiSelectedItems } get multiSelectedEntityIds(): string[] { return this.viewport.state.multiSelectedEntityIds } logger = { info: (...args: any[]) => console.info(...args), error: (...args: any[]) => console.error(...args), warn: (...args: any[]) => console.warn(...args), debug: (...args: any[]) => console.debug(...args) } init(viewport: Viewport) { this.viewport = viewport window['Model'] = this window['print'] = this.print.bind(this) window['executestring'] = this.executestring.bind(this) window['logger'] = this.logger window['msg'] = this.msg } find(entityId: string): ItemJson { const item = this.viewport.entityManager.findItemById(entityId) if (!item) { print(`Item with ID ${entityId} not found in the viewport.`) } return item } find3D(id: string): any { return this.viewport.entityManager.findObjectById(id) } getPositionByEntityId(entityId: string): THREE.Vector3 { const item = this.viewport.entityManager.findItemById(entityId) const matrix = getMatrixFromTf(item.tf) const position = new THREE.Vector3() matrix.decompose(position, new THREE.Quaternion(), new THREE.Vector3()) return position } deleteInv(itemId) { this.viewport.runtimeManager.removeEntity(itemId) } deleteItem(itemId) { if (this.viewport.runtimeManager.has(itemId)) { // 临时执行器 this.viewport.runtimeManager.removeEntity(itemId) return } this.viewport.stateManager.update(({ deleteEntity }) => { deleteEntity(itemId) }) } createExecutor(item: ItemJson): void { this.viewport.runtimeManager.addEntity(item) } createInv(boxType: ContainerT, lpn: string, rack: string, bay?: number, level?: number, cell?: number): void { const scale = getRenderer(boxType).defaultScale this.viewport.runtimeManager.addEntity({ id: lpn, t: boxType as string, tf: [[-5000, -1, 0], [0, 0, 0], [scale.x, scale.y, scale.z]], v: true, dt: { in: [], out: [], center: [], storeAt: { item: rack, bay, level, cell } } }) // 这一段代码不要删除,他是用向正式环境提交数据用的 /* this.viewport.stateManager.update(({ getEntity, putEntity, addEntity }) => { debugger const item = getEntity(lpn) if (item) { _.extend(item, { t: boxType as string, tf: [[-5000, -1, 0], [0, 0, 0], [scale.x, scale.y, scale.z]], v: true, dt: { in: [], out: [], center: [], storeAt: { item: rack, bay, level, cell } } }) putEntity(item) } else { addEntity({ id: lpn, t: boxType as string, tf: [[-5000, -1, 0], [0, 0, 0], [scale.x, scale.y, scale.z]], v: true, dt: { in: [], out: [], center: [], storeAt: { item: rack, bay, level, cell } } }) } }) */ } getPositionByLogicXY(logicX: number, logicY: number): THREE.Vector3 { const item = this.viewport.entityManager.findItemByLogicXY(logicX, logicY) if (!item) { return null } const matrix = getMatrixFromTf(item.tf) const position = new THREE.Vector3() matrix.decompose(position, new THREE.Quaternion(), new THREE.Vector3()) return position } getItemByXYZ(x: number, y: number, z: number): ItemJson | undefined { const item = this.viewport.entityManager.findItemByLogicXYZ(x, y, z) return item } dispose() { this.viewport = null as any window['Model'] = null window['print'] = null window['executestring'] = null window['logger'] = null window['msg'] = null window['RCS'] = null window['LCC'] = null } async executestring(script: string) { return executeTypeScript.call(this, script, {}) } print(...data: any[]) { console.log(...data) } msg(content: string, type: number = 1) { if (type === 1) { system.showErrorDialog(content) } else if (type === 2) { system.showInfoDialog(content) } } // 脚本与 Worker 的映射,用于快速查找 private scriptWorkers = new WeakMap() private toolProxyManager = new ToolProxyManager() /** * 开启另外的线程,运行脚本 */ async runScript(script: CustomScript) { // 检查脚本是否已在运行 if (script.isRunning) { console.warn(`脚本 "${script.name}" 已在运行中`) return } try { // 创建 Blob URL 作为 Worker 执行内容 const jsCode = await compileTypeScript(script.content) const wrappedContent = ` // Worker 端工具代理 // 创建代理对象 const createToolProxy = (port) => { return new Proxy({}, { get(_, toolName) { return new Proxy({}, { get(_, methodName) { return (...args) => { return new Promise((resolve, reject) => { const callId = Math.random().toString(36).substr(2, 10); // 发送调用请求 port.postMessage({ type: 'call', tool: toolName, method: methodName, callId, args }); // 监听响应 const responseHandler = (event) => { const { data } = event; if (data.callId === callId) { console.log('[Worker] 收到响应: ' + toolName + '.' + methodName, data.type); port.removeEventListener('message', responseHandler); if (data.type === 'result') { resolve(data.result); } else if (data.type === 'error') { const err = new Error(data.error.message); err.name = data.error.name; err.stack = data.error.stack; reject(err); } } }; port.addEventListener('message', responseHandler); }); }; } }); } }); }; // 创建代理对象 const tools = new Proxy({}, toolProxyHandler); self.onmessage = function(__initMessage) { if (__initMessage.data.type === 'init') { // 设置通信端口 self.port = __initMessage.data.port; self.port.onmessage = function(event) { const { type, callId, result, error } = event.data; if (!self.pendingCalls || !self.pendingCalls[callId]) return; const { resolve, reject } = self.pendingCalls[callId]; delete self.pendingCalls[callId]; if (type === 'result') { resolve(result); } else if (type === 'error') { const err = new Error(error.message); err.name = error.name; err.stack = error.stack; reject(err); } }; self.port.start(); // 解构代理工具 const { LCC, RCS } = tools; try { (async function() { // ---------------------custom script start---------------------[ ${jsCode} // ]---------------------custom script end--------------------- })().then(() => { self.postMessage({ type: 'completed' }) }).catch(error => { // 捕获异步错误 self.postMessage({ type: 'error', error: { name: error.name, message: error.message, stack: error.stack } }) }) } catch (error) { // 捕获脚本错误 self.postMessage({ type: 'error', error: { name: error.name, message: error.message, stack: error.stack } }) } finally { // 确保关闭 worker self.close() } } } ` // console.log(wrappedContent) const blob = new Blob([wrappedContent], { type: 'application/javascript' }) const blobUrl = URL.createObjectURL(blob) const worker = new Worker(blobUrl) // 创建代理并初始化 Worker const port = this.toolProxyManager.createToolProxyForWorker(worker) // 存储 Worker 引用并更新状态 this.scriptWorkers.set(script, worker) script.worker = markRaw(worker) script.isRunning = true URL.revokeObjectURL(blobUrl) // 清理不再需要的 Blob URL // 设置 Worker 事件监听器 worker.onmessage = (e) => this.handleWorkerMessage(script, e) worker.onerror = (e) => this.handleWorkerError(script, e) worker.onmessageerror = (e) => this.handleMessageError(script, e) console.log(`[ScriptRunner] 创建 Worker 并启动脚本 "${script.name}"`) worker.postMessage({ type: 'init', port: port }, [port]) console.log(`[ScriptRunner] 初始化消息已发送给 Worker`) } catch (error) { console.error(`启动脚本 "${script.name}" 失败:`, error) script.isRunning = false } } /** * 停止脚本 */ stopScript(script: CustomScript) { this.cleanupScript(script, '手动停止') } /** * 处理来自 Worker 的消息 */ private handleWorkerMessage(script: CustomScript, event: MessageEvent): void { const { data } = event if (data?.type === 'call') { // 处理来自 Worker 的工具调用请求 this.toolProxyManager.handleWorkerCall(script.worker, event) return } if (data?.type === 'completed') { // 脚本自然完成 console.log(`脚本 "${script.name}" 执行完成`) this.cleanupScript(script, '执行完成') return } if (data?.type === 'error') { // 脚本内部错误 const error = new Error(data.error.message) error.name = data.error.name error.stack = data.error.stack console.error(`[${script.name}] 执行错误:`, error) this.cleanupScript(script, '执行错误') return } // 常规消息处理 console.log(`[${script.name}] 收到消息:`, data) debugger } /** * 处理 Worker 错误 */ private handleWorkerError(script: CustomScript, event: ErrorEvent): void { console.error(`[${script.name}] 发生错误:`, event.message, `@${event.filename}:${event.lineno}`) this.cleanupScript(script, '运行时错误') } /** * 处理消息错误 */ private handleMessageError(script: CustomScript, event: MessageEvent): void { console.error(`[${script.name}] 消息解析错误:`, event) this.cleanupScript(script, '消息错误') } /** * 清理脚本资源 */ private cleanupScript(script: CustomScript, reason: string): void { if (!script.isRunning || !script.worker) return try { // 清理代理管理器中的资源 this.toolProxyManager.cleanupWorker(script.worker) // 终止 Worker script.worker.terminate() this.scriptWorkers.delete(script) script.worker = undefined script.isRunning = false console.log(`脚本 "${script.name}" 已停止 (原因: ${reason})`) } catch (error) { console.error(`清理脚本 "${script.name}" 资源失败:`, error) } } }