diff --git a/package.json b/package.json index 4e2af2e..bc23c0d 100644 --- a/package.json +++ b/package.json @@ -13,26 +13,27 @@ "format": "prettier --write src/" }, "dependencies": { - "@vueuse/core": "^13.2.0" + "@vueuse/core": "^13.2.0", + "fdir": "^6.4.6" }, "devDependencies": { "@ease-forge/runtime": "^1.0.12", "@ease-forge/shared": "^1.0.12", "@element-plus/icons-vue": "^2.3.1", + "@guolao/vue-monaco-editor": "^1.5.5", "@rolldown/pluginutils": "1.0.0-beta.8-commit.56abf23", "@tsconfig/node22": "^22.0.1", + "@types/codemirror": "^5.60.16", "@types/jquery": "^3.3.31", "@types/lodash": "^4.17.7", "@types/node": "^22.14.0", "@types/three": "^0.176.0", - "@types/codemirror": "^5.60.16", + "@typescript/vfs": "^1.6.1", "@vicons/antd": "^0.13.0", "@vicons/fa": "^0.12.0", "@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue-jsx": "^4.2.0", "@vue/tsconfig": "^0.7.0", - "@typescript/vfs": "^1.6.1", - "rollup-plugin-typescript2": "^0.36.0", "ag-grid-community": "^28.2.1", "ag-grid-enterprise": "^28.2.1", "ag-grid-vue3": "^28.2.1", @@ -42,6 +43,7 @@ "dat.gui": "^0.7.9", "decimal.js": "^10.5.0", "element-plus": "^2.9.10", + "gsap": "^3.13.0", "hotkeys-js": "^3.13.10", "jquery": "^3.6.0", "json5": "^2.2.3", @@ -53,23 +55,22 @@ "pinia": "^3.0.1", "prettier": "3.5.3", "rimraf": "^6.0.1", + "rollup-plugin-typescript2": "^0.36.0", "sortablejs": "1.15.6", "split.js": "^1.6.4", "three": "^0.177.0", + "three-csg-ts": "^3.2.0", + "three-dxf-viewer": "^1.0.36", + "three-mesh-bvh": "^0.9.0", "troika-three-text": "^0.52.4", "tslib": "2.8.1", "typescript": "~5.8.0", "vite": "^6.2.4", "vite-plugin-vue-devtools": "^7.7.2", "vue": "^3.5.13", - "@guolao/vue-monaco-editor": "^1.5.5", "vue-i18n": "9.2.2", "vue-router": "^4.5.0", "vue-tsc": "^2.2.8", - "vue3-menus": "^1.1.2", - "three-mesh-bvh": "^0.9.0", - "three-dxf-viewer": "^1.0.36", - "gsap": "^3.13.0", - "three-csg-ts": "^3.2.0" + "vue3-menus": "^1.1.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c83d9ee..190fec2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@vueuse/core': specifier: ^13.2.0 version: 13.2.0(vue@3.5.14(typescript@5.8.3)) + fdir: + specifier: ^6.4.6 + version: 6.4.6(picomatch@4.0.2) devDependencies: '@ease-forge/runtime': specifier: ^1.0.12 @@ -1121,8 +1124,8 @@ packages: resolution: {integrity: sha512-QFNnTvU3UjgWFy8Ef9iDHvIdcgZ344ebkwYx4/KLbR+CKQA4xBaHzv+iRpp86QfMHP8faFQLh8iOc57215y4Rg==} engines: {node: ^18.19.0 || >=20.5.0} - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2936,7 +2939,7 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.1 - fdir@6.4.4(picomatch@4.0.2): + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -3485,7 +3488,7 @@ snapshots: tinyglobby@0.2.13: dependencies: - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 totalist@3.0.1: {} @@ -3576,7 +3579,7 @@ snapshots: vite@6.3.5(@types/node@22.15.21)(less@4.3.0): dependencies: esbuild: 0.25.4 - fdir: 6.4.4(picomatch@4.0.2) + fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.3 rollup: 4.41.0 diff --git a/src/core/Constract.ts b/src/core/Constract.ts index a0cc2b7..37b1ed0 100644 --- a/src/core/Constract.ts +++ b/src/core/Constract.ts @@ -34,6 +34,8 @@ const Constract = Object.freeze({ HEIGHT_WAY_LINE: 0.02, // 鼠标点击延迟 - MOUSE_CLICK_DELAY: 200 + MOUSE_CLICK_DELAY: 200, + + WAY_ID_LABEL: 'way_id_label', }) export default Constract diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index f0b8d9e..6dc1650 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -58,7 +58,7 @@ export default abstract class BaseRenderer { /** * 创建测量线 */ - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): LineLike { + createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): LineLike | undefined | void { throw new Error('createLineBasic method must be implemented in derived class.') } @@ -155,7 +155,7 @@ export default abstract class BaseRenderer { if (!rackRenderer) { console.error(`Cannot find renderer for rack type ${rack.t}`) } - const { position, rotation } = rackRenderer.getStorePlacement(this.tempViewport, item) + const { position, rotation } = rackRenderer.getStorePlacement(rack, item.dt.storeAt.bay, item.dt.storeAt.level, item.dt.storeAt.cell) if (!position || !rotation) { console.error(`无法获取物品 ${item.id} 的存储位置`) } @@ -291,7 +291,6 @@ export default abstract class BaseRenderer { this.tempViewport.entityManager.appendLineObject(id, line) if (line instanceof THREE.Object3D) { this.appendToScene(line) - } this.afterCreateOrUpdateLine(start, end, type, option, line) @@ -312,7 +311,7 @@ export default abstract class BaseRenderer { * 获取物品存放位 * 获取物品存储到地堆货位中,返回可存放的位置和角度一个 Position 和 Rotation */ - getStorePlacement(viewport: Viewport, item: ItemJson) + getStorePlacement(storeItem: ItemJson, bay = 0, level = 0, cell = 0) : { position: [number, number, number], rotation: [number, number, number] } { throw new Error(' 不支持库存物品的添加. t=' + this.itemTypeName) } diff --git a/src/core/engine/SceneHelp.ts b/src/core/engine/SceneHelp.ts index aaf100b..50b1deb 100644 --- a/src/core/engine/SceneHelp.ts +++ b/src/core/engine/SceneHelp.ts @@ -81,14 +81,14 @@ export default class SceneHelp { this.scene.add(hemisphereLight) // 完全不透明的地板 - const geometry = new THREE.PlaneGeometry(gridOption.gridSize, gridOption.gridSize) - const material = new THREE.MeshBasicMaterial({ - color: '#ffffff', - side: THREE.BackSide - }) - const ground = new THREE.Mesh(geometry, material) - ground.rotation.x = -Math.PI / 2 - this.scene.add(ground) + // const geometry = new THREE.PlaneGeometry(gridOption.gridSize, gridOption.gridSize) + // const material = new THREE.MeshBasicMaterial({ + // color: '#ffffff', + // side: THREE.BackSide + // }) + // const ground = new THREE.Mesh(geometry, material) + // ground.rotation.x = -Math.PI / 2 + // this.scene.add(ground) // const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5) // directionalLight.position.set(5, 5, 5).multiplyScalar(3) diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index 8714e54..20bb691 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -219,6 +219,7 @@ export default class EntityManager { if (start.t !== itemTypeName) { // 只通知起点对应的渲染器 + // console.log(`[create] ${start.id} -> ${end.id} [${lineDiffItem.type}] => ${lineId}, 但起点类型不匹配, 跳过`) continue } renderer.createLine(start, end, lineDiffItem.type) diff --git a/src/core/manager/LabelManager.ts b/src/core/manager/LabelManager.ts index 2e5990c..5931067 100644 --- a/src/core/manager/LabelManager.ts +++ b/src/core/manager/LabelManager.ts @@ -43,6 +43,40 @@ export default class LabelManager { return labelObj } + createOrUpdateMeshLabel(parentObj: Object3DLike, text: string, option: LabelOption) { + let labelObj = this.labelMap.get(parentObj.userData.labelObjectId) + + if (!labelObj) { + labelObj = this.createLabelObject(option) + parentObj.userData.meshLabelObjectId = labelObj.uuid + + this.labelGroup.add(labelObj) + } + + if (labelObj) { + const matrix: THREE.Matrix4 = parentObj.matrix + const pos = new THREE.Vector3() + matrix.decompose(pos, new THREE.Quaternion(), new THREE.Vector3()) + if (option.offset) { + if (option.offset instanceof THREE.Vector3) { + pos.add(option.offset) + } else if (Array.isArray(option.offset)) { + pos.add(new THREE.Vector3(...option.offset)) + } + } + labelObj.position.copy(pos) + + if (labelObj instanceof CSS2DObject) { + labelObj.element.innerHTML = text + } else if (labelObj instanceof Text) { + labelObj.text = text + labelObj.quaternion.copy(this.viewport.camera.quaternion) + labelObj.sync() + } + } + return labelObj + } + createLabel(parentObj: Object3DLike | LineLike, option: LabelOption): Text | CSS2DObject { const labelObj = this.createLabelObject(option) parentObj.userData.labelObjectId = labelObj.uuid @@ -215,6 +249,8 @@ export interface LabelOption { * ex: css='5px 8px', text=0.2 */ padding?: number | string + + offset?: THREE.Vector3 | [number, number, number] text?: string format?: (distance: number) => string diff --git a/src/example/example1.js b/src/example/example1.js index 6bdf0e7..99997d6 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -207,124 +207,164 @@ export default { dt: { in: [], out: [], center: ['3cdb6OHkp132soSsgW8McA', '3ExXFSuV9WB2WMY2Quyq6L'] } }, { - id: '6Vu3dX1V7Si0ISWIiCkoEh', t: 'gstore', v: true, strokeWidth: 0.1, - tf: [[1.5, 0, 0.63], [0, 90, 0], [1, 0, 1]], - dt: { in: [], out: [], center: [], storeWidth: 1.1 } - }, - { - id: '592UY0EMScbwIyQqgs8aAs', t: 'gstore', v: true, strokeWidth: 0.1, - tf: [[3.9, 0, 0.63], [0, 90, 0], [1, 0, 1]], - dt: { in: [], out: [], center: [] } - }, - { - id: '38TYyVWMGLf8OogQMIiSOz', t: 'gstore', v: true, strokeWidth: 0.1, - tf: [[7.1, 0, 2.865], [0, 0, 0], [1, 0, 1]], - dt: { in: [], out: [], center: [] } - }, - { - id: '1hAaZ1xtvukZowAKeWAcqs', t: 'gstore', v: true, strokeWidth: 0.1, - tf: [[7.1, 0, 4.35], [0, 0, 0], [1, 0, 1]], - dt: { in: [], out: [], center: [] } - }, - { - id: '28GxDYUqDwZc2WsOgMU2wi', t: 'gstore', v: true, strokeWidth: 0.1, - tf: [[7.1, 0, 6.75], [0, 90, 0], [1, 0, 1]], - dt: { in: [], out: [], center: [] } - }, - { - id: '2fWOnUmFpvYyCWEqAyU0QC', t: 'way', v: true, - tf: [[1.5, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r'] } - }, - { - id: '09PTEMUnACWY0MUG4qmk0r', t: 'way', v: true, + id: '1', t: 'way', v: true, tf: [[2.7, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['2fWOnUmFpvYyCWEqAyU0QC', '2CSDVrpqthaiQuyWUymCwy', '0mVU9FacN1fmCAmQqwWgIZ'] } - }, - { - id: '2CSDVrpqthaiQuyWUymCwy', t: 'way', v: true, - tf: [[3.9, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r'] } + dt: { in: [], out: [], center: ['38', '36', '2'], autogyration: true } }, { - id: '0mVU9FacN1fmCAmQqwWgIZ', t: 'way', v: true, + id: '2', t: 'way', v: true, tf: [[2.7, 0, 2.832], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r', '7LuzEYQQI7OQcEUekEqWcm'] } + dt: { in: [], out: [], center: ['1', '3'] } }, { - id: '7LuzEYQQI7OQcEUekEqWcm', t: 'way', v: true, + id: '3', t: 'way', v: true, tf: [[2.7, 0, 3.932], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['0mVU9FacN1fmCAmQqwWgIZ', '2RForJhOHXtcw0gq8mYAMh'] } + dt: { + in: [], out: [], center: ['2', '4'], + linkStoreAt: { item: 'rack1', bay: 3, level: 2, cell: 0 } + } }, { - id: '2RForJhOHXtcw0gq8mYAMh', t: 'way', v: true, + id: '4', t: 'way', v: true, tf: [[2.7, 0, 4.582], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['7LuzEYQQI7OQcEUekEqWcm', '32vDSCKBrgMWycW0ySIgsJ'] } + dt: { in: [], out: [], center: ['3', '5'] } }, { - id: '32vDSCKBrgMWycW0ySIgsJ', t: 'way', v: true, + id: '5', t: 'way', v: true, tf: [[2.7, 0, 5.232], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['2RForJhOHXtcw0gq8mYAMh', '0wcYKcGQialFQCGkAa6aYB'] } + dt: { + in: [], out: [], center: ['4', '6'], + linkStoreAt: { item: 'rack1', bay: 2, level: 2, cell: 0 } + } }, { - id: '0wcYKcGQialFQCGkAa6aYB', t: 'way', v: true, + id: '6', t: 'way', v: true, tf: [[2.7, 0, 5.882], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['32vDSCKBrgMWycW0ySIgsJ', '55g6mUWBdozg4m2ueUEUsy'] } + dt: { in: [], out: [], center: ['5', '7'] } }, { - id: '55g6mUWBdozg4m2ueUEUsy', t: 'way', v: true, + id: '7', t: 'way', v: true, tf: [[2.7, 0, 6.532], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['0wcYKcGQialFQCGkAa6aYB', '5iKoIUBhnU08EM0IsoyOSW'] } + dt: { + in: [], out: [], center: ['6', '8'], + linkStoreAt: { item: 'rack1', bay: 1, level: 1, cell: 0 } + } }, { - id: '5iKoIUBhnU08EM0IsoyOSW', t: 'way', v: true, + id: '8', t: 'way', v: true, tf: [[2.7, 0, 7.75], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['55g6mUWBdozg4m2ueUEUsy'] } + dt: { + in: [], out: [], center: ['7'], + linkStoreAt: { item: 'rack1', bay: 0, level: 2, cell: 0 } + } }, { - id: '3ZP01pHXJRuyeg24oCaaMq', t: 'way', v: true, + id: '17', t: 'way', v: true, tf: [[5.65, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['5onDSGuIKBpUQo6g0EIsuS'] } + dt: { in: [], out: [], center: ['20'] } }, { - id: '5onDSGuIKBpUQo6g0EIsuS', t: 'way', v: true, + id: '20', t: 'way', v: true, tf: [[5.65, 0, 2.865], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['3ZP01pHXJRuyeg24oCaaMq', '41A0CKR8cFW8wKkcSMQ4uk'] } + dt: { + in: [], out: [], center: ['17', '21'], + linkStoreAt: { item: '54', bay: 0, level: 0, cell: 0 } + } }, { - id: '41A0CKR8cFW8wKkcSMQ4uk', t: 'way', v: true, + id: '21', t: 'way', v: true, tf: [[5.65, 0, 3.932], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['5onDSGuIKBpUQo6g0EIsuS', '4PunEz5C3Xk66EaOgMEuMq'] } + dt: { + in: [], out: [], center: ['20', '22'], + linkStoreAt: { item: 'rack1', bay: 3, level: 1, cell: 0 } + } }, { - id: '4PunEz5C3Xk66EaOgMEuMq', t: 'way', v: true, + id: '22', t: 'way', v: true, tf: [[5.65, 0, 4.348], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['41A0CKR8cFW8wKkcSMQ4uk', '6oCW8i0dpRtuCEIWIaAcQi'] } + dt: { + in: [], out: [], center: ['21', '23'], + linkStoreAt: { item: '56', bay: 0, level: 0, cell: 0 } + } }, { - id: '6oCW8i0dpRtuCEIWIaAcQi', t: 'way', v: true, + id: '23', t: 'way', v: true, tf: [[5.65, 0, 5.232], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['4PunEz5C3Xk66EaOgMEuMq', '3C9Z8c6oxQbWcS4uSGkC8b'] } + dt: { + in: [], out: [], center: ['22', '24'], autogyration: true, + linkStoreAt: { item: 'rack1', bay: 2, level: 1, cell: 0 } + } }, { - id: '3C9Z8c6oxQbWcS4uSGkC8b', t: 'way', v: true, + id: '24', t: 'way', v: true, tf: [[5.65, 0, 5.882], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['6oCW8i0dpRtuCEIWIaAcQi', '1jJX8KZLMPSSCwuCOU6AQz'] } + dt: { in: [], out: [], center: ['23', '25'] } }, { - id: '1jJX8KZLMPSSCwuCOU6AQz', t: 'way', v: true, + id: '25', t: 'way', v: true, tf: [[5.65, 0, 6.532], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['3C9Z8c6oxQbWcS4uSGkC8b', '0aJ81sOKqm9FYo60AIQmMG'] } + dt: { + in: [], out: [], center: ['24', '26'], + linkStoreAt: { item: 'rack1', bay: 1, level: 1, cell: 0 } + } }, { - id: '0aJ81sOKqm9FYo60AIQmMG', t: 'way', v: true, + id: '26', t: 'way', v: true, tf: [[5.65, 0, 6.744], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['1jJX8KZLMPSSCwuCOU6AQz', '2qtxSDVn30EcI2uY4W0CWf'] } + dt: { + in: [], out: [], center: ['25', '27'], + linkStoreAt: { item: '58', bay: 0, level: 0, cell: 0 } + } }, { - id: '2qtxSDVn30EcI2uY4W0CWf', t: 'way', v: true, + id: '27', t: 'way', v: true, tf: [[5.65, 0, 7.75], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['0aJ81sOKqm9FYo60AIQmMG'] } + dt: { + in: [], out: [], center: ['26'], + linkStoreAt: { item: 'rack1', bay: 0, level: 1, cell: 0 } + } + }, + { + id: '36', t: 'way', v: true, + tf: [[3.9, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: { + in: [], out: [], center: ['1'], + linkStoreAt: { item: '47', bay: 0, level: 0, cell: 0 } + } + }, + { + id: '38', t: 'way', v: true, + tf: [[1.5, 0, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: { + in: [], out: [], center: ['1'], + linkStoreAt: { item: '49', bay: 0, level: 0, cell: 0 } + } + }, + { + id: '47', t: 'gstore', v: true, + tf: [[3.9, 0, 0.63], [0, 90, 0], [1, 0.01, 1]], + dt: { in: [], out: [], center: [], strokeWidth: 0.1 } + }, + { + id: '49', t: 'gstore', v: true, + tf: [[1.5, 0, 0.63], [0, 90, 0], [1, 0.01, 1]], + dt: { in: [], out: [], center: [], strokeWidth: 0.1 } + }, + + { + id: '54', t: 'gstore', v: true, + tf: [[7.1, 0, 2.865], [0, 0, 0], [1, 0.01, 1]], + dt: { in: [], out: [], center: [], strokeWidth: 0.1 } + }, + { + id: '56', t: 'gstore', v: true, + tf: [[7.1, 0, 4.35], [0, 0, 0], [1, 0.01, 1]], + dt: { in: [], out: [], center: [], strokeWidth: 0.1 } + }, + { + id: '58', t: 'gstore', v: true, + tf: [[7.1, 0, 6.75], [0, 90, 0], [1, 0.01, 1]], + dt: { in: [], out: [], center: [], strokeWidth: 0.1 } }, { id: 'ptr1', t: 'cl2', v: true, @@ -354,7 +394,7 @@ export default { dt: { in: [], out: [], center: [], storeAt: { // 存储位置 - item: '6Vu3dX1V7Si0ISWIiCkoEh', // 库存容器ID + item: '49', // 库存容器ID bay: 0, // 列 level: 0, // 层 cell: 0 // 格 @@ -368,7 +408,7 @@ export default { tf: [[0, 0, 0], [0, 0, 0], [1.2, 0.15, 1]], dt: { in: [], out: [], center: [], - storeAt: { item: '38TYyVWMGLf8OogQMIiSOz', bay: 0, level: 0, cell: 0 } + storeAt: { item: '54', bay: 0, level: 0, cell: 0 } } }, { diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts index de2666c..56516d7 100644 --- a/src/modules/gstore/GstoreRenderer.ts +++ b/src/modules/gstore/GstoreRenderer.ts @@ -51,12 +51,16 @@ export default class GstoreRenderer extends BaseRenderer { return this.pointManager.createByItem(item) } - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { - throw new Error('not allow store line.') + createLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { + // throw new Error('not allow store line.') + } + + createLineBasic(start: ItemJson, end: ItemJson, type: LinkType) { + // throw new Error('not allow store line.') } updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { - throw new Error('not allow store line.') + // throw new Error('not allow store line.') } /** @@ -168,15 +172,9 @@ export default class GstoreRenderer extends BaseRenderer { * 获取物品存放位 * 获取物品存储到地堆货位中,返回可存放的位置和角度一个 Position 和 Rotation */ - getStorePlacement(viewport: Viewport, item: ItemJson): { position: [number, number, number], rotation: [number, number, number] } { - if (!item.dt?.storeAt?.item) { - // 没有定义存储位置,返回空对象 - //@ts-ignore - return {} - } - + getStorePlacement(me: ItemJson, bay = 0, level = 0, cell = 0): { position: [number, number, number], rotation: [number, number, number] } { // 将这个物品添加到库存中 - const me = viewport.entityManager.findItemById(item.dt.storeAt.item) + // const me = viewport.entityManager.findItemById(item.dt.storeAt.item) return { position: me.tf[0], rotation: me.tf[1] } } } diff --git a/src/modules/rack/RackRenderer.ts b/src/modules/rack/RackRenderer.ts index a32bcb0..21c9f4f 100644 --- a/src/modules/rack/RackRenderer.ts +++ b/src/modules/rack/RackRenderer.ts @@ -3,7 +3,7 @@ import BaseRenderer from '@/core/base/BaseRenderer.ts' import { decimalSumBy } from '@/core/ModelUtils' import Constract from '@/core/Constract.ts' import type Viewport from '@/core/engine/Viewport.ts' -import Rack3dObject from "@/modules/rack/Rack3dObject"; +import Rack3dObject from '@/modules/rack/Rack3dObject' /** * 货架货位渲染器 @@ -42,32 +42,35 @@ export default class RackRenderer extends BaseRenderer { ) } + createLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { + // throw new Error('not allow store line.') + } - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { - throw new Error('not allow store line.') + createLineBasic(start: ItemJson, end: ItemJson, type: LinkType) { + // throw new Error('not allow store line.') } updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { - throw new Error('not allow store line.') + // throw new Error('not allow store line.') } /** * 获取物品存放在货架后的位置和旋转角度 * item.dt.storeAt 必须存在, 比如 { item:'货架ID', bay:0, level:1, cell:0 } */ - getStorePlacement(viewport: Viewport, item: ItemJson): { position: [number, number, number], rotation: [number, number, number] } { - if (!item.dt?.storeAt?.item) { - // 没有定义存储位置,返回空对象 - //@ts-ignore - return {} - } - - const bay = item.dt?.storeAt?.bay || 0 - const level = item.dt?.storeAt?.level || 0 + getStorePlacement(rack: ItemJson, bay = 0, level = 0, cell = 0): { position: [number, number, number], rotation: [number, number, number] } { + // if (!item.dt?.storeAt?.item) { + // // 没有定义存储位置,返回空对象 + // //@ts-ignore + // return {} + // } + // + // const bay = item.dt?.storeAt?.bay || 0 + // const level = item.dt?.storeAt?.level || 0 // 暂时不用算格子 const cell = item.dt?.storeAt?.cell || 0 // 目标货架 - const rack = viewport.stateManager.findItemById(item.dt.storeAt.item) + // const rack = viewport.stateManager.findItemById(item.dt.storeAt.item) const rackWidth = decimalSumBy(rack.dt.bays, (b: any) => b.bayWidth) const bays = rack.dt.bays const levelHeights = rack.dt.bays[bay]?.levelHeight @@ -140,7 +143,7 @@ export default class RackRenderer extends BaseRenderer { for (const subItemId of subItems) { const subItem = getEntity(subItemId) if (subItem) { - const { position, rotation } = this.getStorePlacement(viewport, subItem) + const { position, rotation } = this.getStorePlacement(item, subItem.dt.storeAt.bay, subItem.dt.storeAt.level, subItem.dt.storeAt.cell) if (position) { subItem.tf[0][0] = position[0] subItem.tf[0][1] = position[1] diff --git a/src/modules/way/WayRenderer.ts b/src/modules/way/WayRenderer.ts index e538f94..77520ff 100644 --- a/src/modules/way/WayRenderer.ts +++ b/src/modules/way/WayRenderer.ts @@ -1,12 +1,15 @@ import * as THREE from 'three' import BaseRenderer from '@/core/base/BaseRenderer.ts' import MoveLinePointPng from '@/assets/images/moveline_point.png' -import { createLinkPlaneMatrix4, getCargoLineId, getLinkDirection, getMatrixFromTf } from '@/core/ModelUtils.ts' +import { createLinkPlaneMatrix4, getCargoLineId, getLineId, getLinkDirection, getMatrixFromTf } from '@/core/ModelUtils.ts' import Constract from '@/core/Constract.ts' -import type { Object3DLike } from '@/types/ModelTypes.ts' +import type { LineLike, Object3DLike } from '@/types/ModelTypes.ts' import TriangleUrl from '@/assets/images/conveyor/shapes/triangle.png?inline' import Triangle2Url from '@/assets/images/conveyor/shapes/triangle-double.png?inline' import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' +import LineSegmentManager from '@/core/manager/LineSegmentManager.ts' +import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' +import { getRenderer } from '@/core/manager/ModuleManager.ts' /** * AGV行走路线渲染器 point 是二维码站点 @@ -16,6 +19,7 @@ export default class WayRenderer extends BaseRenderer { static POINT_NAME = 'way_point' static LINE_NAME = 'way_line' static GUIDEWAY_LINE_NAME = 'guideway' + static RED_LINE_NAME = 'red_line' pointGeometry: THREE.BufferGeometry pointMaterial: THREE.Material @@ -27,6 +31,7 @@ export default class WayRenderer extends BaseRenderer { // gapSize: 0, // worldUnits: true // }) + redLineMaterial: LineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 1 }) lineGeometry: THREE.BufferGeometry = new THREE.PlaneGeometry(1, 1).rotateX(-Math.PI / 2) lineMaterial = new THREE.MeshBasicMaterial({ color: 0xa0cfff, @@ -103,19 +108,48 @@ export default class WayRenderer extends BaseRenderer { createLine(start: ItemJson, end: ItemJson, type: LinkType): Object3DLike { if (start.t === this.itemTypeName && end.t === this.itemTypeName) { - return this._createOrUpdateGuideway(start, end, type) + // return this._createOrUpdateGuideway(start, end, type) } else { // throw new Error('目前只支持二维码站点之间的连接') + const lineId = getLineId(start.id, end.id, type) + this.getRedLineManager().createOrUpdateLine(lineId, start.tf[0], end.tf[0], '#ff0000') } } updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { if (start.t === this.itemTypeName && end.t === this.itemTypeName) { - this.deleteLine(start, end, type) - this._createOrUpdateGuideway(start, end, type) + // this.deleteLine(start, end, type) + // this._createOrUpdateGuideway(start, end, type) } else { // throw new Error('目前只支持二维码站点之间的连接') + const lineId = getLineId(start.id, end.id, type) + this.getRedLineManager().createOrUpdateLine(lineId, start.tf[0], end.tf[0], '#ff0000') + } + } + + deleteLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { + if ((start.t === this.itemTypeName || start.t === 'unknown') && (end.t === this.itemTypeName || end.t === 'unknown')) { + // 二维码站点之间的连接 + const lineId = getCargoLineId(WayRenderer.GUIDEWAY_LINE_NAME, start.id, end.id) + const object = this.tempViewport.entityManager.findLineObjectById(lineId) + + // 删除箭头 + if (object?.userData.dirWraps) { + object.userData.dirWraps.forEach((uuid: string) => { + this.dirPointManager.delete(uuid) + this.dir2PointManager.delete(uuid) + }) + } + + // 删除这条线 + this.guidewayManager.delete(lineId) + this.tempViewport.entityManager.deleteLineObjectOnly(lineId) + + } else { + // throw new Error('目前只支持二维码站点之间的连接') + const lineId = getLineId(start.id, end.id, type) + this.getRedLineManager().deleteLine(lineId) } } @@ -221,30 +255,42 @@ export default class WayRenderer extends BaseRenderer { } - deleteLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { - if ((start.t === this.itemTypeName || start.t === 'unknown') && (end.t === this.itemTypeName || end.t === 'unknown')) { - // 二维码站点之间的连接 - const lineId = getCargoLineId(WayRenderer.GUIDEWAY_LINE_NAME, start.id, end.id) - const object = this.tempViewport.entityManager.findLineObjectById(lineId) + /** + * 创建或更新点之后的回调 + */ + afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: Object3DLike) { + super.afterCreateOrUpdatePoint(item, option, object) + + // 创建一个 id 标签 + this.tempViewport.labelManager.createOrUpdateMeshLabel(object, '' + item.id, { + name: Constract.WAY_ID_LABEL, + useHtmlLabel: false, + fontSize: 0.3, + color: '#000000', + offset: new THREE.Vector3(0, 5, 0) + }) - // 删除箭头 - if (object?.userData.dirWraps) { - object.userData.dirWraps.forEach((uuid: string) => { - this.dirPointManager.delete(uuid) - this.dir2PointManager.delete(uuid) - }) + // 查看 linkStoreAt 属性 + if (item.dt.linkStoreAt?.item) { + // 如果有 linkStoreAt 属性,则创建一个标签 + const lineId = getLineId(item.id, item.dt.linkStoreAt.item, 'center') + const rack = this.tempViewport.stateManager.findItemById(item.dt.linkStoreAt.item) + if (rack) { + const rackRenderer = getRenderer(rack.t) + if (rackRenderer) { + const { position, rotation } = rackRenderer.getStorePlacement(rack, item.dt.linkStoreAt.bay, item.dt.linkStoreAt.level, item.dt.linkStoreAt.cell) + if (!position || !Array.isArray(position) || position.length < 3) { + console.warn('linkStoreAt position is invalid', item.dt.linkStoreAt) + return + } else { + this.redLineManager.createOrUpdateLine(lineId, item.tf[0], [position[0], 0, position[2]], '#ff0000') + } + } } - - // 删除这条线 - this.guidewayManager.delete(lineId) - this.tempViewport.entityManager.deleteLineObjectOnly(lineId) - - } else { - // throw new Error('目前只支持二维码站点之间的连接') } } - afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) { + afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: LineLike) { const startPosition = new THREE.Vector3(start.tf[0][0], this.defulePositionY, start.tf[0][2]) const endPosition = new THREE.Vector3(end.tf[0][0], this.defulePositionY, end.tf[0][2]) @@ -312,6 +358,17 @@ export default class WayRenderer extends BaseRenderer { this.lineMaterial?.dispose() } + get redLineManager(): LineSegmentManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set.') + } + const name = WayRenderer.RED_LINE_NAME + return this.tempViewport.getOrCreateLineManager(name, () => { + // 构建 LineSegment.points 代理对象 + return new LineSegmentManager(name, this.tempViewport, this.redLineMaterial) + }) + } + get guidewayManager(): InstanceMeshManager { if (!this.tempViewport) { throw new Error('tempViewport is not set.') diff --git a/src/types/model.d.ts b/src/types/model.d.ts index 46d2275..eba6063 100644 --- a/src/types/model.d.ts +++ b/src/types/model.d.ts @@ -230,6 +230,16 @@ interface ItemJson extends ItemMetrix { cell?: number // 货架(地堆货位)的格 } + /** + * 连接存储的位置, 用于在图形编辑器中显示连接线 + */ + linkStoreAt?: { + item: string, // 货架(地堆货位)ID + bay?: number, // 货架(地堆货位)的列 + level?: number // 货架(地堆货位)的层 + cell?: number // 货架(地堆货位)的格 + } + bays?: { bayWidth: number, levelHeight: number[]