diff --git a/src/assets/Models/PallTex.png b/src/assets/Models/PallTex.png new file mode 100644 index 0000000..e3e5841 Binary files /dev/null and b/src/assets/Models/PallTex.png differ diff --git a/src/assets/Models/Pallet.3ds b/src/assets/Models/Pallet.3ds new file mode 100644 index 0000000..3b9fed1 Binary files /dev/null and b/src/assets/Models/Pallet.3ds differ diff --git a/src/assets/Models/Queue/QueTex.png b/src/assets/Models/Queue/QueTex.png new file mode 100644 index 0000000..13d74af Binary files /dev/null and b/src/assets/Models/Queue/QueTex.png differ diff --git a/src/assets/Models/Queue/Queue.3ds b/src/assets/Models/Queue/Queue.3ds new file mode 100644 index 0000000..9482960 Binary files /dev/null and b/src/assets/Models/Queue/Queue.3ds differ diff --git a/src/assets/Models/Tote.3ds b/src/assets/Models/Tote.3ds new file mode 100644 index 0000000..7c7f68f Binary files /dev/null and b/src/assets/Models/Tote.3ds differ diff --git a/src/assets/Models/ToteTex.png b/src/assets/Models/ToteTex.png new file mode 100644 index 0000000..05618d7 Binary files /dev/null and b/src/assets/Models/ToteTex.png differ diff --git a/src/core/Constract.ts b/src/core/Constract.ts index 2755a63..2aab268 100644 --- a/src/core/Constract.ts +++ b/src/core/Constract.ts @@ -35,5 +35,6 @@ const Constract = Object.freeze({ MAX_MEASURE_INSTANCES: 1000, MAX_GSTORE_INSTANCES: 500, + MAX_PALLET_INSTANCES: 10000, }) export default Constract diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 1327d62..ecd4081 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -6,6 +6,10 @@ import { Vector2 } from 'three/src/math/Vector2' import EventBus from '@/runtime/EventBus.ts' import Decimal from 'decimal.js' import type { Object3DLike } from '@/types/ModelTypes.ts' +import axios from 'axios' +import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' +import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' +import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' export function setUserDataForItem(item: ItemJson, object: Object3DLike) { if (!object.name && item.name) { @@ -483,3 +487,35 @@ export function decimalSumBy(collection: ArrayLike | null | undefined, ite }) return sum.toNumber() } + +export async function loadByUrl(url): Promise { + return await axios.get(url, { + responseType: 'arraybuffer' + }) +} + +export function loadTexture(url: string) { + return new THREE.TextureLoader().loadAsync(url) +} + +export function load3DModule(arrayBuffer: ArrayBuffer | string, ext: string) { + if (ext.endsWith('.fbx')) { + system.showLoading() + const loader = new FBXLoader() + return loader.parse(arrayBuffer, '') + + } else if (ext.endsWith('.obj')) { + const loader = new OBJLoader() + //@ts-ignore + return loader.parse(arrayBuffer) + + } else if (ext.endsWith('.3ds')) { + const loader = new TDSLoader() + //@ts-ignore + return loader.parse(arrayBuffer, '') + + } else { + system.showErrorDialog('不支持的文件类型!') + return null + } +} diff --git a/src/core/manager/WorldModel.ts b/src/core/manager/WorldModel.ts index 0289fdf..35a8bbb 100644 --- a/src/core/manager/WorldModel.ts +++ b/src/core/manager/WorldModel.ts @@ -6,6 +6,7 @@ import Way from '@/modules/way' import Gstore from '@/modules/gstore' import Rack from '@/modules/rack' import Pallet from "@/modules/pallet" +import Tote from "@/modules/tote" import Ptr from "@/modules/ptr" import Clx from "@/modules/clx" import Charger from "@/modules/charger" @@ -72,6 +73,7 @@ export default class WorldModel { Gstore, Rack, Pallet, + Tote, Ptr, Clx, Charger diff --git a/src/example/ExampleUtil.js b/src/example/ExampleUtil.js index b139f3d..1017118 100644 --- a/src/example/ExampleUtil.js +++ b/src/example/ExampleUtil.js @@ -5,8 +5,8 @@ * @param cols */ export function buildPointPerformanceData(t, rows, cols) { - const spacingX = 1.25 // X轴间距 - const spacingZ = 1.25 // Y轴间距 + const spacingX = 1.5 // X轴间距 + const spacingZ = 1.5 // Y轴间距 // 创建一个二维数组来存储点阵数据 const data = new Map() diff --git a/src/example/example1.js b/src/example/example1.js index b713eeb..09396f5 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -326,17 +326,10 @@ export default { catalogCode: 'f3', t: 'floor', items: [ { - id: 'gstore3', - t: 'gstore', - v: true, - tf: [[0, 0.1, 0], [0, 0, 0], [2.0, 0.1, 1.0]], - dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } - }, - { - id: 'gstore4', - t: 'gstore', + id: 'tote1', + t: 'pallet', v: true, - tf: [[5, 0.1, 0], [0, 0, 0], [2.0, 0.1, 1.0]], + tf: [[0, 0.1, 0], [0, 0, 0], [1.0, 0.1, 1.0]], dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } } ] @@ -347,7 +340,7 @@ export default { }, { catalogCode: '__f2', t: 'floor', - items: buildPointPerformanceData('gstore', 10, 10) + items: buildPointPerformanceData('pallet', 10, 10) } ], elevator: [], // 电梯 diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts index e7e5372..562f47e 100644 --- a/src/modules/gstore/GstoreRenderer.ts +++ b/src/modules/gstore/GstoreRenderer.ts @@ -1,15 +1,11 @@ import * as THREE from 'three' -import * as IfxUtils from '@/core/IfxUtils.ts' 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 Constract from '@/core/Constract.ts' -import InstancePointManager from '@/core/manager/InstancePointManager.ts' -import { BasePlane, type Object3DLike } from '@/types/ModelTypes.ts' +import { type Object3DLike } from '@/types/ModelTypes.ts' /** * 地堆货位渲染器 + * 没有使用 InstanceMesh 目前性能不好 */ export default class GstoreRenderer extends BaseRenderer { static POINT_NAME = 'ground_store' diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts index e1fac1b..ebe4285 100644 --- a/src/modules/measure/MeasureRenderer.ts +++ b/src/modules/measure/MeasureRenderer.ts @@ -9,6 +9,7 @@ import type { Object3DLike } from '@/types/ModelTypes.ts' /** * 辅助测量工具渲染器 + * 支持 InstanceMesh 和 LineSegment */ export default class MeasureRenderer extends BaseRenderer { static LABEL_NAME = 'measure_label' @@ -38,6 +39,7 @@ export default class MeasureRenderer extends BaseRenderer { throw new Error('tempViewport is not set.') } return this.tempViewport.getOrCreatePointManager(this.itemTypeName, () => + // 构建 InstanceMesh 代理对象 InstancePointManager.create(this.itemTypeName, this.tempViewport, this.pointGeometry, @@ -51,6 +53,7 @@ export default class MeasureRenderer extends BaseRenderer { throw new Error('tempViewport is not set.') } return this.tempViewport.getOrCreateLineManager(this.itemTypeName, () => + // 构建 LineSegment.points 代理对象 LineSegmentManager.create(this.itemTypeName, this.tempViewport, this.lineMaterial) diff --git a/src/modules/pallet/PalletRenderer.ts b/src/modules/pallet/PalletRenderer.ts index a575398..4fb8968 100644 --- a/src/modules/pallet/PalletRenderer.ts +++ b/src/modules/pallet/PalletRenderer.ts @@ -1,18 +1,17 @@ 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 InstancePointManager from '@/core/manager/InstancePointManager.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' +import MODULE_3DS_File from '@/assets/Models/Pallet.3ds?url' +import MODULE_3DS_TEX from '@/assets/Models/PallTex.png?url' +import { load3DModule, loadByUrl, loadTexture } from '@/core/ModelUtils.ts' /** * 货架货位渲染器 */ export default class PalletRenderer extends BaseRenderer { - static POINT_NAME = 'pallet' - - pointMaterial: THREE.Material + static POINT_NAME = 'pallet_point' /** * 默认点的高度, 防止和地面重合 @@ -20,91 +19,55 @@ export default class PalletRenderer extends BaseRenderer { 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, object: THREE.Object3D) { - super.afterCreateOrUpdatePoint(item, option, object) - - const point = object - // 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]) - ) + readonly defaultUserData = { + color: 0xcfc195 } - - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { - throw new Error('not allow store line.') + palletGeometry: THREE.BufferGeometry + palletMaterial: THREE.Material + + init() { + return Promise.all([ + super.init(), + loadByUrl(MODULE_3DS_File), + loadTexture(MODULE_3DS_TEX) + + ]).then(([_, { data: queue3dsFile }, queueTexture]) => { + const mesh = load3DModule(queue3dsFile, '.3ds').children[0] as THREE.Mesh + this.palletGeometry = mesh.geometry.rotateX(-Math.PI / 2) + this.palletMaterial = mesh.material as THREE.Material + this.palletMaterial.color.set(this.defaultUserData.color) + this.palletMaterial.map = queueTexture + }) } - updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { - throw new Error('not allow store line.') + createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { + return this.pointManager.createPoint(item) } - - createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D { - // 创建平面几何体 - if (!item.dt.palletWidth || !item.dt.palletDepth) { - system.showErrorDialog('field palletWidth / palletDepth is null!') - return null + get pointManager(): InstancePointManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set.') } - - const group = new THREE.Group() - group.name = PalletRenderer.POINT_NAME - - // 绘制背景矩形框 - const planeGeometry = new THREE.PlaneGeometry(item.dt.palletWidth - this.defaultLineWidth, item.dt.palletDepth - this.defaultLineWidth) - planeGeometry.rotateX(Math.PI / 2) - const planeMaterial = new THREE.MeshBasicMaterial({ - color: '#029de5', - side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"} - }) - const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial) - group.add(planeMesh) - - // 绘制边框 - const lineXLen = item.dt.palletWidth - this.defaultLineWidth - const lineYLen = item.dt.palletDepth - 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) - ]) - const lineMaterial = new LineMaterial({ - color: '#029de5', - linewidth: this.defaultLineWidth, - worldUnits: true, - resolution: new THREE.Vector2(window.innerWidth, window.innerHeight), - side: THREE.DoubleSide - }) - // - 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]) - return group + return this.tempViewport.getOrCreatePointManager(this.itemTypeName, () => + // 构建 InstanceMesh 代理对象 + InstancePointManager.create(this.itemTypeName, + this.tempViewport, + this.palletGeometry, + this.palletMaterial, + Constract.MAX_PALLET_INSTANCES) + ) } dispose() { super.dispose() - this.pointMaterial?.dispose() - } - - createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D { - throw new Error('Pallet createPointBasic not allow!') + if (this.palletGeometry) { + this.palletGeometry.dispose() + this.palletGeometry = undefined + } + if (this.palletMaterial) { + this.palletMaterial.dispose() + this.palletMaterial = undefined + } } } diff --git a/src/modules/tote/ToteEntity.ts b/src/modules/tote/ToteEntity.ts new file mode 100644 index 0000000..9cf671f --- /dev/null +++ b/src/modules/tote/ToteEntity.ts @@ -0,0 +1,5 @@ +import BaseEntity from '@/core/base/BaseItemEntity.ts' + +export default class PalletEntity extends BaseEntity { + +} diff --git a/src/modules/tote/ToteInteraction.ts b/src/modules/tote/ToteInteraction.ts new file mode 100644 index 0000000..c01c91b --- /dev/null +++ b/src/modules/tote/ToteInteraction.ts @@ -0,0 +1,22 @@ +import BaseInteraction from '@/core/base/BaseInteraction.ts' +import * as THREE from 'three' + +export default class PalletInteraction 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/tote/TotePropertySetter.ts b/src/modules/tote/TotePropertySetter.ts new file mode 100644 index 0000000..4a5cfc9 --- /dev/null +++ b/src/modules/tote/TotePropertySetter.ts @@ -0,0 +1,20 @@ +import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; +import { basicFieldsSetter } from "@/editor/widgets/property/PropertyPanelConstant.ts"; + +const propertySetter: PropertySetter = { + flatten: { + fields: [ + ...basicFieldsSetter, + { + dataPath: 'dt.palletWidth', label: '托盘宽度', input: 'InputNumber', + inputProps: {}, + }, + { + dataPath: 'dt.palletDepth', label: '托盘深度', input: 'InputNumber', + inputProps: {}, + }, + ], + }, +}; + +export default propertySetter; diff --git a/src/modules/tote/ToteRenderer.ts b/src/modules/tote/ToteRenderer.ts new file mode 100644 index 0000000..a38a910 --- /dev/null +++ b/src/modules/tote/ToteRenderer.ts @@ -0,0 +1,75 @@ +import * as THREE from 'three' +import BaseRenderer from '@/core/base/BaseRenderer.ts' +import Constract from '@/core/Constract.ts' +import InstancePointManager from '@/core/manager/InstancePointManager.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' +import MODULE_3DS_File from '@/assets/Models/Tote.3ds?url' +import MODULE_3DS_TEX from '@/assets/Models/ToteTex.png?url' +import { load3DModule, loadByUrl, loadTexture } from '@/core/ModelUtils.ts' + +/** + * 货架货位渲染器 + */ +export default class PalletRenderer extends BaseRenderer { + static POINT_NAME = 'pallet_point' + + /** + * 默认点的高度, 防止和地面重合 + */ + 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 defaultUserData = { + color: 0x4559A0 + } + + toteGeometry: THREE.BufferGeometry + toteMaterial: THREE.Material + + init() { + return Promise.all([ + super.init(), + loadByUrl(MODULE_3DS_File), + loadTexture(MODULE_3DS_TEX) + + ]).then(([_, { data: queue3dsFile }, queueTexture]) => { + const mesh = load3DModule(queue3dsFile, '.3ds').children[0] as THREE.Mesh + this.toteGeometry = mesh.geometry.rotateX(-Math.PI / 2) + this.toteGeometry.scale(1, 1, 1) + this.toteGeometry.center() + this.toteMaterial = mesh.material as THREE.Material + this.toteMaterial.map = queueTexture + this.toteMaterial.color.set(this.defaultUserData.color) + }) + } + + createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { + return this.pointManager.createPoint(item) + } + + get pointManager(): InstancePointManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set.') + } + return this.tempViewport.getOrCreatePointManager(this.itemTypeName, () => + // 构建 InstanceMesh 代理对象 + InstancePointManager.create(this.itemTypeName, + this.tempViewport, + this.toteGeometry, + this.toteMaterial, + Constract.MAX_PALLET_INSTANCES) + ) + } + + dispose() { + super.dispose() + if (this.toteGeometry) { + this.toteGeometry.dispose() + this.toteGeometry = undefined + } + if (this.toteMaterial) { + this.toteMaterial.dispose() + this.toteMaterial = undefined + } + } +} diff --git a/src/modules/tote/index.ts b/src/modules/tote/index.ts new file mode 100644 index 0000000..12b9259 --- /dev/null +++ b/src/modules/tote/index.ts @@ -0,0 +1,15 @@ +import { defineModule } from '@/core/manager/ModuleManager.ts' +import ToteRenderer from './ToteRenderer.ts' +import ToteEntity from './ToteEntity.ts' +import ToteInteraction from './ToteInteraction.ts' +import propertySetter from './TotePropertySetter.ts' + +export const ITEM_TYPE_NAME = 'tote' + +export default defineModule({ + name: ITEM_TYPE_NAME, + renderer: new ToteRenderer(ITEM_TYPE_NAME), + interaction: new ToteInteraction(ITEM_TYPE_NAME), + setter: propertySetter, + entity: ToteEntity +})