You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
12 KiB
443 lines
12 KiB
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<CustomScript, Worker>()
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|