From b073dbfa75803e77f9d63d8fcfcc9c69d7265735 Mon Sep 17 00:00:00 2001
From: yuliang <398780299@qq.com>
Date: Wed, 4 Jun 2025 17:55:14 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E5=9C=B0=E5=A0=86=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/components/Model3DView.vue | 10 ++++++
src/modules/gstore/GstoreRenderer.ts | 63 ++++++++++++++++++++++++++++++------
2 files changed, 63 insertions(+), 10 deletions(-)
diff --git a/src/components/Model3DView.vue b/src/components/Model3DView.vue
index e5b7ecf..110e9db 100644
--- a/src/components/Model3DView.vue
+++ b/src/components/Model3DView.vue
@@ -17,6 +17,7 @@
添加输送线
添加货架
+ 添加地堆
材质颜色
@@ -378,6 +379,15 @@ function createShelf(){//创建货架
})
}
+function createGroundStore() {
+ const planeGeometry = new THREE.PlaneGeometry(1, 1);
+ const material = new THREE.MeshBasicMaterial({
+ color: 0x00ff00,
+ side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
+ });
+ const planeMesh = new THREE.Mesh(planeGeometry, material);
+ scene.add(planeMesh);
+}
function initThree() {
const viewerDom = canvasContainer.value
diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts
index 789b311..96fcc95 100644
--- a/src/modules/gstore/GstoreRenderer.ts
+++ b/src/modules/gstore/GstoreRenderer.ts
@@ -1,15 +1,14 @@
import * as THREE from 'three'
import BaseRenderer from '@/core/base/BaseRenderer.ts'
-import { Text } from 'troika-three-text'
-import MoveLinePointPng from '@/assets/images/moveline_point.png'
-import SimSunTTF from '@/assets/fonts/simsunb.ttf'
-import { getLineId } from '@/core/ModelUtils.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'
/**
- * 辅助测量工具渲染器
+ * 地堆货位渲染器
*/
export default class GstoreRenderer extends BaseRenderer {
- static POINT_NAME = 'way_point'
+ static POINT_NAME = 'ground_store'
pointMaterial: THREE.Material
@@ -19,6 +18,7 @@ export default class GstoreRenderer extends BaseRenderer {
readonly defulePositionY: number = 0.5 // 默认点的高度, 0.01, 防止和地面重合
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1.5, 1.2, 0.1)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
+ readonly defaultLineWidth: number = 0.05
constructor(itemTypeName: string) {
super(itemTypeName)
@@ -50,13 +50,56 @@ export default class GstoreRenderer extends BaseRenderer {
}
createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] {
- const obj = new THREE.Sprite(this.pointMaterial as THREE.SpriteMaterial)
- obj.name = GstoreRenderer.POINT_NAME
- return [obj]
+ // 创建平面几何体
+
+ const group = new THREE.Group()
+ group.name = GstoreRenderer.POINT_NAME
+
+ // 绘制背景矩形框
+ const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth);
+ planeGeometry.rotateX(-Math.PI / 2)
+ const planeMaterial = new THREE.MeshBasicMaterial({
+ color: 'white',
+ transparent: true, // 启用透明
+ opacity: 0.2, // 50%透明度
+ depthWrite: false, // 防止深度冲突
+ side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
+ });
+ const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
+ group.add(planeMesh)
+
+ if (!item.dt.storeWidth || !item.dt.storeDepth) {
+ return [group]
+ }
+
+ // 绘制边框
+ const lineXLen = item.dt.storeWidth - this.defaultLineWidth
+ const lineYLen = item.dt.storeDepth - this.defaultLineWidth
+
+ const lineGeometry = new LineGeometry().setPositions([
+ -(lineXLen/2),-(lineYLen/2),0,
+ lineXLen/2,-(lineYLen/2),0,
+ lineXLen/2,lineYLen/2,0,
+ -(lineXLen/2),lineYLen/2,0,
+ -(lineXLen/2),-(lineYLen/2),0
+ ]);
+ lineGeometry.rotateX(-Math.PI / 2)
+ const lineMaterial = new LineMaterial({
+ color: 0x00ff00,
+ linewidth: 0.05,
+ 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)
+
+ return [group]
}
dispose() {
super.dispose()
this.pointMaterial.dispose()
}
-}
\ No newline at end of file
+}
From 0334750c0555c512a194b64d14b5e9b7586d13bf Mon Sep 17 00:00:00 2001
From: luoyifan
Date: Wed, 4 Jun 2025 18:02:39 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=E5=8F=AA=E6=9C=89=E8=A2=AB=E9=80=89?=
=?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=85=83=E7=B4=A0=E6=89=8D=E5=85=81=E8=AE=B8?=
=?UTF-8?q?=E6=8B=96=E6=8B=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/core/base/BaseRenderer.ts | 5 +-
src/core/controls/SelectInspect.ts | 34 +++++++
src/core/manager/EntityManager.ts | 169 ++++++++++++++++++++-------------
src/modules/measure/MeasureRenderer.ts | 1 -
4 files changed, 139 insertions(+), 70 deletions(-)
diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts
index cc279e8..e65a077 100644
--- a/src/core/base/BaseRenderer.ts
+++ b/src/core/base/BaseRenderer.ts
@@ -116,9 +116,8 @@ export default abstract class BaseRenderer {
console.warn('No active viewport to append objects to.')
return
}
-
- const dragObjects = objects.filter(obj => !!obj.userData.draggable)
- this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
+ // const dragObjects = objects.filter(obj => !!obj.userData.draggable)
+ // this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
this.tempViewport.scene.add(...objects)
}
diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts
index 1d07855..f2def9f 100644
--- a/src/core/controls/SelectInspect.ts
+++ b/src/core/controls/SelectInspect.ts
@@ -5,6 +5,9 @@ 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 EventBus from '@/runtime/EventBus'
+import { markRaw } from 'vue'
+import { getMeta } from '@/core/manager/ModuleManager.ts'
+import MouseMoveInspect from '@/core/controls/MouseMoveInspect.ts'
let pdFn, pmFn, puFn
@@ -52,6 +55,8 @@ export default class SelectInspect implements IControls {
*/
selectionId: string
+ clickTime: number | null = null
+
constructor() {
}
@@ -196,6 +201,9 @@ export default class SelectInspect implements IControls {
// 记录鼠标按下位置
this.recStartPos = this.viewport.getClosestIntersection(event)
this.createRectangle()
+ } else {
+ // 为 click 事件添加处理逻辑
+ this.clickTime = Date.now()
}
}
@@ -221,5 +229,31 @@ export default class SelectInspect implements IControls {
onMouseUp(event: MouseEvent) {
this.disposeRect()
+ const clickTime = this.clickTime
+ this.clickTime = null
+ if (Date.now() - clickTime < 200) {
+ // 如果是点击事件,触发选中逻辑
+ const objects: THREE.Object3D[] = this.viewport.entityManager.getObjectByCanvasMouse(event)
+ if (objects.length > 0) {
+ const object = objects[0]
+ const entityId = object.userData.entityId
+ const item = this.viewport.entityManager.findItemById(entityId)
+ const itemTypeName = object.userData.t
+ if (item.dt.protected !== true) {
+ this.viewport.state.selectedObject = markRaw(object)
+ this.viewport.state.selectedItem = markRaw(item)
+ this.viewport.state.selectedEntityId = entityId
+ this.viewport.state.selectedObjectMeta = getMeta(itemTypeName)
+
+ EventBus.dispatch('selectedObjectChanged', {
+ viewport: markRaw(this.viewport),
+ selectedObject: this.viewport.state.selectedObject,
+ selectedItem: this.viewport.state.selectedItem,
+ selectedEntityId: this.viewport.state.selectedEntityId,
+ selectedObjectMeta: this.viewport.state.selectedObjectMeta
+ })
+ }
+ }
+ }
}
}
diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts
index 9af1f09..a09fc19 100644
--- a/src/core/manager/EntityManager.ts
+++ b/src/core/manager/EntityManager.ts
@@ -3,6 +3,7 @@ import type Viewport from '@/core/engine/Viewport'
import type BaseRenderer from '@/core/base/BaseRenderer'
import { getRenderer } from './ModuleManager'
import { getLineId, parseLineId } from '@/core/ModelUtils'
+import { Vector2 } from 'three'
/**
* 实体管理器
@@ -24,6 +25,9 @@ export default class EntityManager {
// 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组
private readonly objects = new Map()
+ // 所有 THREEJS "可选中"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组
+ private readonly _selectableObjects: THREE.Object3D[] = []
+
// 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组
private readonly lines = new Map()
@@ -404,70 +408,70 @@ export default class EntityManager {
}
- /**
- * 重命名一个点
- * 注意, 不能在更新时刻改名. 所有的关系节点都应该改名
- */
- renamePoint(newId: string, originId: string) {
- if (this.isUpdating) {
- throw new Error('Cannot rename point during update')
- }
- const entity = this.entities.get(originId)
- if (!entity) {
- throw new Error(`Entity with id ${originId} does not exist`)
- }
- if (this.entities.has(newId)) {
- throw new Error(`Entity with id ${newId} already exists`)
- }
- entity.id = newId
- this.entities.set(newId, entity)
- this.entities.delete(originId)
- this.objects.set(newId, this.objects.get(originId) || [])
- this.objects.delete(originId)
-
- // 更新关系索引
- const relations = this.relationIndex.get(originId)
- if (relations) {
- this.relationIndex.delete(originId)
-
- // 更新所有关系中的 id
- relations.center.forEach((relatedId) => {
- const rev = this.relationIndex.get(relatedId)
- if (rev && rev.delete('center', originId)) {
- rev.add('center', newId)
- }
- })
- relations.input.forEach((relatedId) => {
- const rev = this.relationIndex.get(relatedId)
- if (rev && rev.delete('out', originId)) {
- rev.add('out', newId)
- }
- })
- relations.output.forEach((relatedId) => {
- const rev = this.relationIndex.get(relatedId)
- if (rev && rev.delete('in', originId)) {
- rev.add('in', newId)
- }
- })
-
- this.relationIndex.set(newId, relations)
- }
-
- // 更新所有线段数据
- for (const [lineId, lineObjects] of this.lines.entries()) {
- const [type, startId, endId] = parseLineId(lineId)
- if (startId === originId) {
- const newLineId = getLineId(newId, endId, type)
- this.lines.set(newLineId, lineObjects)
- this.lines.delete(lineId)
-
- } else if (endId === originId) {
- const newLineId = getLineId(startId, newId, type)
- this.lines.set(newLineId, lineObjects)
- this.lines.delete(lineId)
- }
- }
- }
+ // /**
+ // * 重命名一个点
+ // * 注意, 不能在更新时刻改名. 所有的关系节点都应该改名
+ // */
+ // renamePoint(newId: string, originId: string) {
+ // if (this.isUpdating) {
+ // throw new Error('Cannot rename point during update')
+ // }
+ // const entity = this.entities.get(originId)
+ // if (!entity) {
+ // throw new Error(`Entity with id ${originId} does not exist`)
+ // }
+ // if (this.entities.has(newId)) {
+ // throw new Error(`Entity with id ${newId} already exists`)
+ // }
+ // entity.id = newId
+ // this.entities.set(newId, entity)
+ // this.entities.delete(originId)
+ // this.objects.set(newId, this.objects.get(originId) || [])
+ // this.objects.delete(originId)
+ //
+ // // 更新关系索引
+ // const relations = this.relationIndex.get(originId)
+ // if (relations) {
+ // this.relationIndex.delete(originId)
+ //
+ // // 更新所有关系中的 id
+ // relations.center.forEach((relatedId) => {
+ // const rev = this.relationIndex.get(relatedId)
+ // if (rev && rev.delete('center', originId)) {
+ // rev.add('center', newId)
+ // }
+ // })
+ // relations.input.forEach((relatedId) => {
+ // const rev = this.relationIndex.get(relatedId)
+ // if (rev && rev.delete('out', originId)) {
+ // rev.add('out', newId)
+ // }
+ // })
+ // relations.output.forEach((relatedId) => {
+ // const rev = this.relationIndex.get(relatedId)
+ // if (rev && rev.delete('in', originId)) {
+ // rev.add('in', newId)
+ // }
+ // })
+ //
+ // this.relationIndex.set(newId, relations)
+ // }
+ //
+ // // 更新所有线段数据
+ // for (const [lineId, lineObjects] of this.lines.entries()) {
+ // const [type, startId, endId] = parseLineId(lineId)
+ // if (startId === originId) {
+ // const newLineId = getLineId(newId, endId, type)
+ // this.lines.set(newLineId, lineObjects)
+ // this.lines.delete(lineId)
+ //
+ // } else if (endId === originId) {
+ // const newLineId = getLineId(startId, newId, type)
+ // this.lines.set(newLineId, lineObjects)
+ // this.lines.delete(lineId)
+ // }
+ // }
+ // }
deleteEntityOnly(id: string) {
return this.entities.delete(id)
@@ -478,15 +482,28 @@ export default class EntityManager {
}
deleteObjectsOnly(id: string) {
+ // 删除对象时,也需要从 _selectableObjects 中移除
+ const rel = this.objects.get(id)
+ if (rel) {
+ _.remove(this._selectableObjects, obj => !rel.includes(obj))
+ }
return this.objects.delete(id)
}
appendObject(id: string, points: THREE.Object3D[]) {
this.objects.set(id, points)
+ // 如果是可选中对象,添加到 _selectableObjects 中
+ if (points.some(obj => obj.userData.selectable !== false)) {
+ this._selectableObjects.push(...points)
+ }
}
appendLineObject(id: string, lines: THREE.Object3D[]) {
this.lines.set(id, lines)
+ // 如果是可选中对象,添加到 _selectableObjects 中
+ if (lines.some(obj => obj.userData.selectable !== false)) {
+ this._selectableObjects.push(...lines)
+ }
}
findLineObjectsById(lineId: string): THREE.Object3D[] {
@@ -494,6 +511,11 @@ export default class EntityManager {
}
deleteLineObjectOnly(id: string) {
+ // 删除线对象时,也需要从 _selectableObjects 中移除
+ const rel = this.lines.get(id)
+ if (rel) {
+ _.remove(this._selectableObjects, obj => !rel.includes(obj))
+ }
return this.lines.delete(id)
}
@@ -507,8 +529,23 @@ export default class EntityManager {
return this.entities.get(linkStartPointId)
}
- getEntityMap() {
- return this.entities
+ getObjectByCanvasMouse(event: MouseEvent): THREE.Object3D[] {
+ const _domElement = this.viewport.renderer.domElement
+ const rect = _domElement.getBoundingClientRect()
+ const _pointer = new Vector2()
+ _pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1
+ _pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1
+ this.viewport.raycaster.setFromCamera(_pointer, this.viewport.camera)
+ const _intersections = this.viewport.raycaster.intersectObjects(this._selectableObjects, true)
+
+ if (!_intersections || _intersections.length === 0) {
+ return []
+ }
+ // 根据距离排序射线命中的对象集
+ return _.map(
+ _intersections.sort((a, b) => a.distance - b.distance),
+ r => r.object
+ )
}
}
diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts
index f4f01b9..610e214 100644
--- a/src/modules/measure/MeasureRenderer.ts
+++ b/src/modules/measure/MeasureRenderer.ts
@@ -110,7 +110,6 @@ export default class MeasureRenderer extends BaseRenderer {
const dragObjects = objects.filter(obj => !!obj.userData.draggable)
this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
- // this.tempViewport.dragControl.setDragObjects(objects, 'remove')
this.group.add(...objects)
}
From a1be15ad468cd1f124c2dafd0a5156a4b463fece Mon Sep 17 00:00:00 2001
From: yuliang <398780299@qq.com>
Date: Wed, 4 Jun 2025 18:07:51 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=E6=96=B0=E5=BB=BARack=E6=A8=A1=E5=9D=97?=
=?UTF-8?q?=E6=96=87=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/modules/rack/RackEntity.ts | 5 ++
src/modules/rack/RackInteraction.ts | 22 ++++++++
src/modules/rack/RackMeta.ts | 14 +++++
src/modules/rack/RackRenderer.ts | 105 ++++++++++++++++++++++++++++++++++++
src/modules/rack/index.ts | 15 ++++++
5 files changed, 161 insertions(+)
create mode 100644 src/modules/rack/RackEntity.ts
create mode 100644 src/modules/rack/RackInteraction.ts
create mode 100644 src/modules/rack/RackMeta.ts
create mode 100644 src/modules/rack/RackRenderer.ts
create mode 100644 src/modules/rack/index.ts
diff --git a/src/modules/rack/RackEntity.ts b/src/modules/rack/RackEntity.ts
new file mode 100644
index 0000000..713ed0e
--- /dev/null
+++ b/src/modules/rack/RackEntity.ts
@@ -0,0 +1,5 @@
+import BaseEntity from '@/core/base/BaseItemEntity.ts'
+
+export default class RackEntity extends BaseEntity {
+
+}
diff --git a/src/modules/rack/RackInteraction.ts b/src/modules/rack/RackInteraction.ts
new file mode 100644
index 0000000..7a7b41d
--- /dev/null
+++ b/src/modules/rack/RackInteraction.ts
@@ -0,0 +1,22 @@
+import BaseInteraction from '@/core/base/BaseInteraction.ts'
+import * as THREE from 'three'
+
+export default class RackInteraction 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.storeWidth = 1.2 // 宽度
+ item.dt.storeDepth = 1.2 // 深度
+ return item
+ }
+}
diff --git a/src/modules/rack/RackMeta.ts b/src/modules/rack/RackMeta.ts
new file mode 100644
index 0000000..2273048
--- /dev/null
+++ b/src/modules/rack/RackMeta.ts
@@ -0,0 +1,14 @@
+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: '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
\ No newline at end of file
diff --git a/src/modules/rack/RackRenderer.ts b/src/modules/rack/RackRenderer.ts
new file mode 100644
index 0000000..e6d5984
--- /dev/null
+++ b/src/modules/rack/RackRenderer.ts
@@ -0,0 +1,105 @@
+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'
+
+/**
+ * 地堆货位渲染器
+ */
+export default class RackRenderer extends BaseRenderer {
+ static POINT_NAME = 'ground_store'
+
+ pointMaterial: THREE.Material
+
+ /**
+ * 默认点的高度, 防止和地面重合
+ */
+ readonly defulePositionY: number = 0.5 // 默认点的高度, 0.01, 防止和地面重合
+ readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1.5, 1.2, 0.1)
+ readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
+ readonly defaultLineWidth: number = 0.05
+
+ 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(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)
+ )
+ }
+
+
+ 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.')
+ }
+
+ createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] {
+ // 创建平面几何体
+
+ const group = new THREE.Group()
+ group.name = RackRenderer.POINT_NAME
+
+ // 绘制背景矩形框
+ const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth);
+ planeGeometry.rotateX(-Math.PI / 2)
+ const planeMaterial = new THREE.MeshBasicMaterial({
+ color: 'white',
+ transparent: true, // 启用透明
+ opacity: 0.2, // 50%透明度
+ depthWrite: false, // 防止深度冲突
+ side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
+ });
+ const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
+ group.add(planeMesh)
+
+ if (!item.dt.storeWidth || !item.dt.storeDepth) {
+ return [group]
+ }
+
+ // 绘制边框
+ const lineXLen = item.dt.storeWidth - this.defaultLineWidth
+ const lineYLen = item.dt.storeDepth - this.defaultLineWidth
+
+ const lineGeometry = new LineGeometry().setPositions([
+ -(lineXLen/2),-(lineYLen/2),0,
+ lineXLen/2,-(lineYLen/2),0,
+ lineXLen/2,lineYLen/2,0,
+ -(lineXLen/2),lineYLen/2,0,
+ -(lineXLen/2),-(lineYLen/2),0
+ ]);
+ lineGeometry.rotateX(-Math.PI / 2)
+ const lineMaterial = new LineMaterial({
+ color: 0x00ff00,
+ linewidth: 0.05,
+ 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)
+
+ return [group]
+ }
+
+ dispose() {
+ super.dispose()
+ this.pointMaterial.dispose()
+ }
+}
diff --git a/src/modules/rack/index.ts b/src/modules/rack/index.ts
new file mode 100644
index 0000000..4e16547
--- /dev/null
+++ b/src/modules/rack/index.ts
@@ -0,0 +1,15 @@
+import { defineModule } from '@/core/manager/ModuleManager.ts'
+import RackRenderer from './RackRenderer.ts'
+import RackEntity from './RackEntity.ts'
+import RackMeta from './RackMeta.ts'
+import RackInteraction from './RackInteraction.ts'
+
+export const ITEM_TYPE_NAME = 'rack'
+
+export default defineModule({
+ name: ITEM_TYPE_NAME,
+ renderer: new RackRenderer(ITEM_TYPE_NAME),
+ interaction: new RackInteraction(ITEM_TYPE_NAME),
+ meta: RackMeta,
+ entity: RackEntity
+})