Browse Source

仿真环境启动

master
修宁 6 months ago
parent
commit
993784c1f6
  1. 2
      src/components/yvTable/YvTable.vue
  2. 5
      src/core/engine/Viewport.ts
  3. 50
      src/core/manager/EntityManager.ts
  4. 233
      src/core/manager/EnvManager.ts
  5. 67
      src/core/manager/RunManager.ts
  6. 14
      src/core/manager/RuntimeManager.ts
  7. 2
      src/core/manager/WorldModel.ts
  8. 18
      src/core/script/ModelManager.ts
  9. 30
      src/editor/ModelMain.vue
  10. 75
      src/editor/OpenProject.vue
  11. 4
      src/editor/menus/FileMenu.ts
  12. 50
      src/modules/cl2/Cl23dObject.ts
  13. 11
      src/types/LCC.d.ts
  14. 11
      src/types/Model.d.ts

2
src/components/yvTable/YvTable.vue

@ -287,9 +287,7 @@ export default {
})
_.forEach(insertRows, (row, index) => {
if (!row._rid) {
row._rid = _.uniqueId('_')
}
})
sel.add = _.cloneDeep(insertRows)
if (sel.add.length > 0 || sel.update.length > 0 || sel.remove.length > 0) {

5
src/core/engine/Viewport.ts

@ -26,6 +26,7 @@ import ItemFindManager from '@/core/manager/ItemFindManager.ts'
import { MapControls } from 'three/examples/jsm/controls/MapControls'
import ModelManager from '@/core/script/ModelManager.ts'
import RuntimeManager from '@/core/manager/RuntimeManager.ts'
import EnvManager from '@/core/manager/EnvManager.ts'
/**
*
@ -50,6 +51,7 @@ export default class Viewport {
interactionManager = new InteractionManager()
modelManager = new ModelManager()
runtimeManager = new RuntimeManager()
envManager = new EnvManager()
// 状态管理器
stateManager: StateManager
@ -63,7 +65,8 @@ export default class Viewport {
markRaw(this.itemFindManager),
markRaw(this.interactionManager),
markRaw(this.modelManager),
markRaw(this.runtimeManager)
markRaw(this.runtimeManager),
markRaw(this.envManager)
]
// 对象实例管理器 moduleName -> InstanceMeshManager

50
src/core/manager/EntityManager.ts

@ -101,6 +101,54 @@ export default class EntityManager {
this.lineDiffs.delete.clear()
}
deleteEntityOnlyRuntime(id: string) {
const entity = this.___entityMap.get(id)
if (entity) {
this.viewport.itemFindManager.remove(id)
const renderer = getRenderer(entity.t)
if (renderer) {
renderer.tempViewport = this.viewport
renderer.deletePoint(id, { isRuntime: true })
this.___entityMap.delete(id)
renderer.tempViewport = null
}
}
}
/**
* , StateManager Link
*/
createOrUpdateEntityOnlyRuntime(entity: ItemJson) {
if (!entity?.id) {
throw new Error('Entity must have an id')
}
const originEntity = this.___entityMap.get(entity.id)
const renderer = getRenderer(entity.t)
if (!renderer) {
throw new Error(`Renderer for type ${entity.t} not found`)
}
this.viewport.itemFindManager.addOrUpdate(entity)
this.___entityMap.set(entity.id, entity)
const option = {
isRuntime: true,
originEntity: _.cloneDeep(originEntity)
} as RendererCudOption
renderer.tempViewport = this.viewport
if (typeof originEntity === 'undefined') {
renderer.createPointForEntity(entity, option)
} else {
renderer.updatePointForEntity(entity, option)
}
renderer.tempViewport = null
}
/**
* , center[] / in[] / out[] ,
*/
@ -510,6 +558,7 @@ export default class EntityManager {
}
let item: ItemJson | undefined = undefined
this.___entityMap.forEach((value) => {
//@ts-ignore
if (value.logicX === logicX && value.logicY === logicY) {
item = value
return
@ -521,6 +570,7 @@ export default class EntityManager {
findItemByLogicXYZ(x: number, y: number, z: number): ItemJson | undefined {
let item: ItemJson | undefined = undefined
this.___entityMap.forEach((value) => {
//@ts-ignore
if (value.tf[0][0] === x && /*value.tf[0][1] === y &&*/ value.tf[0][2] === z && value.logicX) {
item = value
return

233
src/core/manager/EnvManager.ts

@ -0,0 +1,233 @@
import type Viewport from '@/core/engine/Viewport.ts'
import { worldModel } from '@/core/manager/WorldModel.ts'
import mqtt, { type IConnackPacket, type IPublishPacket } from 'mqtt'
import type { Cl2Task } from '@/modules/cl2/Cl23dObject.ts'
import type { ErrorWithReasonCode } from 'mqtt/src/lib/shared.ts'
import type Cl23dObject from '@/modules/cl2/Cl23dObject.ts'
import { Request } from '@ease-forge/shared'
export default class EnvManager {
private viewport: Viewport
private client: mqtt.MqttClient = null
init(viewport: Viewport): void {
this.viewport = viewport
}
// 从后台读取所有车
async loadExecutors() {
const res = await Request.request.post('/api/workbench/EnvController@getAllExecutor', {
projectUuid: worldModel.state.project_uuid,
catalogCode: worldModel.state.catalogCode,
envId: worldModel.state.runState.currentEnvId
})
for (const row of res.data) {
const executor_id = row.executor_id
const payload = JSON.parse(row.virtual_executor_payload)
const wayPointId = row.virtual_location_at
const point = Model.find(wayPointId)
if (!point) {
console.error(`Waypoint with ID ${wayPointId} not found for executor ${executor_id}.`)
continue
}
const item = _.cloneDeep(payload)
item.id = executor_id
item.tf[0] = _.cloneDeep(point.tf[0])
Model.createExecutor(item)
}
}
// 从后台读取所有库存
async loadInv() {
const res = await Request.request.post('/api/workbench/EnvController@getAllInv', {
projectUuid: worldModel.state.project_uuid,
catalogCode: worldModel.state.catalogCode,
envId: worldModel.state.runState.currentEnvId
})
for (const row of res.data) {
const bay = row.bay
const cell = row.cell // : 0
const level = row.level // : 0
const loc_code = row.loc_code // : "rack1_0_0_0"
const lpn = row.lpn // : "LPN1"
const rack = row.rack // : "rack1"
const container_type = row.container_type // : "pallet"
Model.createInv(container_type, lpn, rack, bay, level, cell)
}
}
appendExecutor(id) {
const obj = this.viewport.entityManager.findObjectById(id)
const item = this.viewport.entityManager.findItemById(id)
if (item.t == 'cl2' && obj.userData.t === 'cl2') {
debugger
const cl2 = obj as Cl23dObject
cl2.onMqttConnect(item, this.client)
// this.client.subscribe(['/wcs_server/' + cl2.id], { qos: 0 })
// this.client.publish('/agv_robot/status', JSON.stringify(m20020), { retain: true })
}
}
onMqttConnect = (packet: IConnackPacket) => {
console.log('Connected')
}
onMqttMessage = (topic: string, payload: Buffer, packet: IPublishPacket) => {
console.log(`[${topic}] ${msg}`)
debugger
const a: Cl2Task = JSON.parse(msg.toString())
this.handleMessage(a)
}
onMqttError = (error: Error | ErrorWithReasonCode) => {
console.error('Error:', error)
}
async start(env: EnvInfo) {
if (!env) {
system.showErrorDialog('Environment is not specified, cannot start EnvManager.')
return
}
if (!worldModel.state.isOpened) {
system.showErrorDialog('WorldModel is not opened, cannot start EnvManager.')
return
}
if (!this.viewport) {
system.showErrorDialog('Viewport is not initialized, cannot start EnvManager.')
return
}
if (!worldModel.state.runState.currentEnvId) {
system.showErrorDialog('Current environment ID is not set, cannot start EnvManager.')
return
}
if (worldModel.state.runState.isRunning) {
system.showErrorDialog('EnvManager is already running, cannot start again.')
return
}
const payload = env.env_payload
const brokerUrl = payload?.mqtt?.websocket
const username = payload?.mqtt?.username
const password = payload?.mqtt?.password
if (!brokerUrl || !username || !password) {
system.showErrorDialog('MQTT broker URL, username, or password is not set in the environment payload.')
return
}
this.stop()
system.showLoading()
worldModel.state.runState.isLoading = true
worldModel.state.runState.currentEnv = Object.freeze(env)
try {
await this.loadExecutors()
await this.loadInv()
this.client = mqtt.connect(brokerUrl, {
path: '/mqtt',
clientId: system.createUUID(),
clean: true,
connectTimeout: 300,
username,
password,
unixSocket: true,
keepalive: 60
})
this.client.on('connect', this.onMqttConnect)
this.client.on('message', this.onMqttMessage)
this.client.on('error', this.onMqttError)
worldModel.state.runState.isRunning = true
} finally {
system.clearLoading()
worldModel.state.runState.isLoading = false
}
}
async stop() {
system.showLoading()
try {
if (worldModel.state.runState.isRunning) {
worldModel.state.runState.isRunning = false
}
worldModel.state.runState.currentEnv = null
if (this.client) {
this.client.removeAllListeners()
this.client.end()
this.client = null
}
this.clearExecutors()
this.clearInv()
this.viewport.runtimeManager.clear()
} finally {
system.clearLoading()
worldModel.state.runState.isLoading = false
}
}
clearInv() {
}
clearExecutors() {
}
static CURRENT_ALL_ENV: EnvInfo[]
/**
*
* @param worldId ID
* @param envName
* @param isVirtual
*/
static async createEnv(worldId: string, envName: string, isVirtual: boolean) {
throw new Error('Method not implemented.')
}
/**
*
*/
static async getAllEnv(worldId: string): Promise<ServerResponse<EnvInfo[]>> {
// system.invokeServer('')
if (!worldId) {
return Promise.resolve({ success: true, data: [], msg: '' })
}
const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', {
worldId: worldId
})
if (res.success) {
EnvManager.CURRENT_ALL_ENV = res.data
for (const env of res.data) {
// payload 转换为 json 数据
if (env.env_payload) {
try {
env.env_payload = JSON.parse(env.env_payload)
} catch (e) {
console.error('解析环境负载失败:', e)
env.env_payload = {}
}
}
}
return res
}
EnvManager.CURRENT_ALL_ENV = []
return res
}
/**
*
*/
dispose(): void {
this.viewport = null
this.stop()
}
}

67
src/core/manager/RunManager.ts

@ -1,67 +0,0 @@
import { Request } from '@ease-forge/shared'
/**
*
*/
class RunManager {
// 是否正在运行
isRunning: boolean = false
/**
*
*/
async getAllEnv(worldId: string): Promise<ServerResponse<EnvInfo[]>> {
// system.invokeServer('')
if (!worldId) {
return Promise.resolve({ success: true, data: [], msg: '' })
}
const res = await Request.request.post('/api/workbench/EnvController@getAllEnv', {
worldId: worldId
})
for (const env of res.data) {
// payload 转换为 json 数据
if (env.env_payload) {
try {
env.env_payload = JSON.parse(env.env_payload)
} catch (e) {
console.error('解析环境负载失败:', e)
env.env_payload = {}
}
}
}
return res
}
/**
*
* @param worldId ID
* @param envName
* @param isVirtual
*/
async createEnv(worldId: string, envName: string, isVirtual: boolean) {
throw new Error('Method not implemented.')
}
/**
*
*/
async run(timeRate: number, envId: number) {
if (this.isRunning) {
await this.stop()
}
// 启动 websocket 监听等等
}
/**
*
*/
async stop() {
// 停止 websocket 监听等等
}
}
const runManager = new RunManager()
export default runManager

14
src/core/manager/RuntimeManager.ts

@ -5,6 +5,8 @@ import type Viewport from '@/core/engine/Viewport.ts'
*/
export default class RuntimeManager {
private viewport: Viewport
private readonly tmpExecutors = new Set<string>
// 货架ID -> 托盘ID
private readonly storeRackMap = new Map<string, Set<string>>()
init(viewport: Viewport): void {
@ -58,11 +60,21 @@ export default class RuntimeManager {
*
*/
clear() {
for (const id of this.tmpExecutors) {
this.viewport.entityManager.deleteEntityOnlyRuntime(id)
}
this.tmpExecutors.clear()
this.storeRackMap.clear()
}
dispose(): void {
this.viewport = null
this.storeRackMap.clear()
this.clear()
}
addEntity(item: ItemJson) {
this.tmpExecutors.add(item.id)
this.viewport.entityManager.createOrUpdateEntityOnlyRuntime(item)
}
}

2
src/core/manager/WorldModel.ts

@ -26,6 +26,7 @@ export interface WorldModelState {
isRunning: boolean,
isVirtual: boolean,
timeRate: number,
currentEnv: EnvInfo
}
}
@ -218,6 +219,7 @@ export default class WorldModel {
const items = _.cloneDeep(floor.items)
delete floor.items
console.log("floor", floor);
//@ts-ignore
const vdata: VData = {
items: items as ItemJson[],
infos: floor,

18
src/core/script/ModelManager.ts

@ -94,10 +94,25 @@ export default class ModelManager implements IControls, Model {
})
}
createInv(boxType: ContainerT, lpn: string, rack: string, bay?: number, level?: number, cell?: number): void {
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)
@ -125,6 +140,7 @@ export default class ModelManager implements IControls, Model {
})
}
})
*/
}
getPositionByLogicXY(logicX: number, logicY: number): THREE.Vector3 {

30
src/editor/ModelMain.vue

@ -31,11 +31,13 @@
<el-button :icon="renderIcon('Play')" type="primary"
v-if="!worldModelState.runState.isRunning"
:disabled="!worldModelState.runState.currentEnvId"
:loading="worldModelState.runState.isLoading">启动
:loading="worldModelState.runState.isLoading"
@click="startEnv">启动仿真
</el-button>
<el-button :icon="renderIcon('Stop')" type="danger" plain
v-if="worldModelState.runState.isRunning"
:loading="worldModelState.runState.isLoading">停止
:loading="worldModelState.runState.isLoading"
@click="stopEnv">停止仿真
</el-button>
</div>
</div>
@ -150,9 +152,8 @@ import CatalogDefine from './CatalogDefine.vue'
import Logo from '@/assets/images/logo.png'
import './ModelMain.less'
import EventBus from '@/runtime/EventBus.js'
import { useRouter } from 'vue-router'
import { worldModel } from '@/core/manager/WorldModel.ts'
import runManager from '@/core/manager/RunManager.js'
import EnvManager from '@/core/manager/EnvManager.js'
export default {
components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine },
@ -269,15 +270,16 @@ export default {
},
watch: {
'worldModelState.isOpened': {
handler(newVal) {
if (newVal.isOpened) {
handler() {
if (this.worldModelState.isOpened) {
this.reloadEnvList()
}
}
},
'worldModelState.project_uuid': {
handler(newVal) {
if (newVal.isOpened) {
immediate: true,
handler() {
if (this.worldModelState.isOpened) {
this.reloadEnvList()
}
}
@ -299,8 +301,18 @@ export default {
methods: {
renderIcon,
getWidgetBySide,
startEnv() {
const env = this.envList.find(env => env.env_id === this.worldModelState.runState.currentEnvId)
if (env) {
this.currentViewport.envManager.start(env)
}
},
stopEnv() {
this.currentViewport.envManager.stop()
},
reloadEnvList() {
runManager.getAllEnv(this.worldModelState.project_uuid).then(res => {
EnvManager.getAllEnv(this.worldModelState.project_uuid)
.then(res => {
this.envList = res.data
})
},

75
src/editor/OpenProject.vue

@ -1,31 +1,31 @@
<script setup lang="ts">
import { onMounted, reactive, useTemplateRef } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import { ElButton } from "element-plus";
import { type GridOptions } from "ag-grid-enterprise";
import { type GridApi, type GridReadyEvent } from "ag-grid-community";
import { Request } from "@ease-forge/shared";
import { localeText as localeTextCn } from "../components/yvTable/yv-aggrid-cn.locale";
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import { onMounted, reactive, useTemplateRef } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import { ElButton } from 'element-plus'
import { type GridOptions } from 'ag-grid-enterprise'
import { type GridApi, type GridReadyEvent } from 'ag-grid-community'
import { Request } from '@ease-forge/shared'
import { localeText as localeTextCn } from '../components/yvTable/yv-aggrid-cn.locale'
import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css'
defineOptions({
name: 'OpenProject',
});
name: 'OpenProject'
})
//
const emit = defineEmits<{
"cancel": [];
"open": [row: any];
"add": [expose: OpenProjectExpose];
}>();
'cancel': [];
'open': [row: any];
'add': [expose: OpenProjectExpose];
}>()
// Props
interface OpenProjectProps {
}
// props
const props = withDefaults(defineProps<OpenProjectProps>(), {});
const props = withDefaults(defineProps<OpenProjectProps>(), {})
// State
interface OpenProjectState {
@ -33,7 +33,7 @@ interface OpenProjectState {
}
// state
const state = reactive<OpenProjectState>({});
const state = reactive<OpenProjectState>({})
// Data
interface OpenProjectData {
@ -48,42 +48,39 @@ const data: OpenProjectData = {
// suppressNoRowsOverlay: true,
// suppressLoadingOverlay: true,
//
rowSelection: "single",
rowSelection: 'single',
columnDefs: [
{ field: 'id', headerName: 'id', editable: false, hide: true },
{ field: 'id', headerName: 'id', editable: false },
{ field: 'projectUuid', headerName: '项目编号', editable: false },
{ field: 'projectLabel', headerName: '项目标题', editable: false },
{ field: 'projectVersion', headerName: '项目版本', editable: false },
{ field: 'server', headerName: '所在服务器', editable: false },
{ field: 'directoryData', headerName: '目录数据', editable: false },
{ field: 'otherData', headerName: '其他数据', editable: false },
{ field: 'createAt', headerName: '创建时间', editable: false },
{ field: 'createBy', headerName: '创建人', editable: false },
{ field: 'updateAt', headerName: '最后更新时间', editable: false },
{ field: 'updateBy', headerName: '更新人', editable: false },
{ field: 'updateBy', headerName: '更新人', editable: false }
],
onGridReady(event: GridReadyEvent) {
data.api = event.api
},
},
};
const grid = useTemplateRef<InstanceType<typeof AgGridVue>>("gridRef");
onMounted(loadData);
}
}
}
const grid = useTemplateRef<InstanceType<typeof AgGridVue>>('gridRef')
onMounted(loadData)
function loadData() {
Request.request.post("/api/workbench/LccModelManager@projectList").then(res => {
state.grid1Data = res.records;
});
Request.request.post('/api/workbench/LccModelManager@projectList').then(res => {
state.grid1Data = res.records
})
}
function open() {
const row = data.api?.getSelectedRows()?.[0];
if (!row) return;
emit("open", row);
const row = data.api?.getSelectedRows()?.[0]
if (!row) return
emit('open', row)
}
function add() {
emit("add", expose);
emit('add', expose)
}
interface OpenProjectExpose {
@ -95,14 +92,14 @@ interface OpenProjectExpose {
const expose: OpenProjectExpose = {
state,
data,
loadData,
};
loadData
}
//
defineExpose(expose);
defineExpose(expose)
export type {
OpenProjectProps,
OpenProjectState,
OpenProjectState
}
</script>

4
src/editor/menus/FileMenu.ts

@ -109,7 +109,7 @@ export default defineMenu((menus) => {
}
}), {
title: '打开项目',
width: 1500,
width: 600,
height: 500,
showClose: true,
showMax: true,
@ -142,6 +142,8 @@ export default defineMenu((menus) => {
envId: 1,
otherData: JSON.stringify(worldModel.state.worldData)
})
system.msg('保存成功', 'success')
} finally {
system.clearLoading()
}

50
src/modules/cl2/Cl23dObject.ts

@ -409,27 +409,9 @@ export default class Cl23dObject extends THREE.Object3D {
on() {
}
}
debugger
const m20020 = {
'content': {
'CreateMonoTime': 233701185,
'CreateTime': 1750638957541,
'CurDirection': 0,
'CurLogicX': 6,
'CurLogicY': 2,
'CurOrientation': -3.1375624383367926,
'CurX': 6,
'CurY': 2,
'MarkerType': 1,
'SendTime': 1750638957541,
'SeqNo': 11,
'VehicleId': 3,
'X': 2652.477598132277,
'Y': 3944.4427159671854
},
'id': 20020
}
/*
// 事件绑定
client.on('connect', () => {
console.log('Connected')
@ -447,6 +429,7 @@ export default class Cl23dObject extends THREE.Object3D {
client.on('error', (error) => {
console.error('Error:', error)
})
*/
} catch (e) {
console.error(e)
}
@ -455,8 +438,33 @@ export default class Cl23dObject extends THREE.Object3D {
/*==========消息处理============*/
onMqttConnect(item: ItemJson, client: mqtt.MqttClient) {
const m20020 = {
'content': {
'CreateMonoTime': 233701185,
'CreateTime': 1750638957541,
'CurDirection': 0,
'CurLogicX': 6,
'CurLogicY': 2,
'CurOrientation': -3.1375624383367926,
'CurX': 6,
'CurY': 2,
'MarkerType': 1,
'SendTime': 1750638957541,
'SeqNo': 11,
'VehicleId': 3,
'X': 2652.477598132277,
'Y': 3944.4427159671854
},
'id': 20020
}
client.subscribe(['/wcs_server/' + item.id], { qos: 0 })
client.publish('/agv_robot/status', JSON.stringify(m20020), { retain: true })
}
handleMessage(data: Cl2Task) {
return;
return
if (data.id === 10010) {
if (this.taskList.length <= 0) {

11
src/types/LCC.d.ts

@ -11,17 +11,6 @@ declare interface LCC {
loadFloor(projectUUID: string, catalogCode: string, envId: string): Promise<ServerResponse<boolean>>
/**
*
* @param boxType
* @param lpn
* @param rack ID
* @param bay ,
* @param level ,
* @param cell ,
*/
createInv(boxType: ContainerT, lpn: string, rack: string, bay: number = 0, level: number = 0, cell: number = 0): void
/**
*
* @param lpn
* @param rack ID

11
src/types/Model.d.ts

@ -30,7 +30,7 @@ declare interface Model {
/**
*
*
* @param boxType
* @param lpn
* @param rack ID
@ -40,6 +40,13 @@ declare interface Model {
*/
createInv(boxType: ContainerT, lpn: string, rack: string, bay: number = 0, level: number = 0, cell: number = 0): void
/**
*
* @param item Json
*/
createExecutor(item: ItemJson): void
/**
*
*/
@ -205,7 +212,7 @@ declare function msg(str: string, type: MSG_TYPE = 1): void
*
*/
interface EnvInfo {
env_id: string
env_id: number
world_id: string
env_name: string
is_virtual: boolean

Loading…
Cancel
Save