diff --git a/src/assets/images/ptr/charger.png b/src/assets/images/ptr/charger.png new file mode 100644 index 0000000..24815f1 Binary files /dev/null and b/src/assets/images/ptr/charger.png differ diff --git a/src/assets/images/ptr/clx.png b/src/assets/images/ptr/clx.png new file mode 100644 index 0000000..bae6acc Binary files /dev/null and b/src/assets/images/ptr/clx.png differ diff --git a/src/assets/images/ptr/ptr.png b/src/assets/images/ptr/ptr.png index b65c957..8da0d6a 100644 Binary files a/src/assets/images/ptr/ptr.png and b/src/assets/images/ptr/ptr.png differ diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 297b984..7befdd4 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -191,7 +191,22 @@ export function escByKeyboard() { return } - viewport.interactionManager.exitInteraction() + if (viewport.interactionManager.currentTool) { + // 1.退出当前交互 + viewport.interactionManager.exitInteraction() + + } else if (viewport.dragControl.isDragging) { + // 2.取消拖拽 + viewport.dragControl.cancelDrag() + + } else if (viewport.state.multiSelectedEntityIds?.length > 0) { + // 3.取消多选 + viewport.selectInspect.cancelMultiSelect() + + } else if (viewport.state.selectedEntityId) { + // 4.取消单选 + viewport.selectInspect.cancelSelect() + } system.msg('操作已取消') } @@ -258,7 +273,7 @@ export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') { // 根据方向移动 switch (direct) { case '↑': - console.log('向上移动', item.tf[0][2],'-=', delta) + console.log('向上移动', item.tf[0][2], '-=', delta) item.tf[0][2] -= delta // 向上移动 break case '↓': diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index 88056a1..1fef5cd 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -124,7 +124,7 @@ export default abstract class BaseRenderer { this.tempViewport.scene.add(...objects) const dragObjects = objects.filter(obj => !!obj.userData.draggable) - this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') + //this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') } removeFromScene(...objects: THREE.Object3D[]) { @@ -133,7 +133,7 @@ export default abstract class BaseRenderer { return } this.tempViewport.scene.remove(...objects) - this.tempViewport.dragControl.setDragObjects(objects, 'remove') + //this.tempViewport.dragControl.setDragObjects(objects, 'remove') } /** diff --git a/src/core/controls/EsDragControl2.ts b/src/core/controls/EsDragControl2.ts new file mode 100644 index 0000000..0bce29a --- /dev/null +++ b/src/core/controls/EsDragControl2.ts @@ -0,0 +1,281 @@ +import * as THREE from 'three' +import type Viewport from '@/core/engine/Viewport.ts' +import type IControls from '@/core/controls/IControls.ts' +import { Curve } from 'three' +import { getClosestObject } from '@/core/ModelUtils.ts' +import EventBus from '@/runtime/EventBus.ts' + +/** + * DragControl2 - ThreeJS 拖拽管理器(仅限 X/Z 平面) + */ +export default class DragControl2 implements IControls { + private viewport: Viewport + private _is_enabled: boolean = true + private domElement: HTMLElement + public isDragging: boolean = false + private isPointerDown: boolean = false + private dragStartMouse: THREE.Vector2 = new THREE.Vector2() + + private dragShadows: THREE.Group | null = null + + init(viewport: Viewport): void { + this.viewport = viewport + + const domElement = this.viewport.renderer.domElement + domElement.addEventListener('pointermove', this.onPointerMove) + domElement.addEventListener('pointerdown', this.onPointerDown) + domElement.addEventListener('pointerup', this.onPointerUp) + domElement.addEventListener('pointerleave', this.onPointerLeave) + + domElement.style.cursor = 'auto' + this.domElement = domElement + } + + + /** + * 设置启用/禁用 + */ + public set enabled(value: boolean) { + this._is_enabled = value + if (!value) { + this.cleanupDrag() + } + } + + public get enabled(): boolean { + return this._is_enabled + } + + /** + * 卸载资源 + */ + dispose(): void { + if (this.domElement) { + this.domElement.removeEventListener('pointermove', this.onPointerMove) + this.domElement.removeEventListener('pointerdown', this.onPointerDown) + this.domElement.removeEventListener('pointerup', this.onPointerUp) + this.domElement.removeEventListener('pointerleave', this.onPointerLeave) + + this.cleanupDrag() + this.domElement.style.cursor = 'auto' + this.viewport = null + } + } + + /** + * 清理拖拽状态 + */ + private cleanupDrag(): void { + if (this.domElement) { + this.domElement.style.cursor = 'auto' + this.isDragging = false + this.isPointerDown = false + this.dragStartMouse.set(NaN, NaN) + this.removeShadows() + } + } + + /** + * 获取当前鼠标坐标(归一化设备坐标) + */ + private getMousePosition(clientX: number, clientY: number): THREE.Vector2 { + const rect = this.domElement.getBoundingClientRect() + return new THREE.Vector2( + ((clientX - rect.left) / rect.width) * 2 - 1, + ((clientY - rect.top) / rect.height) * -2 + 1 + ) + } + + /** + * 射线检测,返回第一个可拖拽对象 + */ + private getIntersectedDraggableObject(mouse: THREE.Vector2): THREE.Object3D | null { + const raycaster = new THREE.Raycaster() + raycaster.setFromCamera(mouse, this.viewport.camera) + + const draggableObjects = this.viewport.entityManager._draggableObjects || [] + const intersects = raycaster.intersectObjects(draggableObjects, true) + + if (intersects.length > 0) { + return getClosestObject(intersects[0].object) + } + } + + /** + * 创建拖拽阴影 + */ + private createShadows(objects: THREE.Object3D[]): void { + this.removeShadows() + + console.log('createShadows', objects.map(obj => obj.name)) + + this.dragShadows = new THREE.Group() + for (const obj of objects) { + if (!obj.userData.entityId) { + console.error('Object does not have entityId:', obj.name) + continue // 跳过没有 entityId 的对象 + } + + const shadow = obj.clone() + + shadow.userData = { + isShadow: true, + entityId: obj.userData.entityId, + originPosition: obj.position.clone() // 保存原始位置 + } + this.dragShadows.add(shadow) + } + this.viewport.scene.add(this.dragShadows) + } + + /** + * 移除阴影 + */ + private removeShadows(): void { + if (this.dragShadows) { + this.viewport.scene.remove(this.dragShadows) + this.dragShadows = null + } + } + + /** + * 更新阴影位置(仅 X/Z 平面) + */ + private updateShadows(newPosition: THREE.Vector2): void { + if (!this.dragShadows) return + + // 计算新位置与拖拽开始位置的偏移量 + const offsetX = newPosition.x - this.dragStartMouse.x + const offsetZ = newPosition.y - this.dragStartMouse.y + + this.dragShadows.children.forEach((shadow, index) => { + const newPosX = shadow.userData.originPosition.x + offsetX + const newPosZ = shadow.userData.originPosition.z + offsetZ + + shadow.position.set(newPosX, shadow.userData.originPosition.y, newPosZ) // 锁定 Y 轴高度 + console.log(`Updated shadow position for ${shadow.name}:`, shadow.position) + }) + } + + private dragComplete = (startPos: THREE.Vector2, targetPos: THREE.Vector2): void => { + // console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`) + this.viewport.stateManager.beginStateUpdate() + for (const object of this.dragShadows.children) { + const entityId = object.userData.entityId + if (entityId) { + const entity = this.viewport.stateManager.findItemById(entityId) + if (entity) { + // 更新实体位置 + entity.tf[0][0] = object.position.x + entity.tf[0][2] = object.position.z + + } else { + system.showErrorDialog('not found entityId:' + entityId) + } + } else { + system.showErrorDialog('not found entity') + } + } + this.viewport.stateManager.endStateUpdate() + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: this.viewport.state.multiSelectedObjects + }) + EventBus.dispatch('selectedObjectPropertyChanged', {}) + + this.cleanupDrag() + } + + /** + * pointermove 事件处理 + */ + private onPointerMove = (event: PointerEvent): void => { + if (!this.enabled || !this.domElement) return + + if (!isNaN(this.dragStartMouse.x) && !isNaN(this.dragStartMouse.y) && this.dragShadows) { + this.isDragging = true + // 更新鼠标样式 + this.domElement.style.cursor = 'grabbing' + + // 更新阴影位置(锁定 Y 轴) + this.updateShadows(new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z)) + + } else { + + const mouse = this.getMousePosition(event.clientX, event.clientY) + const intersected = this.getIntersectedDraggableObject(mouse) + if (intersected) { + this.domElement.style.cursor = 'grab' + } else { + this.domElement.style.cursor = 'auto' + } + } + } + + /** + * pointerdown 事件处理 + */ + private onPointerDown = (event: PointerEvent): void => { + if (!this.enabled) return + + const mouse = this.getMousePosition(event.clientX, event.clientY) + const intersected = this.getIntersectedDraggableObject(mouse) + + if (!intersected) return + + this.isPointerDown = true + this.dragStartMouse.set(intersected.position.x, intersected.position.z) + this.viewport.controls.enabled = false + + let selectedObjects = [intersected] + if (this.viewport.state.multiSelectedObjects.length > 0) { + if (this.viewport.state.multiSelectedObjects.includes(intersected)) { + // drag multi-selected objects + selectedObjects = this.viewport.state.multiSelectedObjects + } + } + + this.createShadows(selectedObjects) + } + + + /** + * pointerup 事件处理 + */ + private onPointerUp = (event: PointerEvent): void => { + if (!this.enabled) return + + this.dragStartMouse.set(NaN, NaN) + this.isPointerDown = false + // console.log('enable controls') + this.viewport.controls.enabled = true + + if (this.isDragging) { + const startPos = this.dragStartMouse + const targetPos = new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z) + + if (startPos && targetPos) { + this.dragComplete(startPos, targetPos) + } + } + } + + /** + * pointerleave 事件处理 + */ + private onPointerLeave = (_event: PointerEvent): void => { + if (!this.enabled) return + this.cleanupDrag() + } + + /** + * 取消当前拖拽状态 + */ + cancelDrag(): void { + if (!this.domElement) return + if (this.isDragging || this.isPointerDown) { + this.cleanupDrag() + this.domElement.style.cursor = 'auto' + this.viewport.controls.enabled = true + } + } +} diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index cf74775..d2d85d2 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -351,7 +351,7 @@ export default class SelectInspect implements IControls { selectedObject: this.viewport.state.selectedObject, selectedItem: this.viewport.state.selectedItem, selectedEntityId: this.viewport.state.selectedEntityId, - selectedObjectSetter: this.viewport.state.selectedObjectSetter, + selectedObjectSetter: this.viewport.state.selectedObjectSetter }) } } else { @@ -365,7 +365,7 @@ export default class SelectInspect implements IControls { selectedObject: null, selectedItem: null, selectedEntityId: null, - selectedObjectSetter: null, + selectedObjectSetter: null }) } } @@ -421,4 +421,31 @@ export default class SelectInspect implements IControls { } + cancelMultiSelect() { + this.viewport.state.multiSelectedObjects = [] + this.viewport.state.multiSelectedItems = [] + this.viewport.state.multiSelectedEntityIds = [] + this.viewport.state.multiSelectedObjectMetas = [] + EventBus.dispatch('multiSelectedObjectsChanged', { + viewport: markRaw(this.viewport), + multiSelectedObjects: [], + multiSelectedItems: [], + multiSelectedEntityIds: [], + multiSelectedObjectMetas: [] + }) + } + + cancelSelect() { + this.viewport.state.selectedObject = null + this.viewport.state.selectedItem = null + this.viewport.state.selectedEntityId = null + this.viewport.state.selectedObjectSetter = null + EventBus.dispatch('selectedObjectChanged', { + viewport: markRaw(this.viewport), + selectedObject: null, + selectedItem: null, + selectedEntityId: null, + selectedObjectSetter: null + }) + } } diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 01ffd67..b71292e 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -22,7 +22,8 @@ import StateManager from '@/core/manager/StateManager.ts' import EventBus from '@/runtime/EventBus.ts' import Constract from '@/core/Constract.ts' import type { IMeta } from '@/core/base/IMeta.ts' -import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; +import DragControl2 from '@/core/controls/EsDragControl2.ts' +import type { PropertySetter } from '@/core/base/PropertyTypes.ts' /** * 视窗对象 @@ -35,15 +36,17 @@ export default class Viewport { statsControls: Stats controls: OrbitControls raycaster: THREE.Raycaster - dragControl: any // EsDragControls + // dragControl: any // EsDragControls animationFrameId: any = null scene: SceneHelp selectInspect = new SelectInspect() mouseMoveInspect = new MouseMoveInspect() + dragControl = new DragControl2() tools: IControls[] = [ markRaw(this.selectInspect), - markRaw(this.mouseMoveInspect) + markRaw(this.mouseMoveInspect), + markRaw(this.dragControl) ] // 状态管理器 @@ -156,18 +159,9 @@ export default class Viewport { this.renderer = renderer - // 创建正交摄像机 - // this.initMode2DCamera() - this.watchList.push(watch(() => this.state.view3DMode, (newVal) => { - if (newVal === Constract.Mode3D) { - this.initMode3DCamera() - } else { - this.initMode2DCamera() - } - }, { immediate: true })) // 注册拖拽组件 - this.dragControl = new EsDragControls(this) + // this.dragControl = new EsDragControls(this) // 性能监控 const statsControls = new Stats() @@ -203,6 +197,16 @@ export default class Viewport { tool.init(this) } + // 创建正交摄像机 + // this.initMode2DCamera() + this.watchList.push(watch(() => this.state.view3DMode, (newVal) => { + if (newVal === Constract.Mode3D) { + this.initMode3DCamera() + } else { + this.initMode2DCamera() + } + }, { immediate: true })) + // 触发所有物品类型的 afterAddViewport 方法 _.forEach(getAllItemTypes(), (itemType: ItemTypeDefineOption) => { itemType.clazz.afterAddViewport(this) @@ -458,6 +462,15 @@ export default class Viewport { dispose() { this.state.isReady = false + if (this.tools) { + for (const tool of this.tools) { + if (tool.dispose) { + tool.dispose() + } + } + this.tools = [] + } + if (this.animationFrameId !== null) { cancelAnimationFrame(this.animationFrameId) this.animationFrameId = null @@ -472,14 +485,6 @@ export default class Viewport { this.watchList = [] } - if (this.tools) { - for (const tool of this.tools) { - if (tool.dispose) { - tool.dispose() - } - } - this.tools = [] - } if (this.resizeObserver) { this.resizeObserver.unobserve(this.viewerDom) diff --git a/src/core/manager/InteractionManager.ts b/src/core/manager/InteractionManager.ts index a846d21..b7991c8 100644 --- a/src/core/manager/InteractionManager.ts +++ b/src/core/manager/InteractionManager.ts @@ -53,7 +53,7 @@ export default class InteractionManager implements IControls { } this.viewport.state.cursorMode = 'normal' - this.viewport.dragControl.dragControls.enabled = true + this.viewport.dragControl.enabled = true this.viewport.viewerDom.style.cursor = '' this.option = undefined @@ -90,7 +90,7 @@ export default class InteractionManager implements IControls { // 初始化交互 this.currentTool = interaction - this.viewport.dragControl.dragControls.enabled = false + this.viewport.dragControl.enabled = false this.currentTool.start(this.viewport, this.option) @@ -103,4 +103,4 @@ export default class InteractionManager implements IControls { dispose(): void { this.exitInteraction() } -} \ No newline at end of file +} diff --git a/src/core/manager/WorldModel.ts b/src/core/manager/WorldModel.ts index f9666ae..0289fdf 100644 --- a/src/core/manager/WorldModel.ts +++ b/src/core/manager/WorldModel.ts @@ -7,6 +7,8 @@ import Gstore from '@/modules/gstore' import Rack from '@/modules/rack' import Pallet from "@/modules/pallet" import Ptr from "@/modules/ptr" +import Clx from "@/modules/clx" +import Charger from "@/modules/charger" import StateManager from '@/core/manager/StateManager.ts' export interface WorldModelState { @@ -70,7 +72,9 @@ export default class WorldModel { Gstore, Rack, Pallet, - Ptr + Ptr, + Clx, + Charger ]).then(() => { console.log('世界模型初始化完成') diff --git a/src/example/example1.js b/src/example/example1.js index 32b0ea3..dfa7bd5 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -7,10 +7,10 @@ export default { Dashboard: [], // 监控面板 DataTable: [], // 地图自带的数据 Trigger: [ // 触发器 - { name: 'OnOpen', fn: '' }, // 打开 - { name: 'OnReset', fn: '' }, // 仿真重置 - { name: 'OnStart', fn: '' }, // 开始仿真 - { name: 'OnStop', fn: '' } // 停止仿真 + {name: 'OnOpen', fn: ''}, // 打开 + {name: 'OnReset', fn: ''}, // 仿真重置 + {name: 'OnStart', fn: ''}, // 开始仿真 + {name: 'OnStop', fn: ''} // 停止仿真 ], gridHelper: { // 网格辅助线 axesEnabled: true, // 是否显示中心轴 @@ -93,11 +93,11 @@ export default { bays: [ // 每列的配置 { bayWidth: 1.4, // 列的宽度 - levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度 + levelHeight: [1.4, 1.4, 1.4] // 每层的高度 }, - {bayWidth: 1.4, levelHeight: [ 1.4, 1.4, 1.4 ]}, - {bayWidth: 1.4, levelHeight: [ 1.4, 1.4, 1.4 ]}, - {bayWidth: 1.4, levelHeight: [ 1.4, 1.4, 1.4 ]}, + {bayWidth: 1.4, levelHeight: [1.4, 1.4, 1.4]}, + {bayWidth: 1.4, levelHeight: [1.4, 1.4, 1.4]}, + {bayWidth: 1.4, levelHeight: [1.4, 1.4, 1.4]}, ] } }, @@ -106,87 +106,258 @@ export default { t: 'measure', v: true, tf: [[-4, 0.1, 4], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['P2'] } + dt: {in: [], out: [], center: ['P2']} }, { id: 'P2', t: 'measure', v: true, tf: [[5, 0.1, 4], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['P1', 'P3'] } + dt: {in: [], out: [], center: ['P1', 'P3']} }, { id: 'P3', t: 'measure', v: true, tf: [[5, 0.1, 6.25], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['P2'] } + dt: {in: [], out: [], center: ['P2']} }, { id: '39zML1rnSOOQGQYQ2YUMGy', t: 'way', v: true, tf: [[-4, 0.1, 2], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['6wrGKiVJniwgKkoggOoEy6'] } + dt: {in: [], out: [], center: ['6wrGKiVJniwgKkoggOoEy6']} }, { id: '6wrGKiVJniwgKkoggOoEy6', t: 'way', v: true, tf: [[5, 0.1, 2], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['39zML1rnSOOQGQYQ2YUMGy'] } + dt: {in: [], out: [], center: ['39zML1rnSOOQGQYQ2YUMGy']} }, { id: '6UhIIw9QPYh6acwyW8OSGs', t: 'gstore', v: true, tf: [[-1, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } + dt: {in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4} }, { id: '1D0WSRPj8JJJwIcmA0UMqG', t: 'gstore', v: true, tf: [[0.75, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } + dt: {in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4} }, { id: 'gstore3', t: 'gstore', v: true, tf: [[3, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } - },{ + dt: {in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4} + }, { id: 'pallet1', t: 'pallet', v: true, tf: [[0.75, 0.075, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} }, { id: 'pallet2', t: 'pallet', v: true, tf: [[3, 0.075, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} }, { id: 'ptr1', t: 'ptr', v: true, tf: [[0.75, 0.075, 2], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], ptrWidth: 1.5, ptrDepth: 1.5, ptrHeight: 1.98 } + dt: {in: [], out: [], center: [], ptrWidth: 1.5, ptrDepth: 1.5, ptrHeight: 1.98} }, { id: 'ptr2', t: 'ptr', v: true, tf: [[3, 0.075, 2], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], ptrWidth: 1.5, ptrDepth: 1.5, ptrHeight: 1.98 } + dt: {in: [], out: [], center: [], ptrWidth: 1.5, ptrDepth: 1.5, ptrHeight: 1.98} }, { id: 'pallet3', t: 'pallet', v: true, - tf: [[3, 0.175, 1.9], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } + tf: [[3, 0.175, 1.88], [0, 0, 0], [1.5, 1.2, 0.1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} }, { id: 'pallet4', t: 'pallet', v: true, tf: [[0.75, 0.175, 3.5], [0, 0, 0], [1.5, 1.2, 0.1]], - dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} } ] + }, + { + catalogCode: 'f2', t: 'floor', // 楼层 + items: [{ + id: 'rack1', + t: 'rack', + v: true, + tf: [[4.196, 0.1, 5.882], [0, 270, 0], [1, 1, 1]], + dt: { + rackDepth: 1, + rackWidth: 5.1, + rackHeight: 4.2, + levelCount: 3, + bayCount: 4, + hideFloor: false, + extendColumns: true, + columnSpacing: 1, + bays: [ + {bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4]}, + {bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4]}, + {bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4]}, + {bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4]}], + center: [], + in: [], + out: [] + } + }, {id: '3ExXFSuV9WB2WMY2Quyq6L', t: 'measure', v: true, tf: [[0, 0.1, 0], [90, 0, 0], [0.1, 0.1, 0.1]], dt: {in: [], out: [], center: ['4YZKo6XtvL2migaIUmakAr', '7Ik2sRdDZy8Q6sC00AaqCv']}}, { + id: '3ExXFSuV9WB2WMY2Quyq6L', + t: 'measure', + v: true, + tf: [[0, 0.1, 0], [90, 0, 0], [0.1, 0.1, 0.1]], + dt: {in: [], out: [], center: ['4YZKo6XtvL2migaIUmakAr', '7Ik2sRdDZy8Q6sC00AaqCv']} + }, {id: '4YZKo6XtvL2migaIUmakAr', t: 'measure', v: true, tf: [[8.4, 0.1, 0], [90, 0, 0], [0.1, 0.1, 0.1]], dt: {in: [], out: [], center: ['3ExXFSuV9WB2WMY2Quyq6L', '3cdb6OHkp132soSsgW8McA']}}, { + id: '3cdb6OHkp132soSsgW8McA', + t: 'measure', + v: true, + tf: [[8.4, 0.1, 9], [90, 0, 0], [0.1, 0.1, 0.1]], + dt: {in: [], out: [], center: ['4YZKo6XtvL2migaIUmakAr', '7Ik2sRdDZy8Q6sC00AaqCv']} + }, {id: '7Ik2sRdDZy8Q6sC00AaqCv', t: 'measure', v: true, tf: [[0, 0.1, 9], [90, 0, 0], [0.1, 0.1, 0.1]], dt: {in: [], out: [], center: ['3cdb6OHkp132soSsgW8McA', '3ExXFSuV9WB2WMY2Quyq6L']}}, { + id: '6Vu3dX1V7Si0ISWIiCkoEh', + t: 'gstore', + v: true, + tf: [[1.5, 0.1, 0.63], [0, 0, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], storeWidth: 1.1, storeDepth: 1.3} + }, {id: '592UY0EMScbwIyQqgs8aAs', t: 'gstore', v: true, tf: [[3.9, 0.1, 0.63], [0, 0, 0], [1, 1, 1]], dt: {in: [], out: [], center: [], storeWidth: 1.1, storeDepth: 1.3}}, { + id: '38TYyVWMGLf8OogQMIiSOz', + t: 'gstore', + v: true, + tf: [[7.1, 0.1, 2.865], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], storeWidth: 1.1, storeDepth: 1.3} + }, {id: '1hAaZ1xtvukZowAKeWAcqs', t: 'gstore', v: true, tf: [[7.1, 0.1, 4.35], [0, 90, 0], [1, 1, 1]], dt: {in: [], out: [], center: [], storeWidth: 1.1, storeDepth: 1.3}}, { + id: '28GxDYUqDwZc2WsOgMU2wi', + t: 'gstore', + v: true, + tf: [[7.1, 0.1, 6.75], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], storeWidth: 1.1, storeDepth: 1.3} + }, {id: '2fWOnUmFpvYyCWEqAyU0QC', t: 'way', v: true, tf: [[1.5, 0.1, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r']}}, { + id: '09PTEMUnACWY0MUG4qmk0r', + t: 'way', + v: true, + tf: [[2.7, 0.1, 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.1, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r']}}, { + id: '0mVU9FacN1fmCAmQqwWgIZ', + t: 'way', + v: true, + tf: [[2.7, 0.1, 2.832], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['09PTEMUnACWY0MUG4qmk0r', '7LuzEYQQI7OQcEUekEqWcm']} + }, {id: '7LuzEYQQI7OQcEUekEqWcm', t: 'way', v: true, tf: [[2.7, 0.1, 3.932], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['0mVU9FacN1fmCAmQqwWgIZ', '2RForJhOHXtcw0gq8mYAMh']}}, { + id: '2RForJhOHXtcw0gq8mYAMh', + t: 'way', + v: true, + tf: [[2.7, 0.1, 4.582], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['7LuzEYQQI7OQcEUekEqWcm', '32vDSCKBrgMWycW0ySIgsJ']} + }, {id: '32vDSCKBrgMWycW0ySIgsJ', t: 'way', v: true, tf: [[2.7, 0.1, 5.232], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['2RForJhOHXtcw0gq8mYAMh', '0wcYKcGQialFQCGkAa6aYB']}}, { + id: '0wcYKcGQialFQCGkAa6aYB', + t: 'way', + v: true, + tf: [[2.7, 0.1, 5.882], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['32vDSCKBrgMWycW0ySIgsJ', '55g6mUWBdozg4m2ueUEUsy']} + }, {id: '55g6mUWBdozg4m2ueUEUsy', t: 'way', v: true, tf: [[2.7, 0.1, 6.532], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['0wcYKcGQialFQCGkAa6aYB', '5iKoIUBhnU08EM0IsoyOSW']}}, { + id: '5iKoIUBhnU08EM0IsoyOSW', + t: 'way', + v: true, + tf: [[2.7, 0.1, 7.632], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['55g6mUWBdozg4m2ueUEUsy']} + }, { + id: '3ZP01pHXJRuyeg24oCaaMq', + t: 'way', + v: true, + tf: [[5.65, 0.1, 2.13], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['5onDSGuIKBpUQo6g0EIsuS']} + }, {id: '5onDSGuIKBpUQo6g0EIsuS', t: 'way', v: true, tf: [[5.65, 0.1, 2.865], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['3ZP01pHXJRuyeg24oCaaMq', '41A0CKR8cFW8wKkcSMQ4uk']}}, { + id: '41A0CKR8cFW8wKkcSMQ4uk', + t: 'way', + v: true, + tf: [[5.65, 0.1, 3.932], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['5onDSGuIKBpUQo6g0EIsuS', '4PunEz5C3Xk66EaOgMEuMq']} + }, {id: '4PunEz5C3Xk66EaOgMEuMq', t: 'way', v: true, tf: [[5.65, 0.1, 4.348], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['41A0CKR8cFW8wKkcSMQ4uk', '6oCW8i0dpRtuCEIWIaAcQi']}}, { + id: '6oCW8i0dpRtuCEIWIaAcQi', + t: 'way', + v: true, + tf: [[5.65, 0.1, 5.232], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['4PunEz5C3Xk66EaOgMEuMq', '3C9Z8c6oxQbWcS4uSGkC8b']} + }, {id: '3C9Z8c6oxQbWcS4uSGkC8b', t: 'way', v: true, tf: [[5.65, 0.1, 5.882], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['6oCW8i0dpRtuCEIWIaAcQi', '1jJX8KZLMPSSCwuCOU6AQz']}}, { + id: '1jJX8KZLMPSSCwuCOU6AQz', + t: 'way', + v: true, + tf: [[5.65, 0.1, 6.532], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['3C9Z8c6oxQbWcS4uSGkC8b', '0aJ81sOKqm9FYo60AIQmMG']} + }, {id: '0aJ81sOKqm9FYo60AIQmMG', t: 'way', v: true, tf: [[5.65, 0.1, 6.744], [90, 0, 0], [0.25, 0.25, 0.1]], dt: {in: [], out: [], center: ['1jJX8KZLMPSSCwuCOU6AQz', '2qtxSDVn30EcI2uY4W0CWf']}}, { + id: '2qtxSDVn30EcI2uY4W0CWf', + t: 'way', + v: true, + tf: [[5.65, 0.1, 7.632], [90, 0, 0], [0.25, 0.25, 0.1]], + dt: {in: [], out: [], center: ['0aJ81sOKqm9FYo60AIQmMG']} + }, { + id: 'ptr1', + t: 'ptr', + v: true, + tf: [[5.65, 0.1, 2.865], [0, -90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], ptrWidth: 1.5, ptrDepth: 1.5, ptrHeight: 1.98} + }, { + id: 'clx1', + t: 'clx', + v: true, + tf: [[1.5, 0.1, 2.13], [0, 0, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], clxWidth: 1.65, clxDepth: 1.65, clxHeight: 3.393} + }, { + id: 'charger1', + t: 'charger', + v: true, + tf: [[2.696, 0.1, 8.75], [0, 0, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], chargerWidth: 0.53, chargerDepth: 0.275, clxHeight: 0.3} + }, { + id: 'charger2', + t: 'charger', + v: true, + tf: [[5.655, 0.1, 8.75], [0, 0, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], chargerWidth: 0.53, chargerDepth: 0.275, clxHeight: 0.3} + }, { + id: 'pallet1122', + t: 'pallet', + v: true, + tf: [[1.5, 0.1, 0.63], [0, 0, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} + }, { + id: 'pallet1123', + t: 'pallet', + v: true, + tf: [[7.100, 0.1, 4.35], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} + }, { + id: 'pallet1124', + t: 'pallet', + v: true, + tf: [[4.196, 0.1, 3.95], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} + }, { + id: 'pallet1125', + t: 'pallet', + v: true, + tf: [[4.196, 0.1, 5.225], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} + }, { + id: 'pallet1126', + t: 'pallet', + v: true, + tf: [[4.196, 0.1, 7.775], [0, 90, 0], [1, 1, 1]], + dt: {in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2} + }] } ], elevator: [], // 电梯 @@ -196,41 +367,41 @@ export default { { label: '仓库楼层', // 目录分组名 items: [ - { catalogCode: '-f1', label: '地下室 (-f1)' }, // 目录项 - { catalogCode: 'f1', label: '一楼 (f1)' }, - { catalogCode: 'f2', label: '二楼 (f2)' }, - { catalogCode: 'OUT', label: '外场 (OUT)' }, - { catalogCode: 'fe', label: '楼层电梯 (fe)' } + {catalogCode: '-f1', label: '地下室 (-f1)'}, // 目录项 + {catalogCode: 'f1', label: '一楼 (f1)'}, + {catalogCode: 'f2', label: '二楼 (f2)'}, + {catalogCode: 'OUT', label: '外场 (OUT)'}, + {catalogCode: 'fe', label: '楼层电梯 (fe)'} ] }, { label: '密集库区域', items: [ - { catalogCode: 'm1', label: 'M1 (m1)' }, - { catalogCode: 'm2', label: 'M2 (m2)' }, - { catalogCode: 'm3', label: 'M3 (m3)' }, - { catalogCode: 'm4', label: 'M4 (m4)' }, - { catalogCode: 'me', label: '提升机 (me)' } + {catalogCode: 'm1', label: 'M1 (m1)'}, + {catalogCode: 'm2', label: 'M2 (m2)'}, + {catalogCode: 'm3', label: 'M3 (m3)'}, + {catalogCode: 'm4', label: 'M4 (m4)'}, + {catalogCode: 'me', label: '提升机 (me)'} ] }, { label: '多穿库A', items: [ - { catalogCode: 'd1', label: 'D1 (d1)' }, - { catalogCode: 'd2', label: 'D2 (d2)' }, - { catalogCode: 'd3', label: 'D3 (d3)' }, - { catalogCode: 'd4', label: 'D4 (d4)' }, - { catalogCode: 'de1', label: '提升机 (de1)' } + {catalogCode: 'd1', label: 'D1 (d1)'}, + {catalogCode: 'd2', label: 'D2 (d2)'}, + {catalogCode: 'd3', label: 'D3 (d3)'}, + {catalogCode: 'd4', label: 'D4 (d4)'}, + {catalogCode: 'de1', label: '提升机 (de1)'} ] }, { label: '多穿库B', items: [ - { catalogCode: 'e1', label: 'E1 (e1)' }, - { catalogCode: 'e2', label: 'E2 (e2)' }, - { catalogCode: 'e3', label: 'E3 (e3)' }, - { catalogCode: 'e4', label: 'E4 (e4)' }, - { catalogCode: 'ee1', label: '提升机 (ee1)' } + {catalogCode: 'e1', label: 'E1 (e1)'}, + {catalogCode: 'e2', label: 'E2 (e2)'}, + {catalogCode: 'e3', label: 'E3 (e3)'}, + {catalogCode: 'e4', label: 'E4 (e4)'}, + {catalogCode: 'ee1', label: '提升机 (ee1)'} ] } ] diff --git a/src/model/itemType/ItemType.ts b/src/model/itemType/ItemType.ts index ed1eb1a..220e07b 100644 --- a/src/model/itemType/ItemType.ts +++ b/src/model/itemType/ItemType.ts @@ -60,7 +60,7 @@ export default abstract class ItemType { * 添加到 viewport 后的回调 */ afterAddViewport(viewport: Viewport): void { - viewport.dragControl.setDragObjects(this.pointArray, 'push') + //viewport.dragControl.setDragObjects(this.pointArray, 'push') const toolbox = this.createToolbox(viewport) viewport.toolbox[this.name] = toolbox } @@ -74,4 +74,4 @@ export default abstract class ItemType { * EsDragControls 拖拽完成(放开鼠标)时的回调 */ abstract dragPointComplete(viewport: Viewport) -} \ No newline at end of file +} diff --git a/src/model/itemType/Toolbox.ts b/src/model/itemType/Toolbox.ts index caae638..fd9845e 100644 --- a/src/model/itemType/Toolbox.ts +++ b/src/model/itemType/Toolbox.ts @@ -290,7 +290,7 @@ export default abstract class Toolbox { this.addToScene(marker) // 把点加入拖拽控制器 - this.viewport.dragControl.setDragObjects([marker], 'push') + //this.viewport.dragControl.setDragObjects([marker], 'push') if (this.startPoint) { this.afterAddPoint(this.startPoint, marker) @@ -320,4 +320,4 @@ export default abstract class Toolbox { */ destory() { } -} \ No newline at end of file +} diff --git a/src/modules/charger/ChargerEntity.ts b/src/modules/charger/ChargerEntity.ts new file mode 100644 index 0000000..dc7878c --- /dev/null +++ b/src/modules/charger/ChargerEntity.ts @@ -0,0 +1,5 @@ +import BaseEntity from '@/core/base/BaseItemEntity.ts' + +export default class ChargerEntity extends BaseEntity { + +} diff --git a/src/modules/charger/ChargerInteraction.ts b/src/modules/charger/ChargerInteraction.ts new file mode 100644 index 0000000..28b4173 --- /dev/null +++ b/src/modules/charger/ChargerInteraction.ts @@ -0,0 +1,22 @@ +import BaseInteraction from '@/core/base/BaseInteraction.ts' +import * as THREE from 'three' + +export default class ChargerInteraction extends BaseInteraction { + + get isSinglePointMode(): boolean { + return true + } + + constructor(itemTypeName: string) { + super(itemTypeName) + } + + createPointOfItem(item: ItemJson, point: THREE.Vector3): ItemJson { + item = super.createPointOfItem(item, point) + + // 创建一个地堆货架 + item.dt.palletWidth = 1 // 宽度 + item.dt.palletDepth = 1.2 // 深度 + return item + } +} diff --git a/src/modules/charger/ChargerMeta.ts b/src/modules/charger/ChargerMeta.ts new file mode 100644 index 0000000..cbf8f74 --- /dev/null +++ b/src/modules/charger/ChargerMeta.ts @@ -0,0 +1,49 @@ +import type { IMeta } from '@/core/base/IMeta.ts' + +export default [ + { field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, + { field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, + { field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, + { editor: 'TransformEditor', category: 'basic' }, + { field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, + { editor: '-', category: 'basic' }, + + { field: 'dt.chargerWidth', editor: 'NumberInput', label: '充电桩宽度', category: 'basic' }, + { field: 'dt.chargerDepth', editor: 'NumberInput', label: '充电桩深度', category: 'basic' }, + /** + * dt.bays 5列3层货架示例 + * { + * dt: { + * rackDepth: 1.1, // 货架深度 + * levelCount: 3, // 总层数 + * bayCount: 5, // 总列数 + * hideFloor: false, // 隐藏底板 + * extendColumns: true, // 扩展挡板 + * columnSpacing: 1, // 支脚跨越 + * bays: [ // 每列的配置 + * { + * bayWidth: 1.6, // 列的宽度 + * levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度 + * }, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * ] + * } + * } + * + * + * + * + * + * + * + */ + + + { field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, + { field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, + { field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, + { field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } +] as IMeta diff --git a/src/modules/charger/ChargerRenderer.ts b/src/modules/charger/ChargerRenderer.ts new file mode 100644 index 0000000..869b18b --- /dev/null +++ b/src/modules/charger/ChargerRenderer.ts @@ -0,0 +1,97 @@ +import * as THREE from 'three' +import BaseRenderer from '@/core/base/BaseRenderer.ts' +import { Line2 } from 'three/examples/jsm/lines/Line2.js' +import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' +import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' +import {decimalSumBy} from "@/core/ModelUtils"; +import Constract from '@/core/Constract.ts' +import chargerUrl from '@/assets/images/ptr/charger.png' + +/** + * 充电器渲染器 + */ +export default class ChargerRenderer extends BaseRenderer { + static POINT_NAME = 'charger' + + pointMaterial: THREE.Material + + /** + * 默认点的高度, 防止和地面重合 + */ + readonly defulePositionY: number = Constract.HEIGHT_WAY + readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1) + readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) + readonly defaultLineWidth: number = 0.15 + + constructor(itemTypeName: string) { + super(itemTypeName) + } + + /** + * 所有的点,必须使用 storeWidth/storeDepth, 改TF无效 + */ + override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, objects: THREE.Object3D[]) { + super.afterCreateOrUpdatePoint(item, option, objects) + + const point = objects[0] + // point.position.y = this.defulePositionY + // point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) + point.rotation.set( + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) + ) + } + + + createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { + throw new Error('not allow store line.') + } + + updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { + throw new Error('not allow store line.') + } + + + createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { + // 创建平面几何体 + if (!item.dt.chargerWidth || !item.dt.chargerDepth) { + return [] + } + + const textureLoader = new THREE.TextureLoader() + const texture = textureLoader.load(chargerUrl) + + const group = new THREE.Group() + group.name = ChargerRenderer.POINT_NAME + + // 绘制背景矩形框 + const planeGeometry = new THREE.PlaneGeometry(item.dt.chargerWidth, item.dt.chargerDepth); + planeGeometry.rotateX(-Math.PI / 2) + const planeMaterial = new THREE.MeshLambertMaterial({ + map: texture, // 颜色贴图 + transparent: true, // 允许透明纹理 + }); + const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); + group.add(planeMesh) + + // 设置位置 + group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) + + const points = [group] + this.fillObjectUserDataFromItem(item, ...points) + this.afterCreateOrUpdatePoint(item, option, points) + this.tempViewport.entityManager.appendObject(item.id, points) + this.appendToScene(...points) + return points + } + + dispose() { + super.dispose() + this.pointMaterial?.dispose() + } + + createPointBasic(item: ItemJson, option?: RendererCudOption): Object3D[] { + return []; + } +} diff --git a/src/modules/charger/index.ts b/src/modules/charger/index.ts new file mode 100644 index 0000000..1085ad5 --- /dev/null +++ b/src/modules/charger/index.ts @@ -0,0 +1,15 @@ +import { defineModule } from '@/core/manager/ModuleManager.ts' +import ChargerRenderer from './ChargerRenderer.ts' +import ChargerEntity from './ChargerEntity.ts' +import ChargerMeta from './ChargerMeta.ts' +import ChargerInteraction from './ChargerInteraction.ts' + +export const ITEM_TYPE_NAME = 'charger' + +export default defineModule({ + name: ITEM_TYPE_NAME, + renderer: new ChargerRenderer(ITEM_TYPE_NAME), + interaction: new ChargerInteraction(ITEM_TYPE_NAME), + meta: ChargerMeta, + entity: ChargerEntity +}) diff --git a/src/modules/clx/ClxEntity.ts b/src/modules/clx/ClxEntity.ts new file mode 100644 index 0000000..c81c09a --- /dev/null +++ b/src/modules/clx/ClxEntity.ts @@ -0,0 +1,5 @@ +import BaseEntity from '@/core/base/BaseItemEntity.ts' + +export default class ClxEntity extends BaseEntity { + +} diff --git a/src/modules/clx/ClxInteraction.ts b/src/modules/clx/ClxInteraction.ts new file mode 100644 index 0000000..b6bde8b --- /dev/null +++ b/src/modules/clx/ClxInteraction.ts @@ -0,0 +1,22 @@ +import BaseInteraction from '@/core/base/BaseInteraction.ts' +import * as THREE from 'three' + +export default class ClxInteraction extends BaseInteraction { + + get isSinglePointMode(): boolean { + return true + } + + constructor(itemTypeName: string) { + super(itemTypeName) + } + + createPointOfItem(item: ItemJson, point: THREE.Vector3): ItemJson { + item = super.createPointOfItem(item, point) + + // 创建一个地堆货架 + item.dt.palletWidth = 1 // 宽度 + item.dt.palletDepth = 1.2 // 深度 + return item + } +} diff --git a/src/modules/clx/ClxMeta.ts b/src/modules/clx/ClxMeta.ts new file mode 100644 index 0000000..24ad4fc --- /dev/null +++ b/src/modules/clx/ClxMeta.ts @@ -0,0 +1,49 @@ +import type { IMeta } from '@/core/base/IMeta.ts' + +export default [ + { field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, + { field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, + { field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, + { editor: 'TransformEditor', category: 'basic' }, + { field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, + { editor: '-', category: 'basic' }, + + { field: 'dt.clxWidth', editor: 'NumberInput', label: 'CLX宽度', category: 'basic' }, + { field: 'dt.clxDepth', editor: 'NumberInput', label: 'CLX深度', category: 'basic' }, + /** + * dt.bays 5列3层货架示例 + * { + * dt: { + * rackDepth: 1.1, // 货架深度 + * levelCount: 3, // 总层数 + * bayCount: 5, // 总列数 + * hideFloor: false, // 隐藏底板 + * extendColumns: true, // 扩展挡板 + * columnSpacing: 1, // 支脚跨越 + * bays: [ // 每列的配置 + * { + * bayWidth: 1.6, // 列的宽度 + * levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度 + * }, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * ] + * } + * } + * + * + * + * + * + * + * + */ + + + { field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, + { field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, + { field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, + { field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } +] as IMeta diff --git a/src/modules/clx/ClxRenderer.ts b/src/modules/clx/ClxRenderer.ts new file mode 100644 index 0000000..07b77e2 --- /dev/null +++ b/src/modules/clx/ClxRenderer.ts @@ -0,0 +1,97 @@ +import * as THREE from 'three' +import BaseRenderer from '@/core/base/BaseRenderer.ts' +import { Line2 } from 'three/examples/jsm/lines/Line2.js' +import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' +import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' +import {decimalSumBy} from "@/core/ModelUtils"; +import Constract from '@/core/Constract.ts' +import clxUrl from '@/assets/images/ptr/clx.png' + +/** + * clx渲染器 + */ +export default class ClxRenderer extends BaseRenderer { + static POINT_NAME = 'clx' + + pointMaterial: THREE.Material + + /** + * 默认点的高度, 防止和地面重合 + */ + readonly defulePositionY: number = Constract.HEIGHT_WAY + readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1) + readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) + readonly defaultLineWidth: number = 0.15 + + constructor(itemTypeName: string) { + super(itemTypeName) + } + + /** + * 所有的点,必须使用 storeWidth/storeDepth, 改TF无效 + */ + override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, objects: THREE.Object3D[]) { + super.afterCreateOrUpdatePoint(item, option, objects) + + const point = objects[0] + // point.position.y = this.defulePositionY + // point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) + point.rotation.set( + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) + ) + } + + + createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { + throw new Error('not allow store line.') + } + + updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { + throw new Error('not allow store line.') + } + + + createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { + // 创建平面几何体 + if (!item.dt.clxWidth || !item.dt.clxDepth) { + return [] + } + + const textureLoader = new THREE.TextureLoader() + const texture = textureLoader.load(clxUrl) + + const group = new THREE.Group() + group.name = ClxRenderer.POINT_NAME + + // 绘制背景矩形框 + const planeGeometry = new THREE.PlaneGeometry(item.dt.clxWidth, item.dt.clxDepth); + planeGeometry.rotateX(-Math.PI / 2) + const planeMaterial = new THREE.MeshLambertMaterial({ + map: texture, // 颜色贴图 + transparent: true, // 允许透明纹理 + }); + const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); + group.add(planeMesh) + + // 设置位置 + group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) + + const points = [group] + this.fillObjectUserDataFromItem(item, ...points) + this.afterCreateOrUpdatePoint(item, option, points) + this.tempViewport.entityManager.appendObject(item.id, points) + this.appendToScene(...points) + return points + } + + dispose() { + super.dispose() + this.pointMaterial?.dispose() + } + + createPointBasic(item: ItemJson, option?: RendererCudOption): Object3D[] { + return []; + } +} diff --git a/src/modules/clx/index.ts b/src/modules/clx/index.ts new file mode 100644 index 0000000..6e816eb --- /dev/null +++ b/src/modules/clx/index.ts @@ -0,0 +1,15 @@ +import { defineModule } from '@/core/manager/ModuleManager.ts' +import ClxRenderer from './ClxRenderer.ts' +import ClxEntity from './ClxEntity.ts' +import ClxMeta from './ClxMeta.ts' +import ClxInteraction from './ClxInteraction.ts' + +export const ITEM_TYPE_NAME = 'clx' + +export default defineModule({ + name: ITEM_TYPE_NAME, + renderer: new ClxRenderer(ITEM_TYPE_NAME), + interaction: new ClxInteraction(ITEM_TYPE_NAME), + meta: ClxMeta, + entity: ClxEntity +}) diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts index 2f7ea83..4a13d6d 100644 --- a/src/modules/gstore/GstoreRenderer.ts +++ b/src/modules/gstore/GstoreRenderer.ts @@ -35,13 +35,12 @@ export default class GstoreRenderer extends BaseRenderer { point.position.y = this.defulePositionY // point.scale.set(item.dt.storeWidth, this.defaultScale.y, item.dt.storeDepth) point.rotation.set( - THREE.MathUtils.degToRad(this.defaultRotation.x), - THREE.MathUtils.degToRad(this.defaultRotation.y), - THREE.MathUtils.degToRad(this.defaultRotation.z) + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) ) } - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { throw new Error('not allow store line.') } @@ -50,6 +49,10 @@ export default class GstoreRenderer extends BaseRenderer { throw new Error('not allow store line.') } + createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { + throw new Error('not allow createPointBasic.') + } + createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { // 创建平面几何体 if (!item.dt.storeWidth || !item.dt.storeDepth) { @@ -59,7 +62,7 @@ export default class GstoreRenderer extends BaseRenderer { group.name = GstoreRenderer.POINT_NAME // 绘制背景矩形框 - const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth); + const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth) planeGeometry.rotateX(Math.PI / 2) const planeMaterial = new THREE.MeshBasicMaterial({ color: '#dee8ee', @@ -67,8 +70,8 @@ export default class GstoreRenderer extends BaseRenderer { opacity: 0.5, // 50%透明度 depthWrite: false, // 防止深度冲突 side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"} - }); - const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); + }) + const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial) group.add(planeMesh) // 绘制边框 @@ -76,30 +79,32 @@ export default class GstoreRenderer extends BaseRenderer { const lineYLen = item.dt.storeDepth - this.defaultLineWidth const lineGeometry = new LineGeometry().setPositions([ - -(lineXLen/2),0,-(lineYLen/2), - lineXLen/2,0,-(lineYLen/2), - lineXLen/2,0,lineYLen/2, - -(lineXLen/2),0,lineYLen/2, - -(lineXLen/2),0,-(lineYLen/2) - ]); + -(lineXLen / 2), 0, -(lineYLen / 2), + lineXLen / 2, 0, -(lineYLen / 2), + lineXLen / 2, 0, lineYLen / 2, + -(lineXLen / 2), 0, lineYLen / 2, + -(lineXLen / 2), 0, -(lineYLen / 2) + ]) const lineMaterial = new LineMaterial({ color: '#038217', linewidth: this.defaultLineWidth, worldUnits: true, resolution: new THREE.Vector2(window.innerWidth, window.innerHeight), side: THREE.DoubleSide - }); + }) // - const line = new Line2(lineGeometry, lineMaterial); + const line = new Line2(lineGeometry, lineMaterial) group.add(line as THREE.Object3D) // 设置位置 group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) + const points = [group] this.fillObjectUserDataFromItem(item, ...points) this.afterCreateOrUpdatePoint(item, option, points) this.tempViewport.entityManager.appendObject(item.id, points) this.appendToScene(...points) + return points } diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts index b38683e..b591705 100644 --- a/src/modules/measure/MeasureRenderer.ts +++ b/src/modules/measure/MeasureRenderer.ts @@ -110,7 +110,7 @@ export default class MeasureRenderer extends BaseRenderer { } const dragObjects = objects.filter(obj => !!obj.userData.draggable) - this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') + //this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') this.group.add(...objects) } diff --git a/src/modules/pallet/PalletRenderer.ts b/src/modules/pallet/PalletRenderer.ts index c1ca858..a43bdc6 100644 --- a/src/modules/pallet/PalletRenderer.ts +++ b/src/modules/pallet/PalletRenderer.ts @@ -37,9 +37,9 @@ export default class PalletRenderer extends BaseRenderer { //point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) point.rotation.set( - THREE.MathUtils.degToRad(this.defaultRotation.x), - THREE.MathUtils.degToRad(this.defaultRotation.y), - THREE.MathUtils.degToRad(this.defaultRotation.z) + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) ) } diff --git a/src/modules/ptr/PtrMeta.ts b/src/modules/ptr/PtrMeta.ts new file mode 100644 index 0000000..4fede36 --- /dev/null +++ b/src/modules/ptr/PtrMeta.ts @@ -0,0 +1,49 @@ +import type { IMeta } from '@/core/base/IMeta.ts' + +export default [ + { field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, + { field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, + { field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, + { editor: 'TransformEditor', category: 'basic' }, + { field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, + { editor: '-', category: 'basic' }, + + { field: 'dt.ptrWidth', editor: 'NumberInput', label: 'PTR宽度', category: 'basic' }, + { field: 'dt.ptrDepth', editor: 'NumberInput', label: 'PTR深度', category: 'basic' }, + /** + * dt.bays 5列3层货架示例 + * { + * dt: { + * rackDepth: 1.1, // 货架深度 + * levelCount: 3, // 总层数 + * bayCount: 5, // 总列数 + * hideFloor: false, // 隐藏底板 + * extendColumns: true, // 扩展挡板 + * columnSpacing: 1, // 支脚跨越 + * bays: [ // 每列的配置 + * { + * bayWidth: 1.6, // 列的宽度 + * levelHeight: [ 1.4, 1.4, 1.4 ] // 每层的高度 + * }, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * {bayWidth: 1.6, levelHeight: [ 1.4, 1.4, 1.4 ]}, + * ] + * } + * } + * + * + * + * + * + * + * + */ + + + { field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, + { field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, + { field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, + { field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } +] as IMeta diff --git a/src/modules/ptr/PtrRenderer.ts b/src/modules/ptr/PtrRenderer.ts index 3de33b7..6233954 100644 --- a/src/modules/ptr/PtrRenderer.ts +++ b/src/modules/ptr/PtrRenderer.ts @@ -37,9 +37,9 @@ export default class PtrRenderer extends BaseRenderer { // point.position.y = this.defulePositionY // point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) point.rotation.set( - THREE.MathUtils.degToRad(this.defaultRotation.x), - THREE.MathUtils.degToRad(this.defaultRotation.y), - THREE.MathUtils.degToRad(this.defaultRotation.z) + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) ) } @@ -68,7 +68,6 @@ export default class PtrRenderer extends BaseRenderer { // 绘制背景矩形框 const planeGeometry = new THREE.PlaneGeometry(item.dt.ptrWidth, item.dt.ptrDepth); planeGeometry.rotateX(-Math.PI / 2) - planeGeometry.rotateY(-Math.PI / 2) const planeMaterial = new THREE.MeshLambertMaterial({ map: texture, // 颜色贴图 transparent: true, // 允许透明纹理 diff --git a/src/modules/rack/RackRenderer.ts b/src/modules/rack/RackRenderer.ts index 8701a83..fa7fbba 100644 --- a/src/modules/rack/RackRenderer.ts +++ b/src/modules/rack/RackRenderer.ts @@ -37,9 +37,9 @@ export default class RackRenderer extends BaseRenderer { //point.scale.set(_.sumBy(item.dt.bays, b=>b.bayWidth), this.defaultScale.y, item.dt.rackDepth) point.rotation.set( - THREE.MathUtils.degToRad(this.defaultRotation.x), - THREE.MathUtils.degToRad(this.defaultRotation.y), - THREE.MathUtils.degToRad(this.defaultRotation.z) + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) ) } diff --git a/src/modules/way/WayRenderer.ts b/src/modules/way/WayRenderer.ts index 19caaf9..6a13268 100644 --- a/src/modules/way/WayRenderer.ts +++ b/src/modules/way/WayRenderer.ts @@ -177,7 +177,7 @@ export default class WayRenderer extends BaseRenderer { appendToScene(...objects: THREE.Object3D[]) { const dragObjects = objects.filter(obj => !!obj.userData.draggable) - this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') + //this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') // this.tempViewport.dragControl.setDragObjects(objects, 'remove') this.tempViewport?.scene.add(...objects)