Browse Source

Merge remote-tracking branch 'origin/master'

master
liupeng 6 months ago
parent
commit
1309d3b134
  1. 94
      doc/OBB矩形包围盒API.md
  2. 3
      package.json
  3. 174
      pnpm-lock.yaml
  4. BIN
      src/assets/Models/PallTex.png
  5. BIN
      src/assets/Models/Pallet.3ds
  6. BIN
      src/assets/Models/Queue/QueTex.png
  7. BIN
      src/assets/Models/Queue/Queue.3ds
  8. BIN
      src/assets/Models/Tote.3ds
  9. BIN
      src/assets/Models/ToteTex.png
  10. BIN
      src/assets/Models/carton.glb
  11. BIN
      src/assets/Models/carton.jpg
  12. BIN
      src/assets/Models/storageBar.png
  13. BIN
      src/assets/Models/storageBar2.png
  14. 1281
      src/assets/fonts/helvetiker_regular.typeface.json
  15. BIN
      src/assets/images/conveyor/shapes/triangle2.png
  16. 514
      src/components/Model3DView.vue
  17. 828
      src/components/ThreePerfView2.vue
  18. 2
      src/core/Constract.ts
  19. 130
      src/core/IfxUtils.ts
  20. 159
      src/core/ModelUtils.ts
  21. 3
      src/core/base/BaseInteraction.ts
  22. 111
      src/core/base/BaseRenderer.ts
  23. 33
      src/core/base/IMeta.ts
  24. 14
      src/core/engine/SceneHelp.ts
  25. 60
      src/core/engine/Viewport.ts
  26. 40
      src/core/manager/EntityManager.ts
  27. 74
      src/core/manager/InstanceMeshBlock.ts
  28. 181
      src/core/manager/InstanceMeshManager.ts
  29. 198
      src/core/manager/InstancePointManager.ts
  30. 19
      src/core/manager/LabelManager.ts
  31. 17
      src/core/manager/LineSegmentManager.ts
  32. 4
      src/core/manager/WorldModel.ts
  33. 127
      src/editor/BulkCopy.vue
  34. 93
      src/editor/Model2DEditor.vue
  35. 6
      src/editor/ModelMain.less
  36. 18
      src/editor/menus/Model3DView.ts
  37. 99
      src/editor/propEditors/BayEditor.vue
  38. 6
      src/editor/propEditors/InOutCenterEditor.vue
  39. 12
      src/editor/propEditors/TransformEditor.vue
  40. 136
      src/editor/widgets/property/PropertyPanel.vue
  41. 192
      src/example/ExampleUtil.js
  42. 375
      src/example/example1.js
  43. 47942
      src/example/flash.js
  44. 77
      src/model/itemType/ItemType.ts
  45. 106
      src/model/itemType/ItemTypeDefine.ts
  46. 256
      src/model/itemType/ItemTypeLine.ts
  47. 323
      src/model/itemType/Toolbox.ts
  48. 149
      src/model/itemType/ToolboxLine.ts
  49. 7
      src/model/itemType/line/LineMeta.ts
  50. 124
      src/model/itemType/line/conveyor/Conveyor.ts
  51. 9
      src/model/itemType/line/conveyor/ConveyorMeta.ts
  52. 16
      src/model/itemType/line/conveyor/ConveyorToolbox.ts
  53. 202
      src/model/itemType/measure/Measure.ts
  54. 36
      src/model/itemType/measure/MeasureMeta.ts
  55. 94
      src/model/itemType/measure/MeasureToolbox.ts
  56. 7
      src/model/itemType/point/PointMeta.ts
  57. 7
      src/model/itemType/store/QueueMeta.ts
  58. 5
      src/modules/carton/CartonEntity.ts
  59. 22
      src/modules/carton/CartonInteraction.ts
  60. 20
      src/modules/carton/CartonPropertySetter.ts
  61. 87
      src/modules/carton/CartonRenderer.ts
  62. 15
      src/modules/carton/index.ts
  63. 26
      src/modules/gstore/GstorePropertySetter.ts
  64. 160
      src/modules/gstore/GstoreRenderer.ts
  65. 136
      src/modules/measure/MeasureRenderer.ts
  66. 129
      src/modules/pallet/PalletRenderer.ts
  67. 607
      src/modules/rack/RackRenderer.ts
  68. 5
      src/modules/tote/ToteEntity.ts
  69. 22
      src/modules/tote/ToteInteraction.ts
  70. 20
      src/modules/tote/TotePropertySetter.ts
  71. 77
      src/modules/tote/ToteRenderer.ts
  72. 15
      src/modules/tote/index.ts
  73. 413
      src/modules/way/WayRenderer.ts
  74. 32
      src/types/ModelTypes.ts
  75. 1
      src/types/Types.d.ts
  76. 1
      vite.config.ts

94
doc/OBB矩形包围盒API.md

@ -0,0 +1,94 @@
## OBB 包围盒快速查找算法
### 物品数据结构
```typescript
items = ItemJson[]
interface ItemJson {
// 物体ID, 唯一标识
id: string
/**
* 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前。右手坐标系
*/
tf: [
/**
* 平移向量 position, 三维坐标, [0]=x, [1]=高度值,在2D下忽略, [2]=z
*/
[number, number, number],
/**
* 旋转向量 rotation, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算 [0]=X轴逆向旋转角度, [1]=Y轴逆向旋转角度, [2]=Z轴逆向旋转角度
*/
[number, number, number],
/**
* 缩放向量 scale, 三维缩放比例, [0]=X宽度, [1]=Y高度,在2D下忽略, [2]=Z长度
*/
[number, number, number],
]
}
```
### add(...items: ItemJson[])
添加点位
add(...items: ItemJson[])
---
### update(item: ItemJson)
更新点位
update(itemJson)
---
### deleteItem(id: string)
删除点位
deleteItem(id: string)
---
### getItemsByPosition(x:number, z:number): ItemJson[]
根据位置,获取命中的物品集合
getItemsByPosition({x:number, z:number}): ItemJson[]
---
### getItemsByPositionDistance(x, z, distance): {item, distance}[]
getItemsByPositionDistance(x:number, z:number, distance:number): {item:ItemJson, distance:number}[]
根据位置,获取周边单位距离内的所有物品集合,及距离
---
(选择框)
### getItemsByRect(x1,y1,x2,y2): ItemJson[]
根据矩形,获取与矩形有碰撞的所有物品集合
getItemsByPosition({x:number, z:number}): ItemJson[]
---
### getItemsByRect2(x1,y1,x2,y2): ItemJson[]
根据矩形,获取矩形内的所有物品,物品OBB包围盒必须完全在矩形内
getItemsByPosition({x:number, z:number}): ItemJson[]

3
package.json

@ -63,6 +63,7 @@
"vue-i18n": "9.2.2", "vue-i18n": "9.2.2",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
"vue-tsc": "^2.2.8", "vue-tsc": "^2.2.8",
"vue3-menus": "^1.1.2" "vue3-menus": "^1.1.2",
"three-dxf-viewer": "^1.0.36"
} }
} }

174
pnpm-lock.yaml

@ -123,6 +123,9 @@ importers:
three: three:
specifier: ^0.176.0 specifier: ^0.176.0
version: 0.176.0 version: 0.176.0
three-dxf-viewer:
specifier: ^1.0.36
version: 1.0.36
three-mesh-bvh: three-mesh-bvh:
specifier: ^0.9.0 specifier: ^0.9.0
version: 0.9.0(three@0.176.0) version: 0.9.0(three@0.176.0)
@ -321,151 +324,151 @@ packages:
vue: ^3.2.0 vue: ^3.2.0
'@esbuild/aix-ppc64@0.25.4': '@esbuild/aix-ppc64@0.25.4':
resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==, tarball: https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [ppc64] cpu: [ppc64]
os: [aix] os: [aix]
'@esbuild/android-arm64@0.25.4': '@esbuild/android-arm64@0.25.4':
resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.25.4': '@esbuild/android-arm@0.25.4':
resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/android-x64@0.25.4': '@esbuild/android-x64@0.25.4':
resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/darwin-arm64@0.25.4': '@esbuild/darwin-arm64@0.25.4':
resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/darwin-x64@0.25.4': '@esbuild/darwin-x64@0.25.4':
resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/freebsd-arm64@0.25.4': '@esbuild/freebsd-arm64@0.25.4':
resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-x64@0.25.4': '@esbuild/freebsd-x64@0.25.4':
resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/linux-arm64@0.25.4': '@esbuild/linux-arm64@0.25.4':
resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/linux-arm@0.25.4': '@esbuild/linux-arm@0.25.4':
resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/linux-ia32@0.25.4': '@esbuild/linux-ia32@0.25.4':
resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.25.4': '@esbuild/linux-loong64@0.25.4':
resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/linux-mips64el@0.25.4': '@esbuild/linux-mips64el@0.25.4':
resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/linux-ppc64@0.25.4': '@esbuild/linux-ppc64@0.25.4':
resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/linux-riscv64@0.25.4': '@esbuild/linux-riscv64@0.25.4':
resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/linux-s390x@0.25.4': '@esbuild/linux-s390x@0.25.4':
resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/linux-x64@0.25.4': '@esbuild/linux-x64@0.25.4':
resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@esbuild/netbsd-arm64@0.25.4': '@esbuild/netbsd-arm64@0.25.4':
resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [netbsd] os: [netbsd]
'@esbuild/netbsd-x64@0.25.4': '@esbuild/netbsd-x64@0.25.4':
resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [netbsd] os: [netbsd]
'@esbuild/openbsd-arm64@0.25.4': '@esbuild/openbsd-arm64@0.25.4':
resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [openbsd] os: [openbsd]
'@esbuild/openbsd-x64@0.25.4': '@esbuild/openbsd-x64@0.25.4':
resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@esbuild/sunos-x64@0.25.4': '@esbuild/sunos-x64@0.25.4':
resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/win32-arm64@0.25.4': '@esbuild/win32-arm64@0.25.4':
resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/win32-ia32@0.25.4': '@esbuild/win32-ia32@0.25.4':
resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/win32-x64@0.25.4': '@esbuild/win32-x64@0.25.4':
resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz}
engines: {node: '>=18'} engines: {node: '>=18'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -537,102 +540,113 @@ packages:
optional: true optional: true
'@rollup/rollup-android-arm-eabi@4.41.0': '@rollup/rollup-android-arm-eabi@4.41.0':
resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@rollup/rollup-android-arm64@4.41.0': '@rollup/rollup-android-arm64@4.41.0':
resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rollup/rollup-darwin-arm64@4.41.0': '@rollup/rollup-darwin-arm64@4.41.0':
resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-x64@4.41.0': '@rollup/rollup-darwin-x64@4.41.0':
resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rollup/rollup-freebsd-arm64@4.41.0': '@rollup/rollup-freebsd-arm64@4.41.0':
resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-freebsd-x64@4.41.0': '@rollup/rollup-freebsd-x64@4.41.0':
resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.41.0': '@rollup/rollup-linux-arm-gnueabihf@4.41.0':
resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.41.0': '@rollup/rollup-linux-arm-musleabihf@4.41.0':
resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.41.0': '@rollup/rollup-linux-arm64-gnu@4.41.0':
resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.41.0': '@rollup/rollup-linux-arm64-musl@4.41.0':
resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.41.0': '@rollup/rollup-linux-loongarch64-gnu@4.41.0':
resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.41.0': '@rollup/rollup-linux-powerpc64le-gnu@4.41.0':
resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.41.0': '@rollup/rollup-linux-riscv64-gnu@4.41.0':
resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.41.0': '@rollup/rollup-linux-riscv64-musl@4.41.0':
resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.41.0': '@rollup/rollup-linux-s390x-gnu@4.41.0':
resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.41.0': '@rollup/rollup-linux-x64-gnu@4.41.0':
resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.41.0': '@rollup/rollup-linux-x64-musl@4.41.0':
resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.41.0': '@rollup/rollup-win32-arm64-msvc@4.41.0':
resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.41.0': '@rollup/rollup-win32-ia32-msvc@4.41.0':
resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-msvc@4.41.0': '@rollup/rollup-win32-x64-msvc@4.41.0':
resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -644,7 +658,7 @@ packages:
engines: {node: '>=18'} engines: {node: '>=18'}
'@sxzz/popperjs-es@2.11.7': '@sxzz/popperjs-es@2.11.7':
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==, tarball: https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz}
'@tsconfig/node22@22.0.2': '@tsconfig/node22@22.0.2':
resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==} resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==}
@ -865,7 +879,7 @@ packages:
resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
ansi-regex@6.1.0: ansi-regex@6.1.0:
@ -873,7 +887,7 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
ansi-styles@4.3.0: ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
ansi-styles@6.2.1: ansi-styles@6.2.1:
@ -930,16 +944,19 @@ packages:
resolution: {integrity: sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==} resolution: {integrity: sha512-+aFkvqhaAVr1gferNMuN8vkTSrWIFvzlMV9I2KBLCWS2WpZ2+UAkZjlMZmEuT+gcXTi6RrGQCkWq1/bDtGqhIA==}
color-convert@2.0.1: color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, tarball: https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz}
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@ -998,6 +1015,11 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dxf@5.2.0:
resolution: {integrity: sha512-qk29/318lCOrDWKPEx8MztNjBSGoXoPlpyYkgRwyAKMb8UEZ5nQU5IU3ruAYOtSgCWjiVZng41ros4CNkLuMiQ==, tarball: https://registry.npmmirror.com/dxf/-/dxf-5.2.0.tgz}
engines: {node: '>=8.9.0'}
hasBin: true
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@ -1010,7 +1032,7 @@ packages:
vue: ^3.2.0 vue: ^3.2.0
emoji-regex@8.0.0: emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz}
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@ -1020,7 +1042,7 @@ packages:
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
errno@0.1.8: errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==, tarball: https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz}
hasBin: true hasBin: true
error-stack-parser-es@0.1.5: error-stack-parser-es@0.1.5:
@ -1098,7 +1120,7 @@ packages:
engines: {node: '>=14.14'} engines: {node: '>=14.14'}
fsevents@2.3.3: fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
@ -1135,7 +1157,7 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz}
has-symbols@1.1.0: has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
@ -1168,7 +1190,7 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
image-size@0.5.5: image-size@0.5.5:
resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==, tarball: https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
hasBin: true hasBin: true
@ -1181,7 +1203,7 @@ packages:
hasBin: true hasBin: true
is-fullwidth-code-point@3.0.0: is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
is-inside-container@1.0.0: is-inside-container@1.0.0:
@ -1284,7 +1306,7 @@ packages:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
make-dir@2.1.0: make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, tarball: https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz}
engines: {node: '>=6'} engines: {node: '>=6'}
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
@ -1310,7 +1332,7 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
mime@1.6.0: mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, tarball: https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz}
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
@ -1350,7 +1372,7 @@ packages:
hasBin: true hasBin: true
needle@3.3.1: needle@3.3.1:
resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==, tarball: https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz}
engines: {node: '>= 4.4.x'} engines: {node: '>= 4.4.x'}
hasBin: true hasBin: true
@ -1547,7 +1569,7 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.6.1: source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
speakingurl@14.0.1: speakingurl@14.0.1:
@ -1558,7 +1580,7 @@ packages:
resolution: {integrity: sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==} resolution: {integrity: sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==}
string-width@4.2.3: string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, tarball: https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
string-width@5.1.2: string-width@5.1.2:
@ -1566,7 +1588,7 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
strip-ansi@6.0.1: strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
strip-ansi@7.1.0: strip-ansi@7.1.0:
@ -1581,11 +1603,17 @@ packages:
resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
engines: {node: '>=16'} engines: {node: '>=16'}
three-dxf-viewer@1.0.36:
resolution: {integrity: sha512-Tu7k+/yCyovpMJTeQ8fpGHdjtynq66vMMXYzDCwI9prJo2dRDxig4S+ixQVuMIy3I99VZa51z5KwlkeWyCnFzQ==, tarball: https://registry.npmmirror.com/three-dxf-viewer/-/three-dxf-viewer-1.0.36.tgz}
three-mesh-bvh@0.9.0: three-mesh-bvh@0.9.0:
resolution: {integrity: sha512-xAwZj0hZknpwVsdK5BBJTIAZDjDPZCRzURY1o+z/JHBON/jc2UetK1CzPeQZiiOVSfI4jV2z7sXnnGtgsgnjaA==} resolution: {integrity: sha512-xAwZj0hZknpwVsdK5BBJTIAZDjDPZCRzURY1o+z/JHBON/jc2UetK1CzPeQZiiOVSfI4jV2z7sXnnGtgsgnjaA==}
peerDependencies: peerDependencies:
three: '>= 0.159.0' three: '>= 0.159.0'
three@0.171.0:
resolution: {integrity: sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==, tarball: https://registry.npmmirror.com/three/-/three-0.171.0.tgz}
three@0.176.0: three@0.176.0:
resolution: {integrity: sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==} resolution: {integrity: sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==}
@ -1635,6 +1663,9 @@ packages:
peerDependencies: peerDependencies:
browserslist: '>= 4.21.0' browserslist: '>= 4.21.0'
vecks@3.9.2:
resolution: {integrity: sha512-ubOo1KoOrPu3llYmlE4SH60JWcR+YDaObP+1Kzpbcl09dKIf0fbBweW8Xyg+6Nk2Rrnyit6Jxw9bx2LOYpMQMA==, tarball: https://registry.npmmirror.com/vecks/-/vecks-3.9.2.tgz}
vite-hot-client@2.0.4: vite-hot-client@2.0.4:
resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==} resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==}
peerDependencies: peerDependencies:
@ -1765,7 +1796,7 @@ packages:
hasBin: true hasBin: true
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, tarball: https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz}
engines: {node: '>=10'} engines: {node: '>=10'}
wrap-ansi@8.1.0: wrap-ansi@8.1.0:
@ -2607,6 +2638,8 @@ snapshots:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
commander@2.20.3: {}
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
copy-anything@2.0.6: copy-anything@2.0.6:
@ -2654,6 +2687,12 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
dxf@5.2.0:
dependencies:
commander: 2.20.3
lodash: 4.17.21
vecks: 3.9.2
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.155: {} electron-to-chromium@1.5.155: {}
@ -3224,10 +3263,17 @@ snapshots:
dependencies: dependencies:
copy-anything: 3.0.5 copy-anything: 3.0.5
three-dxf-viewer@1.0.36:
dependencies:
dxf: 5.2.0
three: 0.171.0
three-mesh-bvh@0.9.0(three@0.176.0): three-mesh-bvh@0.9.0(three@0.176.0):
dependencies: dependencies:
three: 0.176.0 three: 0.176.0
three@0.171.0: {}
three@0.176.0: {} three@0.176.0: {}
tinyglobby@0.2.13: tinyglobby@0.2.13:
@ -3267,6 +3313,8 @@ snapshots:
escalade: 3.2.0 escalade: 3.2.0
picocolors: 1.1.1 picocolors: 1.1.1
vecks@3.9.2: {}
vite-hot-client@2.0.4(vite@6.3.5(@types/node@22.15.21)(less@4.3.0)): vite-hot-client@2.0.4(vite@6.3.5(@types/node@22.15.21)(less@4.3.0)):
dependencies: dependencies:
vite: 6.3.5(@types/node@22.15.21)(less@4.3.0) vite: 6.3.5(@types/node@22.15.21)(less@4.3.0)

BIN
src/assets/Models/PallTex.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

BIN
src/assets/Models/Pallet.3ds

Binary file not shown.

BIN
src/assets/Models/Queue/QueTex.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

BIN
src/assets/Models/Queue/Queue.3ds

Binary file not shown.

BIN
src/assets/Models/Tote.3ds

Binary file not shown.

BIN
src/assets/Models/ToteTex.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
src/assets/Models/carton.glb

Binary file not shown.

BIN
src/assets/Models/carton.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
src/assets/Models/storageBar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/assets/Models/storageBar2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

1281
src/assets/fonts/helvetiker_regular.typeface.json

File diff suppressed because it is too large

BIN
src/assets/images/conveyor/shapes/triangle2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

514
src/components/Model3DView.vue

@ -2,9 +2,13 @@
<div class="model3d-view"> <div class="model3d-view">
<el-space :gutter="10" class="toolbar"> <el-space :gutter="10" class="toolbar">
<el-upload :on-change="handleFileChange" <el-upload :on-change="handleFileChange"
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false"> :show-file-list="false" accept=".fbx,.obj,.mtl,.3ds,.glb,.gltf" action="" :auto-upload="false">
<el-button type="primary">打开模型</el-button> <el-button type="primary">打开模型</el-button>
</el-upload> </el-upload>
<el-upload :on-change="handleCadFileChange"
:show-file-list="false" accept=".dxf" action="" :auto-upload="false">
<el-button type="primary">打开CAD</el-button>
</el-upload>
<el-upload :on-change="handleTextureUpload" <el-upload :on-change="handleTextureUpload"
:show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false" :show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false"
list-type="picture"> list-type="picture">
@ -80,15 +84,24 @@ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js' import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js' import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js'
import Stats from 'three/examples/jsm/libs/stats.module' import Stats from 'three/examples/jsm/libs/stats.module'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js' import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import Split from '@/components/split/split.vue' import Split from '@/components/split/split.vue'
import SplitArea from '@/components/split/split-area.vue' import SplitArea from '@/components/split/split-area.vue'
import { renderIcon } from '@/utils/webutils.js' import { renderIcon } from '@/utils/webutils.js'
import rackPlatUrl from "@/assets/images/conveyor/shapes/RackPlatform.png"; import rackPlatUrl from '@/assets/images/conveyor/shapes/RackPlatform.png'
import rackBlue from "@/assets/images/conveyor/shapes/Rack-blue.png"; import rackBlue from '@/assets/images/conveyor/shapes/Rack-blue.png'
import triangleUrl from "@/assets/images/conveyor/shapes/triangle.png"; import triangleUrl from '@/assets/images/conveyor/shapes/triangle.png'
import textureUrl from '@/assets/images/conveyor/shapes/RibSideSkirtThumbnail.jpg'
import moveUrl from '@/assets/images/conveyor/shapes/move.svg'
import arrowRightUrl from '@/assets/images/conveyor/shapes/arrow-right.svg'
import rackUrl from '@/assets/images/conveyor/shapes/Rack.png'
import Plastic_Rough_JPG from "@/assets/Models/Plastic_Rough.jpg";
import storageBar_PNG from "@/assets/Models/storageBar.png";
import {DXFViewer} from "three-dxf-viewer";
import cadFont from "@/assets/fonts/helvetiker_regular.typeface.json?url";
// DOM refs // DOM refs
const canvasContainer = ref(null) const canvasContainer = ref(null)
@ -168,118 +181,123 @@ watch(() => restate.mode, (newVal) => {
tcontrols.setMode(newVal) tcontrols.setMode(newVal)
} }
}) })
function addConveyor(){
const conveyorLength = 10; //
const conveyorWidth = 0.8; //
const conveyorHeight = 0.08; //
const wallHeight = conveyorHeight + 0.15; // 线 0.2
const legWidth = 0.1; // X /**
const legDepth = 0.1; // Z * 添加输送线
const legHeight = 0.8; // Y */
function addConveyor() {
const conveyorLength = 10 //
const conveyorWidth = 0.8 //
const conveyorHeight = 0.08 //
const wallHeight = conveyorHeight + 0.15 // 线 0.2
const legWidth = 0.1 // X
const legDepth = 0.1 // Z
const legHeight = 0.8 // Y
// 线 // 线
const conveyorGeometry = new THREE.BoxGeometry(conveyorWidth, conveyorHeight, conveyorLength); const conveyorGeometry = new THREE.BoxGeometry(conveyorWidth, conveyorHeight, conveyorLength)
const conveyorMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); const conveyorMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a })
const conveyor = new THREE.Mesh(conveyorGeometry, conveyorMaterial); const conveyor = new THREE.Mesh(conveyorGeometry, conveyorMaterial)
conveyor.position.y = conveyorHeight / 2; conveyor.position.y = conveyorHeight / 2
// //
const wallWidth = 0.05; const wallWidth = 0.05
const wallGeometry = new THREE.BoxGeometry(wallWidth, wallHeight, conveyorLength); const wallGeometry = new THREE.BoxGeometry(wallWidth, wallHeight, conveyorLength)
const wallMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); const wallMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a })
const leftWall = new THREE.Mesh(wallGeometry, wallMaterial); const leftWall = new THREE.Mesh(wallGeometry, wallMaterial)
leftWall.position.set(-(conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); leftWall.position.set(-(conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0)
const rightWall = new THREE.Mesh(wallGeometry, wallMaterial); const rightWall = new THREE.Mesh(wallGeometry, wallMaterial)
rightWall.position.set((conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0); rightWall.position.set((conveyorWidth / 2 + wallWidth / 2), wallHeight / 2, 0)
// //
const legGeometry = new THREE.BoxGeometry(legWidth, legHeight, legDepth); const legGeometry = new THREE.BoxGeometry(legWidth, legHeight, legDepth)
const legMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }); // const legMaterial = new THREE.MeshBasicMaterial({ color: 0x6a6a6a }) //
const legPositions = [ const legPositions = [
[conveyorWidth / 2 - legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // [conveyorWidth / 2 - legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], //
[-conveyorWidth / 2 + legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], // [-conveyorWidth / 2 + legWidth / 2, -(conveyorLength / 2 - legDepth / 2)], //
[conveyorWidth / 2 - legWidth / 2, +(conveyorLength / 2 - legDepth / 2)], // [conveyorWidth / 2 - legWidth / 2, +(conveyorLength / 2 - legDepth / 2)], //
[-conveyorWidth / 2 + legWidth / 2, +(conveyorLength / 2 - legDepth / 2)] // [-conveyorWidth / 2 + legWidth / 2, +(conveyorLength / 2 - legDepth / 2)] //
]; ]
const legs = []; const legs = []
legPositions.forEach(pos => { legPositions.forEach(pos => {
const leg = new THREE.Mesh(legGeometry, legMaterial); const leg = new THREE.Mesh(legGeometry, legMaterial)
leg.position.set( leg.position.set(
pos[0], pos[0],
-legHeight / 2, -legHeight / 2,
pos[1] pos[1]
); )
legs.push(leg); legs.push(leg)
}); })
// ====== ====== // ====== ======
const beamWidth = conveyorWidth; // 线0.8 const beamWidth = conveyorWidth // 线0.8
const beamLength = conveyorLength-conveyorHeight/2;// 线10 const beamLength = conveyorLength - conveyorHeight / 2// 线10
const beamHeight = 0.08; // 0.1 const beamHeight = 0.08 // 0.1
// Shape // Shape
const shape = new THREE.Shape(); const shape = new THREE.Shape()
const radius = beamHeight / 2; // = const radius = beamHeight / 2 // =
// //
shape.absarc(-beamLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2, false); shape.absarc(-beamLength / 2, 0, radius, Math.PI / 2, -Math.PI / 2, false)
// //
shape.absarc(beamLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false); shape.absarc(beamLength / 2, 0, radius, -Math.PI / 2, Math.PI / 2, false)
shape.closePath(); // shape.closePath() //
// //
const extrudeSettings = { const extrudeSettings = {
depth: beamWidth, depth: beamWidth,
steps: 16, steps: 16,
bevelEnabled: false bevelEnabled: false
}; }
const beamGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); const beamGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
const beamMaterial = new THREE.MeshBasicMaterial({ color: 0x032702 }); // const beamMaterial = new THREE.MeshBasicMaterial({ color: 0x032702 }) //
const beam = new THREE.Mesh(beamGeometry, beamMaterial); const beam = new THREE.Mesh(beamGeometry, beamMaterial)
beam.rotation.x = Math.PI / 2; beam.rotation.x = Math.PI / 2
beam.rotation.z = Math.PI / 2 beam.rotation.z = Math.PI / 2
beam.rotation.y = Math.PI / 2 beam.rotation.y = Math.PI / 2
beam.position.y = beamHeight+conveyorHeight/2; // 线 beam.position.y = beamHeight + conveyorHeight / 2 // 线
beam.position.x = -beamWidth/2; beam.position.x = -beamWidth / 2
addMarkerAt(0, legHeight+beamHeight+conveyorHeight,0,triangleUrl,1,false); addMarkerAt(0, legHeight + beamHeight + conveyorHeight, 0, triangleUrl, 1, false)
addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,0,triangleUrl,.5,true); addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, 0, triangleUrl, .5, true)
addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, conveyorLength / 4, triangleUrl, .5, true)
addMarkerAt(beamWidth/2+wallWidth+0.01, legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); addMarkerAt(beamWidth / 2 + wallWidth + 0.01, legHeight + wallHeight / 2, -conveyorLength / 4, triangleUrl, .5, true)
addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,0,triangleUrl,.5,true); addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, 0, triangleUrl, .5, true)
addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,conveyorLength/4,triangleUrl,.5,true); addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, conveyorLength / 4, triangleUrl, .5, true)
addMarkerAt(-(beamWidth/2+wallWidth+0.01), legHeight+wallHeight/2,-conveyorLength/4,triangleUrl,.5,true); addMarkerAt(-(beamWidth / 2 + wallWidth + 0.01), legHeight + wallHeight / 2, -conveyorLength / 4, triangleUrl, .5, true)
// //
const group = new THREE.Group(); const group = new THREE.Group()
group.add(conveyor); group.add(conveyor)
group.add(leftWall); group.add(leftWall)
group.add(rightWall); group.add(rightWall)
legs.forEach(leg => group.add(leg)); legs.forEach(leg => group.add(leg))
group.add(beam); // group.add(beam) //
// 沿Z 1 // 沿Z 1
group.position.y = legHeight; // group.position.y = legHeight //
scene.add(group); scene.add(group)
} }
function addMarkerAt(x, y,z,textUrl, scale = 1,lip) {
const loader = new THREE.TextureLoader(); function addMarkerAt(x, y, z, textUrl, scale = 1, lip) {
const loader = new THREE.TextureLoader()
loader.load(textUrl, (texture) => { loader.load(textUrl, (texture) => {
// //
let markerGeometry let markerGeometry
if(lip){ if (lip) {
markerGeometry=new THREE.PlaneGeometry(.25 * scale, .25 * scale); markerGeometry = new THREE.PlaneGeometry(.25 * scale, .25 * scale)
}else{ } else {
markerGeometry = new THREE.PlaneGeometry(.45 * scale, .3 * scale); // markerGeometry = new THREE.PlaneGeometry(.45 * scale, .3 * scale) //
} }
const markerMaterial = new THREE.MeshBasicMaterial({ const markerMaterial = new THREE.MeshBasicMaterial({
@ -288,193 +306,346 @@ function addMarkerAt(x, y,z,textUrl, scale = 1,lip) {
opacity: 1, opacity: 1,
depthWrite: false, depthWrite: false,
side: THREE.DoubleSide side: THREE.DoubleSide
}); })
const marker = new THREE.Mesh(markerGeometry, markerMaterial); const marker = new THREE.Mesh(markerGeometry, markerMaterial)
let yHeight=y+0.01 let yHeight = y + 0.01
// 线Y线 // 线Y线
marker.position.set(x, yHeight, z); // Y = 线 + marker.position.set(x, yHeight, z) // Y = 线 +
// X/Z 线 // X/Z 线
marker.rotation.x = -Math.PI / 2; // conveyorBelt marker.rotation.x = -Math.PI / 2 // conveyorBelt
marker.rotation.z = Math.PI / 2; // 90 marker.rotation.z = Math.PI / 2 // 90
if(lip){ if (lip) {
marker.rotation.y = Math.PI / 2; // 90 marker.rotation.y = Math.PI / 2 // 90
}else{ } else {
marker.rotation.y = Math.PI; marker.rotation.y = Math.PI
} }
scene.add(marker); scene.add(marker)
}); })
} }
function createShelf(){// function createShelf() {//
const shelfLength = 5; const shelfLength = 5
const shelfWidth = 0.8; const shelfWidth = 0.8
const shelfThickness = 0.02; const shelfThickness = 0.02
const rows = 3; const rows = 3
const columns = 4; const columns = 4
const spacingY = 1; // const spacingY = 1 //
const gapBetweenPlanks = 0.1; // const gapBetweenPlanks = 0.1 //
const baseHeight = 0; // 0 const baseHeight = 0 // 0
const unitLength = (shelfLength - (columns - 1) * gapBetweenPlanks) / columns; const unitLength = (shelfLength - (columns - 1) * gapBetweenPlanks) / columns
const textureLoader = new THREE.TextureLoader(); const textureLoader = new THREE.TextureLoader()
const shelfTexture = textureLoader.load(rackPlatUrl, () => { const shelfTexture = textureLoader.load(rackPlatUrl, () => {
renderer.render(scene, camera); renderer.render(scene, camera)
}); })
const redPoleTexture = textureLoader.load(rackBlue, () => { const redPoleTexture = textureLoader.load(rackBlue, () => {
renderer.render(scene, camera); // renderer.render(scene, camera) //
}); })
shelfTexture.wrapS = THREE.RepeatWrapping; shelfTexture.wrapS = THREE.RepeatWrapping
shelfTexture.wrapT = THREE.RepeatWrapping; shelfTexture.wrapT = THREE.RepeatWrapping
shelfTexture.repeat.set(1, 20); shelfTexture.repeat.set(1, 20)
shelfTexture.rotation = Math.PI / 2; shelfTexture.rotation = Math.PI / 2
const shelfMaterial = new THREE.MeshPhongMaterial({ const shelfMaterial = new THREE.MeshPhongMaterial({
map: shelfTexture, map: shelfTexture,
color: 0x999999, color: 0x999999,
shininess: 60 shininess: 60
}); })
// //
const poleMaterial = new THREE.MeshPhongMaterial({ const poleMaterial = new THREE.MeshPhongMaterial({
map: shelfTexture, map: shelfTexture,
color: 0x333333, color: 0x333333,
shininess: 60 shininess: 60
}); })
// //
const poleSize = gapBetweenPlanks; // const poleSize = gapBetweenPlanks //
const totalHeight = rows * spacingY; // const totalHeight = rows * spacingY //
for (let row = 0; row < rows; row++) { for (let row = 0; row < rows; row++) {
let currentXOffset = -shelfLength / 2; let currentXOffset = -shelfLength / 2
for (let col = 0; col < columns; col++) { for (let col = 0; col < columns; col++) {
const geometry = new THREE.BoxGeometry(unitLength, shelfThickness, shelfWidth); const geometry = new THREE.BoxGeometry(unitLength, shelfThickness, shelfWidth)
const mesh = new THREE.Mesh(geometry, shelfMaterial); const mesh = new THREE.Mesh(geometry, shelfMaterial)
const x = currentXOffset + unitLength / 2; const x = currentXOffset + unitLength / 2
// Y 使 // Y 使
const y = baseHeight + row * spacingY + shelfThickness / 2; const y = baseHeight + row * spacingY + shelfThickness / 2
const z = 0; const z = 0
mesh.position.set(x, y, z); mesh.position.set(x, y, z)
scene.add(mesh); scene.add(mesh)
if (row === 0) { // if (row === 0) { //
// //
if (col === 0) { if (col === 0) {
const leftPoleX = currentXOffset - poleSize / 2; const leftPoleX = currentXOffset - poleSize / 2
const leftPoleY = baseHeight + totalHeight / 2; const leftPoleY = baseHeight + totalHeight / 2
const leftPoleZ = z - shelfWidth / 2 + poleSize / 2; const leftPoleZ = z - shelfWidth / 2 + poleSize / 2
// //
const leftPole = new THREE.Mesh( const leftPole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
poleMaterial poleMaterial
); )
leftPole.position.set(leftPoleX, leftPoleY, leftPoleZ); leftPole.position.set(leftPoleX, leftPoleY, leftPoleZ)
scene.add(leftPole); scene.add(leftPole)
// // // //
const redLeftPoleZ = z + shelfWidth / 2 - poleSize / 2; const redLeftPoleZ = z + shelfWidth / 2 - poleSize / 2
const redLeftPole = new THREE.Mesh( const redLeftPole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
new THREE.MeshPhongMaterial({ new THREE.MeshPhongMaterial({
map: redPoleTexture, map: redPoleTexture,
color: 0xffffff, // color: 0xffffff, //
shininess: 60, shininess: 60,
transparent: true, transparent: true
}) })
// new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) // new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 })
); )
redLeftPole.position.set(leftPoleX, leftPoleY, redLeftPoleZ); redLeftPole.position.set(leftPoleX, leftPoleY, redLeftPoleZ)
scene.add(redLeftPole); scene.add(redLeftPole)
} }
// //
if (col < columns - 1) { if (col < columns - 1) {
const gapStartX = currentXOffset + unitLength; const gapStartX = currentXOffset + unitLength
// //
const poleX = gapStartX + poleSize / 2; const poleX = gapStartX + poleSize / 2
const poleY = baseHeight + totalHeight / 2; const poleY = baseHeight + totalHeight / 2
const poleZ = z- shelfWidth / 2+poleSize/2; const poleZ = z - shelfWidth / 2 + poleSize / 2
const pole = new THREE.Mesh( const pole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
poleMaterial poleMaterial
); )
pole.position.set(poleX, poleY, poleZ); pole.position.set(poleX, poleY, poleZ)
scene.add(pole); scene.add(pole)
// //
const redPoleX = poleX; // X const redPoleX = poleX // X
const redPoleY = poleY; // Y const redPoleY = poleY // Y
const redPoleZ = z + shelfWidth / 2-poleSize/2; // const redPoleZ = z + shelfWidth / 2 - poleSize / 2 //
const redPoleMaterial = new THREE.MeshPhongMaterial({ const redPoleMaterial = new THREE.MeshPhongMaterial({
color: 0xff0000, color: 0xff0000,
shininess: 60 shininess: 60
}); })
const redPole = new THREE.Mesh( const redPole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
redPoleMaterial redPoleMaterial
); )
redPole.position.set(redPoleX, redPoleY, redPoleZ); redPole.position.set(redPoleX, redPoleY, redPoleZ)
scene.add(redPole); scene.add(redPole)
} }
// //
if (col === columns - 1) { if (col === columns - 1) {
const rightPoleX = currentXOffset + unitLength + gapBetweenPlanks - poleSize / 2; const rightPoleX = currentXOffset + unitLength + gapBetweenPlanks - poleSize / 2
const rightPoleY = baseHeight + totalHeight / 2; const rightPoleY = baseHeight + totalHeight / 2
const rightPoleZ = z - shelfWidth / 2 + poleSize / 2; const rightPoleZ = z - shelfWidth / 2 + poleSize / 2
// //
const rightPole = new THREE.Mesh( const rightPole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
poleMaterial poleMaterial
); )
rightPole.position.set(rightPoleX, rightPoleY, rightPoleZ); rightPole.position.set(rightPoleX, rightPoleY, rightPoleZ)
scene.add(rightPole); scene.add(rightPole)
// //
const redRightPoleZ = z + shelfWidth / 2 - poleSize / 2; const redRightPoleZ = z + shelfWidth / 2 - poleSize / 2
const redRightPole = new THREE.Mesh( const redRightPole = new THREE.Mesh(
new THREE.BoxGeometry(poleSize, totalHeight, poleSize), new THREE.BoxGeometry(poleSize, totalHeight, poleSize),
new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 }) new THREE.MeshPhongMaterial({ color: 0xff0000, shininess: 60 })
); )
redRightPole.position.set(rightPoleX, rightPoleY, redRightPoleZ); redRightPole.position.set(rightPoleX, rightPoleY, redRightPoleZ)
scene.add(redRightPole); scene.add(redRightPole)
} }
} }
currentXOffset += unitLength + gapBetweenPlanks; currentXOffset += unitLength + gapBetweenPlanks
} }
} }
// //
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
scene.add(ambientLight); scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
directionalLight.position.set(5, 10, 7); directionalLight.position.set(5, 10, 7)
scene.add(directionalLight); scene.add(directionalLight)
} }
function createGroundStore() { function createGroundStore() {
const planeGeometry = new THREE.PlaneGeometry(1, 1); const planeGeometry = new THREE.PlaneGeometry(1, 1)
const material = new THREE.MeshBasicMaterial({ const material = new THREE.MeshBasicMaterial({
color: 0x00ff00, color: 0x00ff00,
side: THREE.DoubleSide // :ml-citation{ref="5,8" data="citationList"} side: THREE.DoubleSide // :ml-citation{ref="5,8" data="citationList"}
}); })
const planeMesh = new THREE.Mesh(planeGeometry, material); const planeMesh = new THREE.Mesh(planeGeometry, material)
planeMesh.rotateX(Math.PI / 2) planeMesh.rotateX(Math.PI / 2)
scene.add(planeMesh); scene.add(planeMesh)
let textureLoader = new THREE.TextureLoader()
const texture1 = textureLoader.load(storageBar_PNG);
const texture2 = textureLoader.load(Plastic_Rough_JPG);
const curve = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 2, 0)],
false, // 线
'catmullrom',
0
);
// const points = curve.getPoints(10);
// const geometry = new THREE.BufferGeometry().setFromPoints(points);
// const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });
// const lineS = new THREE.Line(geometry, material);
// group.add(lineS as THREE.Object3D)
const splits = [
[0, 0],
[0.025, 0],
[0.04, 0.005],
[0.06, 0.005],
[0.075, 0],
[0.1, 0],
[0.1, 0.092],
[0.092, 0.1],
[0.075, 0.1],
[0.075, 0.092],
[0.092, 0.092],
[0.092, 0.008],
[0.008, 0.008],
[0.008, 0.092],
[0.025, 0.092],
[0.025, 0.1],
[0.008, 0.1],
[0, 0.092],
[0, 0]
]
let pointsShape = [];
for (let i = 0; i < splits.length ; i ++) {
let _x = splits[i][0] //* 10;
let _y = splits[i][1] //* 10;
pointsShape.push({
x: _x,
y: _y,
});
}
const shape = new THREE.Shape();
shape.moveTo(pointsShape[0].x, pointsShape[0].y);
for (let i = 1; i < pointsShape.length; i++) {
shape.lineTo(pointsShape[i].x , pointsShape[i].y);
}
const options = {
steps: 1,
bevelEnabled: false,
extrudePath: curve,
};
const geometry1 = new THREE.ExtrudeGeometry(shape, options);
resetUVs(geometry1);
// U V
texture1.repeat.set(10, 18); // X2Y3
texture2.repeat.set(2, 2); // X2Y3
// texture1.offset.set(0.5, 0)
texture1.center.set(0.5, 0)
//
texture1.wrapS = THREE.RepeatWrapping;
texture1.wrapT = THREE.RepeatWrapping;
texture2.wrapS = THREE.RepeatWrapping;
texture2.wrapT = THREE.RepeatWrapping;
const material1 = new THREE.MeshPhongMaterial({
// color: '#FF3549',
alphaMap: texture1, //
normalMap: texture2, //
// side: THREE.DoubleSide //
// metalness: 0.6,
// roughness: 0.8,
// specular: 0x6d6d6d,
transparent: true,
needsUpdate: true,
});
material1.color.setHex(0xFF35499C, "srgb");
geometry1.scale(0.8, 1, 1)
// const material1 = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// const mesh1 = new THREE.Mesh(geometry1, material1);
let mesh = new THREE.InstancedMesh(geometry1, material1, 1600);
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
mesh.castShadow = true;
mesh.receiveShadow = true;
let dummy = new THREE.Object3D();
for (let i = 0; i < 40; i++) {
for (let j = 0; j < 40; j++) {
dummy.position.set(i * 0.5, 0, j * 0.5);
dummy.updateMatrix();
mesh.setMatrixAt(i*40 + j, dummy.matrix);
}
}
scene.add(mesh);
}
function resetUVs(geometry) {
if (geometry == undefined) return;
var pos = geometry.getAttribute("position"),
nor = geometry.getAttribute("normal"),
uvs = geometry.getAttribute("uv");
for (var i = 0; i < pos.count; i++) {
var x = 0,
y = 0;
var nx = Math.abs(nor.getX(i)),
ny = Math.abs(nor.getY(i)),
nz = Math.abs(nor.getZ(i));
// if facing X
if (nx >= ny && nx >= nz) {
x = pos.getZ(i);
y = pos.getY(i);
}
// if facing Y
if (ny >= nx && ny >= nz) {
x = pos.getX(i);
y = pos.getZ(i);
}
// if facing Z
if (nz >= nx && nz >= ny) {
x = pos.getX(i);
y = pos.getY(i);
}
uvs.setXY(i, x, y);
}
} }
function initThree() { function initThree() {
@ -837,7 +1008,6 @@ function handleFileChange(file) {
system.clearLoading() system.clearLoading()
} }
reader.readAsArrayBuffer(file)
} else if (fileName.endsWith('.obj')) { } else if (fileName.endsWith('.obj')) {
reader.readAsText(file) reader.readAsText(file)
@ -861,13 +1031,41 @@ function handleFileChange(file) {
system.clearLoading() system.clearLoading()
} }
reader.readAsArrayBuffer(file)
} else if (fileName.endsWith('.glb') || fileName.endsWith('.gltf')) {
reader.onload = () => {
const loader = new GLTFLoader()
const arrayBuffer = reader.result
//@ts-ignore
loader.parseAsync(arrayBuffer, '').then((content) => {
addGroupToScene(content.scene)
})
system.clearLoading()
}
} else { } else {
alert('不支持的文件类型!') alert('不支持的文件类型!')
return
} }
reader.readAsArrayBuffer(file)
} }
async function handleCadFileChange(file) {
console.log('file', file)
if (!file) return
file = file.raw
const viewer = new DXFViewer();
let dxf = await viewer.getFromFile(file, cadFont);
dxf.scale.set(0.001, 0.001, 0.001)
dxf.position.x = -3
dxf.position.y = 1
dxf.rotation.x = -Math.PI / 2
// Add the geometry to the scene
scene.add(dxf);
}
function cleaupModel() { function cleaupModel() {
if (modelGroup) { if (modelGroup) {

828
src/components/ThreePerfView2.vue

@ -0,0 +1,828 @@
<template>
<div class="model3d-view">
<el-space :gutter="10" class="toolbar">
<el-button type="primary" @click="test1">测试1</el-button>
<el-button type="primary" @click="test2">测试2</el-button>
<el-button type="primary" @click="test3">测试3</el-button>
<div class="demo-color-block">
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span>
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span>
<span class="demonstration"> 三角形:<el-text type="danger">{{ restate.faces }}</el-text></span>
<span class="demonstration"> 标签:<el-text type="danger">{{ restate.viewLabelCount }}</el-text></span>
</div>
</el-space>
<div class="main-content">
<div class="canvas-container" ref="canvasContainer" />
</div>
</div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import { getCurrentInstance, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { Line2 } from 'three/examples/jsm/lines/Line2'
import MeasureRenderer from '@/modules/measure/MeasureRenderer.ts'
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2'
import { Text } from 'troika-three-text'
import SimSunTTF from '@/assets/fonts/simsunb.ttf'
const canvasContainer = ref(null)
let resizeObserver: ResizeObserver | null = null
let scene: THREE.Scene | null = null
let renderer: THREE.WebGLRenderer | null = null
let viewerDom: HTMLElement | null = null
let camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null = null
let controls: OrbitControls | null = null
let axesHelper: THREE.AxesHelper | null = null
let gridHelper: THREE.GridHelper | null = null
let statsControls: Stats | null = null
let animationFrameId: number | null = null
let modelGroup = new THREE.Group()
const pointMaterial = new THREE.SpriteMaterial({
color: 0xFFFF99, // 0x303133,
transparent: true,
side: THREE.DoubleSide,
opacity: 1,
sizeAttenuation: true
})
// const lineMaterial = new LineMaterial({
// color: 0xFF8C00,
// linewidth: 5,
// vertexColors: false,
// dashed: false
// })
const lineMaterial = new LineMaterial({
color: 0xFF8C00,
linewidth: 5
})
/**
* 测试1创建平面标识
*/
function test1() {
// cleanupThree()
const [x, y, z] = [5, 0, 5]
//
const width = 1.5
//
const depth = 2.0
// 线
const strokeWidth = 0.1
//
const sectionCount = 1
const material = new THREE.MeshBasicMaterial({
color: 0xd8dad0,
transparent: true,
opacity: 1,
side: THREE.BackSide
})
const mesh = new THREE.Mesh(
createGroundMarking(strokeWidth, width, depth, sectionCount, x, y, z),
material)
scene.add(mesh)
refreshCount()
}
/**
* 创建平面标识
*/
function createGroundMarking(weight: number, width: number, depth: number, sectionCount: number, x: number, y: number, z: number, anchor?: THREE.Vector3) {
const sectionDepth = (depth - (sectionCount + 1) * weight) / sectionCount
const shape = buildRectangle(width, depth)
for (let i = 0; i < sectionCount; i++) {
shape.holes.push(buildRectangle(width - 2 * weight, sectionDepth, 0, weight, weight + i * (weight + sectionDepth)))
}
return createExtrudeItem(shape, 0.1, BasePlane.BOTTOM, x, y, -z, null, anchor)
}
/**
* 根据自定义的 Shape通过放样得到一个实体默认实体的样式是在 front 面放样的
*
* @param {*} shape 自定义的型状
* @param {*} depth 放样深度放样后的物体厚度
* @param {*} basePlane 放样的基准面
* @param {*} x 目标定位x
* @param {*} y 目标定位y
* @param {*} z 目标定位z
* @param {*} euler 旋转到目标位
* @param {*} anchor 锚点
* @returns THREE.ExtrudeGeometry
*/
function createExtrudeItem(shape, depth, basePlane, x = 0, y = 0, z = 0, euler = null, anchor = new THREE.Vector3(0, 0, 0)) {
const geometry = new THREE.ExtrudeGeometry(shape, {
steps: 1,
depth: -depth,
bevelEnabled: false,
bevelThickness: 0,
bevelSize: 0,
bevelOffset: 0,
bevelSegments: 0
})
geometry.center()
const size = geometry.boundingBox.getSize(new THREE.Vector3())
let dx, dy, dz
switch (basePlane) {
case BasePlane.LEFT:
geometry.rotateY(Math.PI / 2)
dx = size.z / 2
dy = size.y / 2
dz = size.x / 2
break
case BasePlane.RIGHT:
geometry.rotateY(-Math.PI / 2)
dx = size.z / 2
dy = size.y / 2
dz = size.x / 2
break
case BasePlane.BEHIND:
geometry.translate(0, 0, size.z)
dx = size.x / 2
dy = size.y / 2
dz = size.z / 2
break
case BasePlane.TOP:
geometry.rotateZ(Math.PI)
geometry.rotateX(Math.PI / 2)
geometry.translate(size.x, -size.z, 0)
dx = size.x / 2
dy = size.z / 2
dz = size.y / 2
break
case BasePlane.BOTTOM:
geometry.rotateX(-Math.PI / 2)
dx = size.x / 2
dy = size.z / 2
dz = size.y / 2
break
default:
//BasePlane.FRONT:
dx = size.x / 2
dy = size.y / 2
dz = size.z / 2
break
}
if (euler != null && euler.isEuler) {
//
geometry.rotateX(euler.x)
geometry.rotateY(euler.y)
geometry.rotateZ(euler.z)
}
geometry.translate(dx + x + anchor.x, dy + y + anchor.y, -dz + z + anchor.z)
return geometry
}
/**
* 创建一个矩形的 Shape
*/
function buildRectangle(width: number, height: number, padding: number[] | number = 0, offsetX = 0, offsetY = 0) {
let pl = 0,
pt = 0,
pr = 0,
pb = 0
if (typeof padding == 'number') {
pl = padding
pt = padding
pr = padding
pb = padding
} else if (Array.isArray(padding)) {
if (padding.length == 1) {
pl = padding[0]
pt = padding[0]
pr = padding[0]
pb = padding[0]
} else if (padding.length > 1 && padding.length < 4) {
pl = padding[0]
pt = padding[1]
pr = padding[0]
pb = padding[1]
} else if (padding.length >= 4) {
pl = padding[0]
pt = padding[1]
pr = padding[2]
pb = padding[3]
}
}
const shape = new THREE.Shape()
shape.moveTo(0 + pl + offsetX, 0 + pb + offsetY)
shape.lineTo(width - pr + offsetX, 0 + pb + offsetY)
shape.lineTo(width - pr + offsetX, height - pt + offsetY)
shape.lineTo(0 + pl + offsetX, height - pt + offsetY)
shape.closePath()
return shape
}
/**
* 平面产量
*/
const BasePlane = {
LEFT: 0b000010,
RIGHT: 0b000001,
FRONT: 0b001000,
BEHIND: 0b000100,
TOP: 0b100000,
BOTTOM: 0b010000,
TRANSVERSE: 0b10000000,
LONGITUDINAL: 0b01000000,
THROW: 0b00100000,
toArray: () => {
return [BasePlane.LEFT, BasePlane.BEHIND, BasePlane.RIGHT, BasePlane.FRONT, BasePlane.BOTTOM, BasePlane.TOP, BasePlane.TRANSVERSE, BasePlane.LONGITUDINAL, BasePlane.THROW]
}
}
/**
* LineSegments2
*/
function test2() {
const count = 10000 //
const material = new THREE.MeshBasicMaterial({
color: '#038217',
transparent: true,
opacity: 1,
side: THREE.BackSide
})
// InstancedMesh
const instancedMesh = new THREE.InstancedMesh(groundMarkingGeometry, material, count)
// , 10 0.3,
for (let i = 0; i < count; i++) {
const x = (i % 10) * 3
const y = 0
const z = Math.floor(i / 10) * 3
console.log(`Setting instance [${i}]=(${x}, ${z})`)
const matrix = new THREE.Matrix4()
matrix.setPosition(x, y, z) // /
instancedMesh.setMatrixAt(i, matrix)
}
scene.add(instancedMesh)
refreshCount()
}
const groundMarkingGeometry = createGroundMarkingGeometry() //
function createGroundMarkingGeometry() {
const weight = 0.1
const width = 1.5
const depth = 2.0
const sectionCount = 1
const sectionDepth = (depth - (sectionCount + 1) * weight) / sectionCount
const shape = buildRectangle(width, depth)
for (let i = 0; i < sectionCount; i++) {
shape.holes.push(
buildRectangle(
width - 2 * weight,
sectionDepth,
0,
weight,
weight + i * (weight + sectionDepth)
)
)
}
return createExtrudeItem(shape, 0.1, BasePlane.BOTTOM, 0, 0, 0)
}
function isLabelInView(label, frustum) {
const pos = new THREE.Vector3()
label.getWorldPosition(pos)
//
if (frustum.containsPoint(pos)) {
//
if (shouldShowLabel(label)) {
return true
}
}
return false
}
const labels: Text[] = []
function getLabelPixelSize(fontSize, cameraZoom) {
const pixelRatio = renderer.getPixelRatio()
const referenceZoom = 1
const referenceFontSize = 0.2
const referencePixelSize = 16 // fontSize=0.2, zoom=1 16px
const scale = (fontSize / referenceFontSize) * (cameraZoom / referenceZoom)
return referencePixelSize * scale * pixelRatio
}
function shouldShowLabel(label, minPixelSize = 700) {
const pixelSize = getLabelPixelSize(label.fontSize, camera.zoom)
return pixelSize >= minPixelSize
}
/**
* InstanceMesh(Point) + BufferGeometry + Label
*/
function test3() {
cleanupThree() //
const xcount = 300
const zcount = 100
const dist = 1.25
const spacing = dist
const y = 0.1
const noShaderMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFF99,
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
})
const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25)
// 使 InstancedMesh
const instancedMesh = new THREE.InstancedMesh(planeGeometry, noShaderMaterial, zcount * xcount)
instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
const dummy = new THREE.Object3D()
const points = []
//
for (let z = 0; z < zcount; z++) {
for (let x = 0; x < xcount; x++) {
const px = x * dist
const pz = z * dist
dummy.position.set(px, y, pz)
dummy.rotation.set(-Math.PI / 2, 0, 0)
dummy.updateMatrix()
instancedMesh.setMatrixAt(z * xcount + x, dummy.matrix)
points.push(new THREE.Vector3(px, y, pz))
}
}
scene.add(instancedMesh)
const positions = []
labels.length = 0
function createTextLabel(text, position): Text {
const label = new Text()
label.text = text
label.font = SimSunTTF
label.fontSize = 0.2
label.color = '#333333'
label.opacity = 0.8
label.padding = 0.2
label.anchorX = 'center'
label.anchorY = 'middle'
label.depthOffset = 1
label.backgroundColor = '#000000'
label.backgroundOpacity = 0.6
label.material.depthTest = false
label.position.copy(position)
label.name = MeasureRenderer.LABEL_NAME
label.position.set(position.x, position.y + 0.3, position.z - 0.2)
label.quaternion.copy(camera.quaternion)
label.visible = false
// label.sync()
return label
}
//
for (let z = 0; z < zcount; z++) {
for (let x = 0; x < xcount - 1; x++) {
const x1 = x * spacing
const z1 = z * spacing
const x2 = (x + 1) * spacing
const z2 = z * spacing
positions.push(x1, y, z1, x2, y, z2)
//
const midPoint = new THREE.Vector3((x1 + x2) / 2, y + 0.5, (z1 + z2) / 2)
const length = Math.hypot(x2 - x1, y - y, z2 - z1)
const label = createTextLabel(length.toFixed(2) + 'm', midPoint)
labels.push(label)
}
}
//
for (let z = 0; z < zcount - 1; z++) {
for (let x = 0; x < xcount; x++) {
const x1 = x * spacing
const z1 = z * spacing
const x2 = x * spacing
const z2 = (z + 1) * spacing
positions.push(x1, y, z1, x2, y, z2)
//
const midPoint = new THREE.Vector3((x1 + x2) / 2, y + 0.5, (z1 + z2) / 2)
const length = Math.hypot(x2 - x1, y - y, z2 - z1)
const label = createTextLabel(length.toFixed(2) + 'm', midPoint)
labels.push(label)
}
}
const positionNums = new Float32Array(positions)
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positionNums, 3))
const material = new THREE.LineBasicMaterial({ color: 0xFF8C00 })
const lineSegments = new THREE.LineSegments(geometry, material)
lineSegments.name = 'grid-lines'
scene.add(lineSegments)
labels.forEach(label => scene.add(label))
//
refreshCount()
}
const restate = reactive({
targetColor: '#ff0000',
loadScale: 1,
viewLabelCount: 0,
mode: 'translate',
objects: 0,
vertices: 0,
faces: 0
})
//
const state = {
showAxesHelper: true,
showGridHelper: true,
camera: {
position: { x: 0, y: 5, z: 10 },
rotation: { x: 0, y: 0, z: 0 }
}
}
onMounted(() => {
nextTick(() => {
initThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(viewerDom)
window['cp'] = getCurrentInstance()
})
})
onBeforeUnmount(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
cleanupThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
window['cp'] = null
})
function initThree() {
viewerDom = canvasContainer.value
if (!viewerDom) {
console.error('Viewer DOM element not found')
return
}
//
scene = new THREE.Scene()
scene.background = new THREE.Color(0xeeeeee)
//
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
})
renderer.clearDepth()
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(viewerDom.clientWidth, viewerDom.clientHeight)
viewerDom.appendChild(renderer.domElement)
//
// initMode3DCamera()
initMode2DCamera()
// 线
axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
gridHelper = new THREE.GridHelper(1000, 1000)
scene.add(gridHelper)
gridHelper.visible = false
//
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
directionalLight.position.set(5, 5, 5).multiplyScalar(3)
directionalLight.castShadow = true
scene.add(directionalLight)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1)
scene.add(hemisphereLight)
//
statsControls = new Stats()
statsControls.showPanel(0)
statsControls.dom.style.right = '0'
statsControls.dom.style.left = 'auto'
viewerDom.appendChild(statsControls.dom)
renderer.setAnimationLoop(animate)
renderer.setClearColor(0x000000, 0)
// animate()
}
//
let frameCount = 0
function animate() {
// animationFrameId = requestAnimationFrame(animate)
renderView()
if (frameCount++ % 60 === 0) { // 60
const frustum = new THREE.Frustum()
const cameraCopy = camera.clone()
//
cameraCopy.updateMatrixWorld()
//
const projScreenMatrix = new THREE.Matrix4().multiplyMatrices(
cameraCopy.projectionMatrix,
cameraCopy.matrixWorldInverse
)
//
frustum.setFromProjectionMatrix(projScreenMatrix)
let viewLabelCount = 0
labels.forEach((label: Text) => {
// label.quaternion.copy(camera.quaternion) // billboard
const isvis = isLabelInView(label, frustum, camera.position)
if (isvis) {
viewLabelCount++
}
if (isvis && label.visible === false) {
label.visible = true
label.sync()
} else if (!isvis && label.visible === true) {
label.visible = false
label.sync()
}
})
restate.viewLabelCount = viewLabelCount
}
}
function handleResize(entries) {
for (let entry of entries) {
const width = entry.contentRect.width
const height = entry.contentRect.height
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height
camera.updateProjectionMatrix()
} else if (camera instanceof THREE.OrthographicCamera) {
camera.left = width / -2
camera.right = width / 2
camera.top = height / 2
camera.bottom = height / -2
camera.updateProjectionMatrix()
}
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
break
}
}
/**
* 初始化2D相机
*/
function initMode2DCamera() {
if (camera) {
scene.remove(camera)
}
// ============================
const cameraNew = new THREE.OrthographicCamera(
viewerDom.clientWidth / -2,
viewerDom.clientWidth / 2,
viewerDom.clientHeight / 2,
viewerDom.clientHeight / -2,
1,
500
)
cameraNew.position.set(0, 60, 0)
cameraNew.lookAt(0, 0, 0)
cameraNew.zoom = 60
camera = cameraNew
scene.add(camera)
// ============================
const controlsNew = new OrbitControls(
camera,
renderer.domElement
)
controlsNew.enableDamping = false
controlsNew.enableZoom = true
controlsNew.enableRotate = false
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.PAN } //
controlsNew.screenSpacePanning = false //
controlsNew.listenToKeyEvents(viewerDom) //
controlsNew.keys = { LEFT: 'KeyA', UP: 'KeyW', RIGHT: 'KeyD', BOTTOM: 'KeyS' }
controlsNew.panSpeed = 1
controlsNew.keyPanSpeed = 20 // normal 7
controlsNew.minDistance = 0.1
controlsNew.maxDistance = 1000
controls = controlsNew
cameraNew.updateProjectionMatrix()
}
function initMode3DCamera() {
if (camera) {
scene.remove(camera)
}
const viewerDom = canvasContainer.value
// ============================
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 0.1, 2000)
cameraNew.position.set(5, 5, 5)
cameraNew.lookAt(0, 0, 0)
camera = cameraNew
scene.add(camera)
const controlsNew = new OrbitControls(camera, viewerDom)
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE } //
controlsNew.enableDamping = false
controlsNew.screenSpacePanning = false //
controlsNew.minDistance = 2
controlsNew.addEventListener('change', syncCameraState)
controls = controlsNew
controls.update()
camera.updateProjectionMatrix()
syncCameraState()
}
/**
* 重新加载相机状态到全局状态
*/
function syncCameraState() {
if (camera) {
state.camera.position.x = camera.position.x
state.camera.position.y = camera.position.y
state.camera.position.z = camera.position.z
state.camera.rotation.x = camera.rotation.x
state.camera.rotation.y = camera.rotation.y
state.camera.rotation.z = camera.rotation.z
}
}
function renderView() {
statsControls?.update()
renderer?.render(scene, camera)
}
function refreshCount() {
//
let totalObjects = 0
let totalVertices = 0
let totalFaces = 0
scene.traverse(function(child) {
if (child.isMesh) {
totalObjects++
//
const geometry = child.geometry
// BufferGeometry
if (geometry.isBufferGeometry) {
//
if (geometry.attributes.position) {
totalVertices += geometry.attributes.position.count
}
//
if (geometry.index) {
totalFaces += geometry.index.count / 3
} else if (geometry.attributes.position) {
//
totalFaces += geometry.attributes.position.count / 3
}
}
// Geometry 使
else if (geometry.isGeometry) {
//
totalVertices += geometry.vertices.length
//
totalFaces += geometry.faces.length
}
}
})
restate.objects = totalObjects
restate.vertices = totalVertices
restate.faces = totalFaces
}
function cleanupThree() {
//
if (scene) {
scene.traverse((obj: THREE.Mesh) => {
//
if (obj.geometry) {
obj.geometry.dispose()
}
//
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose())
} else {
obj.material.dispose()
}
}
//
if (obj.texture) {
obj.texture.dispose()
}
})
//
scene.children = []
modelGroup = null
}
}
</script>
<style scoped lang="less">
.model3d-view {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
.main-content {
display: flex;
flex: 1;
overflow: hidden;
.model3d-content {
height: 100%;
display: flex;
flex-direction: row;
}
}
.canvas-container {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

2
src/core/Constract.ts

@ -32,7 +32,5 @@ const Constract = Object.freeze({
HEIGHT_WAY: 0.01, HEIGHT_WAY: 0.01,
HEIGHT_WAY_LABEL: 0.03, HEIGHT_WAY_LABEL: 0.03,
HEIGHT_WAY_LINE: 0.02, HEIGHT_WAY_LINE: 0.02,
MAX_MEASURE_INSTANCES: 1000,
}) })
export default Constract export default Constract

130
src/core/IfxUtils.ts

@ -0,0 +1,130 @@
import * as THREE from 'three'
import { BasePlane } from '@/types/ModelTypes.ts'
/**
* Shape
*/
export function buildRectangle(width: number, height: number, padding: number[] | number = 0, offsetX = 0, offsetY = 0): THREE.Shape {
let pl = 0,
pt = 0,
pr = 0,
pb = 0
if (typeof padding == 'number') {
pl = padding
pt = padding
pr = padding
pb = padding
} else if (Array.isArray(padding)) {
if (padding.length == 1) {
pl = padding[0]
pt = padding[0]
pr = padding[0]
pb = padding[0]
} else if (padding.length > 1 && padding.length < 4) {
pl = padding[0]
pt = padding[1]
pr = padding[0]
pb = padding[1]
} else if (padding.length >= 4) {
pl = padding[0]
pt = padding[1]
pr = padding[2]
pb = padding[3]
}
}
const shape = new THREE.Shape()
shape.moveTo(0 + pl + offsetX, 0 + pb + offsetY)
shape.lineTo(width - pr + offsetX, 0 + pb + offsetY)
shape.lineTo(width - pr + offsetX, height - pt + offsetY)
shape.lineTo(0 + pl + offsetX, height - pt + offsetY)
shape.closePath()
return shape
}
/**
* Shape front
*
* @param {*} shape
* @param {*} depth
* @param {*} basePlane
* @param {*} x x
* @param {*} y y
* @param {*} z z
* @param {*} euler
* @param {*} anchor
* @returns THREE.ExtrudeGeometry
*/
export function createExtrudeItem(shape: THREE.Shape, depth: number, basePlane: number,
x = 0, y = 0, z = 0, euler = null,
anchor = new THREE.Vector3(0, 0, 0)) {
const geometry = new THREE.ExtrudeGeometry(shape, {
steps: 1,
depth: -depth,
bevelEnabled: false,
bevelThickness: 0,
bevelSize: 0,
bevelOffset: 0,
bevelSegments: 0
})
geometry.center()
const size = geometry.boundingBox.getSize(new THREE.Vector3())
let dx, dy, dz
switch (basePlane) {
case BasePlane.LEFT:
geometry.rotateY(Math.PI / 2)
dx = size.z / 2
dy = size.y / 2
dz = size.x / 2
break
case BasePlane.RIGHT:
geometry.rotateY(-Math.PI / 2)
dx = size.z / 2
dy = size.y / 2
dz = size.x / 2
break
case BasePlane.BEHIND:
geometry.translate(0, 0, size.z)
dx = size.x / 2
dy = size.y / 2
dz = size.z / 2
break
case BasePlane.TOP:
geometry.rotateZ(Math.PI)
geometry.rotateX(Math.PI / 2)
geometry.translate(size.x, -size.z, 0)
dx = size.x / 2
dy = size.z / 2
dz = size.y / 2
break
case BasePlane.BOTTOM:
geometry.rotateX(-Math.PI / 2)
dx = size.x / 2
dy = size.z / 2
dz = size.y / 2
break
default:
//BasePlane.FRONT:
dx = size.x / 2
dy = size.y / 2
dz = size.z / 2
break
}
if (euler != null && euler.isEuler) {
// 注意,需要先旋转,再平移。
geometry.rotateX(euler.x)
geometry.rotateY(euler.y)
geometry.rotateZ(euler.z)
}
geometry.translate(dx + x + anchor.x, dy + y + anchor.y, -dz + z + anchor.z)
geometry.center()
return geometry
}

159
src/core/ModelUtils.ts

@ -1,11 +1,73 @@
import * as THREE from 'three' import * as THREE from 'three'
import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts'
import { getAllItemTypes, getItemTypeByName } from '@/model/itemType/ItemTypeDefine.ts'
import type Viewport from '@/core/engine/Viewport' import type Viewport from '@/core/engine/Viewport'
import { Vector2 } from 'three/src/math/Vector2' import { Vector2 } from 'three/src/math/Vector2'
import EventBus from '@/runtime/EventBus.ts' import EventBus from '@/runtime/EventBus.ts'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import type { Object3DLike } from '@/types/ModelTypes.ts' 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'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
/**
* 线id
*/
export function getCargoLineId(linkName: string, startId: string, endId: string): string {
// 无序线条的id格式为 cargo$endId$startId
return `${linkName}$${[startId, endId].sort().join('$')}`
}
/**
*
* console.log('Guideway: type=' + type + ', start=' + start.id, ', end=' + end.id)
*
*
* Guideway: type=in, start=way11 , end=way12
* Guideway: type=out, start=way11 , end=way12
* Guideway: type=in, start=way12 , end=way11
* Guideway: type=out, start=way12 , end=way11
* Guideway: type=out, start=way21 , end=way22
* Guideway: type=in, start=way22 , end=way21
* Guideway: type=in, start=way31 , end=way32
* Guideway: type=out, start=way32 , end=way31
*
*
* way11 <-> way12
* way21 -> way22
* way31 <- way32
*/
export function getLinkDirection(viewport: Viewport, linkName: string, start: ItemJson, end: ItemJson): { lineId: string, direction: LinkDirection } {
const lineId = getCargoLineId(linkName, start.id, end.id)
if (viewport.entityManager.findLineObjectById(lineId)) {
// 已经处理过这一对
return { lineId, direction: '' }
}
const startOut = start.dt?.out || []
const endIn = end.dt?.in || []
const endOut = end.dt?.out || []
const startIn = start.dt?.in || []
// 判断方向
const forward = startOut.includes(end.id) && endIn.includes(start.id)
const backward = endOut.includes(start.id) && startIn.includes(end.id)
if (forward && backward) {
return { lineId, direction: '<->' }
} else if (forward) {
return { lineId, direction: '->' }
} else if (backward) {
return { lineId, direction: '<-' }
}
// 他们之间没有连接关系
return { lineId, direction: '' }
}
export function setUserDataForItem(item: ItemJson, object: Object3DLike) { export function setUserDataForItem(item: ItemJson, object: Object3DLike) {
if (!object.name && item.name) { if (!object.name && item.name) {
@ -142,7 +204,7 @@ export function getClosestObject(viewport: Viewport, object: THREE.Object3D, ins
if (object.userData && object.userData.t && object.userData.entityId) { if (object.userData && object.userData.t && object.userData.entityId) {
// 找到第一个有效的业务 Object3D // 找到第一个有效的业务 Object3D
if (object instanceof THREE.InstancedMesh && instanceId >= 0) { if (object instanceof THREE.InstancedMesh && instanceId >= 0) {
return viewport.pointManagerMap.get(object.userData.t)?.findByMeshInstanceId(instanceId) return viewport.pointManagerMap.get(object.userData.t)?.findByMeshInstanceId(object.userData.blockIndex, instanceId)
} }
return object return object
} }
@ -361,19 +423,6 @@ export function calcPositionUseSnap(e: MouseEvent, point: THREE.Vector3) {
return point return point
} }
export function getAllControlPoints(): THREE.Object3D[] {
const allPoints: THREE.Object3D[] = []
getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => {
if (itemType.clazz && itemType.clazz.pointArray) {
// 将每个 ItemType 的点添加到结果数组中
allPoints.push(...itemType.clazz.pointArray)
}
})
return allPoints
}
/** /**
* uuid Object3D * uuid Object3D
*/ */
@ -483,3 +532,81 @@ export function decimalSumBy<T>(collection: ArrayLike<T> | null | undefined, ite
}) })
return sum.toNumber() return sum.toNumber()
} }
export async function loadByUrl(url): Promise<any> {
return await axios.get(url, {
responseType: 'arraybuffer'
})
}
/**
* , z轴为平面长度, x轴为平面宽度 linkPlaneByPoint
* @param startPosition
* @param endPosition
* @param direction
* @param width
*
*/
export function createLinkPlaneMatrix4(startPosition: THREE.Vector3, endPosition: THREE.Vector3, width: number, direction: LinkDirection): THREE.Matrix4 {
const dir = new THREE.Vector3().subVectors(endPosition, startPosition)
const length = dir.length() // 平面长度 = 两点距离
dir.normalize()
const center = new THREE.Vector3().addVectors(startPosition, endPosition).multiplyScalar(0.5)
// const orientation = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), dir)
const dummy = new THREE.Object3D()
dummy.position.copy(center)
dummy.lookAt(endPosition)
// dummy.quaternion.setFromRotationMatrix(new THREE.Matrix4().lookAt(startPosition, endPosition, new THREE.Vector3(0, 1, 0)))
dummy.scale.set(width, 1, length) // Z轴缩放为长度
dummy.updateMatrix()
return dummy.matrix
}
export async function loadGlbModule(url: string): Promise<THREE.Group> {
const response = await axios.get(url, {
responseType: 'arraybuffer'
})
const rt = await new GLTFLoader().parseAsync(response.data, '')
return rt.scene
}
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
}
}
export function getMatrixFromTf(tf: number[][]): THREE.Matrix4 {
const dummy = new THREE.Object3D()
dummy.position.set(tf[0][0], tf[0][1], tf[0][2])
dummy.rotation.set(
THREE.MathUtils.degToRad(tf[1][0]),
THREE.MathUtils.degToRad(tf[1][1]),
THREE.MathUtils.degToRad(tf[1][2])
)
dummy.scale.set(tf[2][0], tf[2][1], tf[2][2])
dummy.updateMatrix()
return dummy.matrix
}

3
src/core/base/BaseInteraction.ts

@ -7,6 +7,7 @@ import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { numberToString } from '@/utils/webutils.ts' import { numberToString } from '@/utils/webutils.ts'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import type { Object3DLike } from '@/types/ModelTypes.ts'
let pdFn, pmFn, puFn let pdFn, pmFn, puFn
@ -40,7 +41,7 @@ export default abstract class BaseInteraction {
// 连线起点 // 连线起点
linkStartPointId: string linkStartPointId: string
linkStartPointObject: THREE.Object3D linkStartPointObject: Object3DLike
templineMaterial = new LineMaterial({ templineMaterial = new LineMaterial({
color: 0xE63C17, // 主颜色 color: 0xE63C17, // 主颜色

111
src/core/base/BaseRenderer.ts

@ -6,6 +6,7 @@ import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePo
import Constract from '@/core/Constract.ts' import Constract from '@/core/Constract.ts'
import LineSegmentManager, { LineManageWrap } from '@/core/manager/LineSegmentManager.ts' import LineSegmentManager, { LineManageWrap } from '@/core/manager/LineSegmentManager.ts'
import type { Object3DLike } from '@/types/ModelTypes.ts' import type { Object3DLike } from '@/types/ModelTypes.ts'
import { InstanceMeshWrap } from '@/core/manager/InstanceMeshManager.ts'
/** /**
* *
@ -31,48 +32,6 @@ export default abstract class BaseRenderer {
return Promise.resolve() return Promise.resolve()
} }
get pointManager(): InstancePointManager {
if (!this.tempViewport) {
throw new Error('tempViewport is not set. Please call beginRendererUpdate first.')
}
let pointManager = this.tempViewport.pointManagerMap.get(this.itemTypeName)
if (!pointManager) {
pointManager = this.createPointManager()
if (pointManager) {
this.tempViewport.pointManagerMap.set(this.itemTypeName, pointManager)
}
}
return pointManager
}
get lineSegmentManager(): LineSegmentManager {
if (!this.tempViewport) {
throw new Error('tempViewport is not set. Please call beginRendererUpdate first.')
}
let lineSegmentManager = this.tempViewport.lineSegmentManagerMap.get(this.itemTypeName)
if (!lineSegmentManager) {
lineSegmentManager = this.createLineSegmentManager()
if (lineSegmentManager) {
this.tempViewport.lineSegmentManagerMap.set(this.itemTypeName, lineSegmentManager)
}
}
return lineSegmentManager
}
/**
*
*/
createPointManager(): InstancePointManager {
return null
}
/**
* 线
*/
createLineSegmentManager(): LineSegmentManager {
return null
}
/** /**
* *
* @param viewport * @param viewport
@ -125,7 +84,7 @@ export default abstract class BaseRenderer {
/** /**
* 线 * 线
*/ */
afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) { afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption) {
} }
/** /**
@ -153,7 +112,6 @@ export default abstract class BaseRenderer {
createPointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike { createPointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike {
const point = this.createPoint(item, option) const point = this.createPoint(item, option)
point.visible = ((typeof item.v !== 'undefined') ? item.v : true) point.visible = ((typeof item.v !== 'undefined') ? item.v : true)
setUserDataForItem(item, point) setUserDataForItem(item, point)
@ -195,6 +153,18 @@ export default abstract class BaseRenderer {
const object = this.tempViewport.entityManager.findObjectById(id) const object = this.tempViewport.entityManager.findObjectById(id)
if (object instanceof THREE.Object3D) { if (object instanceof THREE.Object3D) {
this.removeFromScene(object) this.removeFromScene(object)
} else if (object instanceof PointManageWrap) {
// 如果是 PointManageWrap, 则需要调用管理器的删除方法
object.manager.deletePoint(id)
} else if (object instanceof InstanceMeshWrap) {
// 如果是 PointManageWrap, 则需要调用管理器的删除方法
object.manager.delete(id)
} else if (object instanceof LineManageWrap) {
// 如果是 PointManageWrap, 则需要调用管理器的删除方法
object.manager.deleteLine(id)
} }
this.tempViewport.entityManager.deleteEntityOnly(id) this.tempViewport.entityManager.deleteEntityOnly(id)
@ -202,12 +172,21 @@ export default abstract class BaseRenderer {
} }
updatePointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike { updatePointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike {
const point = this.updatePoint(item, option) const originObject = this.tempViewport.entityManager.findObjectById(item.id)
point.visible = ((typeof item.v !== 'undefined') ? item.v : true) const newObject = this.updatePoint(item, originObject, option)
newObject.visible = ((typeof item.v !== 'undefined') ? item.v : true)
setUserDataForItem(item, point) if (originObject !== newObject) {
this.afterCreateOrUpdatePoint(item, option, point) // 如果更新后的对象和原来的对象不同, 则替换掉
return point setUserDataForItem(item, newObject)
this.removeFromScene(originObject as THREE.Object3D)
this.appendToScene(newObject as THREE.Object3D)
this.tempViewport.entityManager.replaceObject(item.id, newObject)
}
this.afterCreateOrUpdatePoint(item, option, newObject)
return newObject
} }
/** /**
@ -215,13 +194,7 @@ export default abstract class BaseRenderer {
* @param item * @param item
* @param option * @param option
*/ */
updatePoint(item: ItemJson, option?: RendererCudOption): Object3DLike { updatePoint(item: ItemJson, object: Object3DLike, option?: RendererCudOption): Object3DLike {
const object = this.tempViewport.entityManager.findObjectById(item.id)
if (!object) {
console.warn(`Point with ID "${item.id}" does not exist.`)
return
}
const point = object const point = object
point.name = item.name || point.name point.name = item.name || point.name
@ -263,11 +236,25 @@ export default abstract class BaseRenderer {
this.tempViewport.entityManager.appendLineObject(id, line) this.tempViewport.entityManager.appendLineObject(id, line)
if (line instanceof THREE.Object3D) { if (line instanceof THREE.Object3D) {
this.appendToScene(line) this.appendToScene(line)
} else if (line instanceof PointManageWrap) {
line.manager.syncMeshObject3D(line)
} }
this.afterCreateOrUpdateLine(start, end, type, option, line) this.afterCreateOrUpdateLine(start, end, type, option, line)
} }
updateLineForEntity(start: ItemJson, end: ItemJson, type: LinkType, option: any = {}) {
const lineId = getLineId(start.id, end.id, type)
const line = this.tempViewport.entityManager.findLineObjectById(lineId)
option.lineId = lineId
option.line = line
this.updateLine(start, end, type, option)
this.afterCreateOrUpdateLine(start, end, type, option, line)
}
/** /**
* 线 * 线
* @param start * @param start
@ -275,10 +262,8 @@ export default abstract class BaseRenderer {
* @param type 线 * @param type 线
* @param option * @param option
*/ */
updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { updateLine(start: ItemJson, end: ItemJson, type: LinkType, option: any) {
const lineId = getLineId(start.id, end.id, type) const { line, lineId } = option
const line = this.tempViewport.entityManager.findLineObjectById(lineId)
const startPoint = this.tempViewport.entityManager.findObjectById(start.id) const startPoint = this.tempViewport.entityManager.findObjectById(start.id)
const endPoint = this.tempViewport.entityManager.findObjectById(end.id) const endPoint = this.tempViewport.entityManager.findObjectById(end.id)
@ -289,8 +274,11 @@ export default abstract class BaseRenderer {
} else if (line instanceof LineManageWrap) { } else if (line instanceof LineManageWrap) {
line.manager.updateLine(lineId, startPoint.position, endPoint.position, line.color) line.manager.updateLine(lineId, startPoint.position, endPoint.position, line.color)
} }
}
this.afterCreateOrUpdateLine(start, end, type, option, line) deleteLineForEntity(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) {
this.deleteLine(start, end, type, option)
this.afterDeleteLine(start, end, type, option)
} }
/** /**
@ -304,7 +292,6 @@ export default abstract class BaseRenderer {
const line = this.tempViewport.entityManager.findLineObjectById(lineId) const line = this.tempViewport.entityManager.findLineObjectById(lineId)
this.tempViewport.entityManager.deleteLineObjectOnly(lineId) this.tempViewport.entityManager.deleteLineObjectOnly(lineId)
this.afterDeleteLine(start, end, type, option, line)
} }
dispose() { dispose() {

33
src/core/base/IMeta.ts

@ -1,33 +0,0 @@
import type { ItemTypeMeta } from '@/model/itemType/ItemTypeDefine.ts'
/**
* "点"
*/
export const BASIC_META_OF_POINT: ItemTypeMeta = [
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true },
{ field: 'name', editor: 'TextInput', label: '名称' },
{ field: 'userData.label', editor: 'TextInput', label: '标签' },
{ editor: 'Transform' },
{ field: 'color', editor: 'Color', label: '颜色' },
{ editor: '-' },
{ editor: 'IN_OUT_CENTER' }
]
/**
* "物流运输单元",
*/
export const BASIC_META_OF_POINT2: ItemTypeMeta = [
{ field: 'userData.selectable', editor: 'Switch', label: '可选中' },
{ field: 'userData.protected', editor: 'Switch', label: '受保护' },
{ field: 'visible', editor: 'Switch', label: '可见' }
]
/**
* "线"
*/
export const BASIC_META_OF_LINE: ItemTypeMeta = []
/**
* "线",
*/
export const BASIC_META_OF_LINE2: ItemTypeMeta = []

14
src/core/engine/SceneHelp.ts

@ -64,9 +64,21 @@ export default class SceneHelp {
this.scene.add(gridHelper2) this.scene.add(gridHelper2)
// 光照 // 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 0.8) // const ambientLight = new THREE.AmbientLight(0xffffff, 0.8)
// this.scene.add(ambientLight)
// 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5)
this.scene.add(ambientLight) this.scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
directionalLight.position.set(5, 5, 5).multiplyScalar(3)
directionalLight.castShadow = true
this.scene.add(directionalLight)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1)
this.scene.add(hemisphereLight)
// const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5) // const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
// directionalLight.position.set(5, 5, 5).multiplyScalar(3) // directionalLight.position.set(5, 5, 5).multiplyScalar(3)
// directionalLight.castShadow = true // directionalLight.castShadow = true

60
src/core/engine/Viewport.ts

@ -8,8 +8,6 @@ import { markRaw, reactive, toRaw, watch } from 'vue'
import type IControls from '../controls/IControls' import type IControls from '../controls/IControls'
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer' import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts'
import { getAllItemTypes } from '@/model/itemType/ItemTypeDefine.ts'
import SceneHelp from './SceneHelp' import SceneHelp from './SceneHelp'
import SelectInspect from '../controls/SelectInspect' import SelectInspect from '../controls/SelectInspect'
@ -26,6 +24,7 @@ import LabelManager from '@/core/manager/LabelManager.ts'
import type InstancePointManager from '@/core/manager/InstancePointManager.ts' import type InstancePointManager from '@/core/manager/InstancePointManager.ts'
import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts'
import type { Object3DLike } from '@/types/ModelTypes.ts' import type { Object3DLike } from '@/types/ModelTypes.ts'
import type InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts'
/** /**
* *
@ -65,6 +64,9 @@ export default class Viewport {
// 点实例管理器 moduleName -> InstancePointManager // 点实例管理器 moduleName -> InstancePointManager
pointManagerMap: Map<string, InstancePointManager> = new Map() pointManagerMap: Map<string, InstancePointManager> = new Map()
// 对象实例管理器 moduleName -> InstanceMeshManager
meshManager: Map<string, InstanceMeshManager> = new Map()
// 线段实例管理器 moduleName -> InstancePointManager // 线段实例管理器 moduleName -> InstancePointManager
lineSegmentManagerMap: Map<string, LineSegmentManager> = new Map() lineSegmentManagerMap: Map<string, LineSegmentManager> = new Map()
@ -109,6 +111,54 @@ export default class Viewport {
} }
/** /**
*
* @param typeName
* @param createFn
*/
getOrCreateMeshManager(typeName: string, createFn: () => InstanceMeshManager): InstanceMeshManager {
let meshManager = this.meshManager.get(typeName)
if (!meshManager) {
meshManager = createFn()
if (meshManager) {
this.meshManager.set(typeName, meshManager)
}
}
return meshManager
}
/**
*
* @param typeName
* @param createFn
*/
getOrCreatePointManager(typeName: string, createFn: () => InstancePointManager): InstancePointManager {
let pointManager = this.pointManagerMap.get(typeName)
if (!pointManager) {
pointManager = createFn()
if (pointManager) {
this.pointManagerMap.set(typeName, pointManager)
}
}
return pointManager
}
/**
* 线
* @param typeName 线
* @param createFn 线
*/
getOrCreateLineManager(typeName: string, createFn: () => LineSegmentManager): LineSegmentManager {
let lineSegmentManager = this.lineSegmentManagerMap.get(typeName)
if (!lineSegmentManager) {
lineSegmentManager = createFn()
if (lineSegmentManager) {
this.lineSegmentManagerMap.set(typeName, lineSegmentManager)
}
}
return lineSegmentManager
}
/**
* THREE * THREE
*/ */
async initThree(option: InitThreeOption) { async initThree(option: InitThreeOption) {
@ -179,6 +229,7 @@ export default class Viewport {
statsControls.dom.style.position = 'absolute' statsControls.dom.style.position = 'absolute'
statsControls.dom.style.top = '0' statsControls.dom.style.top = '0'
statsControls.dom.style.left = '0' statsControls.dom.style.left = '0'
statsControls.dom.style.zIndex = '1'
viewerDom.parentElement.parentElement.appendChild(statsControls.dom) viewerDom.parentElement.parentElement.appendChild(statsControls.dom)
$(statsControls.dom).children().css('height', '28px') $(statsControls.dom).children().css('height', '28px')
@ -216,11 +267,6 @@ export default class Viewport {
} }
}, { immediate: true })) }, { immediate: true }))
// 触发所有物品类型的 afterAddViewport 方法
_.forEach(getAllItemTypes(), (itemType: ItemTypeDefineOption) => {
itemType.clazz.afterAddViewport(this)
})
this.animate() this.animate()
try { try {

40
src/core/manager/EntityManager.ts

@ -96,11 +96,13 @@ export default class EntityManager {
/** /**
* , center[] / in[] / out[] , * , center[] / in[] / out[] ,
*/ */
createOrUpdateEntity(entityRaw: ItemJson, option: EntityCudOption = {}): void { createOrUpdateEntity(entity: ItemJson, option: EntityCudOption = {}): void {
if (!entityRaw?.id) { // if (!entityRaw?.id) {
if (!entity?.id) {
throw new Error('Entity must have an id') throw new Error('Entity must have an id')
} }
const entity = _.cloneDeep(entityRaw) as ItemJson // 改成非深拷贝, 直接使用原始数据, 因为 renderer 可能会修改这个数据, 后续也要能生效
// const entity = _.cloneDeep(entityRaw) as ItemJson
const originEntity = this.___entityMap.get(entity.id) const originEntity = this.___entityMap.get(entity.id)
// 找到这个数据的渲染器 // 找到这个数据的渲染器
@ -119,7 +121,7 @@ export default class EntityManager {
const relation = this.__relationIndex.get(entity.id) const relation = this.__relationIndex.get(entity.id)
if (relation) { if (relation) {
for (const type of (['center', 'in', 'out'] as LinkType[])) { for (const type of (['center', 'input', 'output'] as LinkType[])) {
const relatedIds = relation[type] const relatedIds = relation[type]
if (!relatedIds) continue if (!relatedIds) continue
@ -224,7 +226,7 @@ export default class EntityManager {
// 只通知起点对应的渲染器 // 只通知起点对应的渲染器
continue continue
} }
renderer.updateLine(start, end, lineDiffItem.type) renderer.updateLineForEntity(start, end, lineDiffItem.type)
} }
// "线"删除 // "线"删除
@ -418,6 +420,34 @@ export default class EntityManager {
return this.__objectMap.get(id) return this.__objectMap.get(id)
} }
replaceObject(id: string, newObject: Object3DLike) {
if (newObject.userData.entityId !== id) {
throw new Error(`New object must have userData.entityId set to ${id}`)
}
const originalObject = this.__objectMap.get(id)
_.remove(this._selectableObjects, obj => obj.userData?.entityId === id)
_.remove(this._draggableObjects, obj => obj.userData?.entityId === id)
this.__objectMap.set(id, newObject)
// 如果是可选中对象,添加到 _selectableObjects 中
if (newObject instanceof THREE.Object3D && newObject.userData.selectable !== false) {
this._selectableObjects.push(newObject)
}
if (newObject instanceof THREE.Object3D && newObject.userData.draggable) {
this._draggableObjects.push(newObject)
}
if (this.viewport.state.selectedEntityId === id) {
// 如果当前选中的对象被替换了,更新选中状态
this.viewport.selectInspect.selectById(id)
}
if (_.includes(this.viewport.state.multiSelectedEntityIds, id)) {
// 如果当前多选的对象被替换了,更新多选状态
this.viewport.selectInspect.multiSelectByIds(this.viewport.state.multiSelectedEntityIds)
}
}
appendObject(id: string, object: Object3DLike) { appendObject(id: string, object: Object3DLike) {
this.__objectMap.set(id, object) this.__objectMap.set(id, object)
// 如果是可选中对象,添加到 _selectableObjects 中 // 如果是可选中对象,添加到 _selectableObjects 中

74
src/core/manager/InstanceMeshBlock.ts

@ -0,0 +1,74 @@
import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport.ts'
export default class InstanceMeshBlock {
public readonly name: string
public readonly blockIndex: number
public readonly viewport: Viewport
public readonly instancedMesh: THREE.InstancedMesh
public readonly freeIndices: number[] = []
// instanceId -> itemId
public readonly __indexIdMap = new Map<number, string>()
getFreeMeshIndex(): number {
if (this.freeIndices.length === 0) {
return -1 // No free index available
}
return this.freeIndices.pop() // Return the last free index
}
constructor(itemTypeName: string, allowSelect: boolean, allowDrag: boolean, viewport: Viewport,
geometry: THREE.BufferGeometry, material: THREE.Material,
blockIndex: number, capacity: number) {
this.name = itemTypeName
this.blockIndex = blockIndex
this.viewport = viewport
this.instancedMesh = new THREE.InstancedMesh(geometry, material, capacity)
this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
console.log('createBlock: ' + itemTypeName + '[' + blockIndex + '] capacity:', capacity)
viewport.scene.add(this.instancedMesh)
if (allowSelect) {
this.viewport.entityManager._selectableObjects.push(this.instancedMesh)
}
if (allowDrag) {
this.viewport.entityManager._draggableObjects.push(this.instancedMesh)
}
this.instancedMesh.userData.t = itemTypeName
this.instancedMesh.userData.entityId = 'InstanceMeshBlock_' + itemTypeName + '_' + blockIndex
_.extend(this.instancedMesh.userData,{
t: itemTypeName,
blockIndex: blockIndex
})
const dummy = new THREE.Object3D()
dummy.scale.set(0, 0, 0)
dummy.updateMatrix()
for (let i = 0; i < capacity; i++) {
this.instancedMesh.setMatrixAt(i, dummy.matrix)
this.freeIndices.push(i)
}
this.instancedMesh.instanceMatrix.needsUpdate = true
}
dispose() {
this.viewport.scene.remove(this.instancedMesh)
this.instancedMesh.geometry.dispose()
if (this.instancedMesh.material) {
if (Array.isArray(this.instancedMesh.material)) {
this.instancedMesh.material.forEach(mat => mat.dispose())
} else {
this.instancedMesh.material.dispose()
}
}
this.instancedMesh.dispose()
this.__indexIdMap.clear()
this.freeIndices.length = 0
}
}

181
src/core/manager/InstanceMeshManager.ts

@ -0,0 +1,181 @@
import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport.ts'
import InstanceMeshBlock from '@/core/manager/InstanceMeshBlock.ts'
import { PointManageWrap } from '@/core/manager/InstancePointManager.ts'
import { Matrix4 } from 'three/src/math/Matrix4'
export default class InstanceMeshManager {
private __uuidMap = new Map<string, InstanceMeshWrap>()
public readonly name: string
public readonly viewport: Viewport
public readonly allowSelect: boolean
public readonly allowDrag: boolean
public readonly blockCapacity: number
public readonly blocks: InstanceMeshBlock[] = []
private readonly geometry: THREE.BufferGeometry
private readonly material: THREE.Material
private readonly dummy: THREE.Object3D = new THREE.Object3D()
constructor(name: string, viewport: Viewport,
geometry: THREE.BufferGeometry, material: THREE.Material,
allowSelect: boolean, allowDrag: boolean, blockCapacity: number = 1000) {
this.name = name
this.viewport = viewport
this.allowSelect = allowSelect
this.allowDrag = allowDrag
this.geometry = geometry
this.material = material
this.blockCapacity = blockCapacity
}
/**
*
*/
findByMeshInstanceId(blockIndex: number, instanceId: number): InstanceMeshWrap {
if (!this.blocks[blockIndex]) {
console.error('InstancePointManager: Invalid blockIndex', blockIndex)
return null
}
const uuid = this.blocks[blockIndex].__indexIdMap.get(instanceId)
if (!uuid) return
return this.__uuidMap.get(uuid)
}
create(entityId: string, userData?: any): InstanceMeshWrap {
let meshIndex = -1
let blockIndex = -1
for (const block of this.blocks) {
meshIndex = block.getFreeMeshIndex()
if (meshIndex >= 0) {
blockIndex = block.blockIndex
break
}
}
// 所有 block 都没有空闲索引,创建新的 block
if (meshIndex < 0) {
const block = this.createBlock()
meshIndex = block.getFreeMeshIndex()
blockIndex = block.blockIndex
}
if (meshIndex < 0) {
system.showErrorDialog('InstancePointManager: No free index available after creating new block')
return null
}
return new InstanceMeshWrap(entityId, this, blockIndex, meshIndex, userData)
}
delete(wrapOrId: InstanceMeshWrap | string) {
let wrap: InstanceMeshWrap
if (typeof wrapOrId === 'string') {
wrap = this.__uuidMap.get(wrapOrId)
if (!wrap) return
} else {
wrap = wrapOrId
if (!wrap || !this.__uuidMap.has(wrap.uuid)) {
console.warn(`InstanceMeshManager: Wrap ${wrap.uuid} not found`)
return
}
}
const block = this.blocks[wrap.blockIndex]
if (!block) {
console.warn(`InstanceMeshManager: Block ${wrap.blockIndex} not found for wrap ${wrap.uuid}`)
return
}
// 隐藏实例
this.dummy.scale.set(0, 0, 0)
this.dummy.updateMatrix()
block.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix)
block.instancedMesh.instanceMatrix.needsUpdate = true
// 回收索引
block.freeIndices.push(wrap.meshIndex)
this.__uuidMap.delete(wrap.uuid)
block.__indexIdMap.delete(wrap.meshIndex)
wrap.dispose()
}
// 创建新的 InstanceMeshBlock
createBlock(): InstanceMeshBlock {
const blockIndex = this.blocks.length
const block = new InstanceMeshBlock(this.name, this.allowSelect, this.allowDrag,
this.viewport, this.geometry, this.material,
blockIndex, this.blockCapacity)
this.blocks.push(block)
return block
}
setBlockMatrixAt(wrap: InstanceMeshWrap, matrix: THREE.Matrix4) {
const block = this.blocks[wrap.blockIndex]
if (!block) {
console.warn(`InstanceMeshManager: Block ${wrap.blockIndex} not found!`)
return
}
if (!block.__indexIdMap.has(wrap.meshIndex)) {
this.__uuidMap.set(wrap.uuid, wrap)
wrap.parent = block.instancedMesh
}
block.instancedMesh.setMatrixAt(wrap.meshIndex, matrix)
block.instancedMesh.instanceMatrix.needsUpdate = true
}
dispose() {
for (const block of this.blocks) {
block.dispose()
}
this.blocks.length = 0 // 清空 blocks 数组
this.geometry.dispose() // 释放几何体资源
this.material.dispose() // 释放材质资源
console.log(`InstanceMeshManager ${this.name} disposed.`)
}
}
export class InstanceMeshWrap {
readonly entityId: string
readonly manager: InstanceMeshManager
readonly blockIndex: number
readonly meshIndex: number
//@ts-ignore
userData: UserData = {}
uuid: string
parent: THREE.Object3D | null = null
name: string
visible: boolean
get position(): THREE.Vector3 {
const m = new THREE.Matrix4()
this.manager.blocks[this.blockIndex].instancedMesh.getMatrixAt(this.meshIndex, m)
const pos = new THREE.Vector3()
m.decompose(pos, new THREE.Quaternion(), new THREE.Vector3())
return pos
}
constructor(entityId: string, manager: InstanceMeshManager, blockIndex: number, meshIndex: number, userData?: any) {
this.uuid = system.createUUID()
this.entityId = entityId
this.manager = manager
this.meshIndex = meshIndex
this.blockIndex = blockIndex
this.userData = userData
}
setMatrix4(matrix: THREE.Matrix4): InstanceMeshWrap {
this.manager.setBlockMatrixAt(this, matrix)
return this
}
dispose() {
this.manager.delete(this)
this.parent = null
}
}

198
src/core/manager/InstancePointManager.ts

@ -1,23 +1,38 @@
import * as THREE from 'three' import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport.ts' import type Viewport from '@/core/engine/Viewport.ts'
import { Vector3 } from 'three/src/math/Vector3' import { Vector3 } from 'three/src/math/Vector3'
import InstanceMeshBlock from '@/core/manager/InstanceMeshBlock.ts'
/** /**
* *
* 使 InstanceMesh
*/ */
export default class InstancePointManager { export default class InstancePointManager {
public readonly name: string
public readonly viewport: Viewport public readonly viewport: Viewport
public readonly instancedMesh: THREE.InstancedMesh public readonly allowSelect: boolean
public readonly allowDrag: boolean
public readonly blockCapacity: number = 1000 // 每个 block 的容量
private readonly freeIndices: number[] = [] public readonly blocks: InstanceMeshBlock[] = []
private readonly maxInstanceCount: number
private readonly geometry: THREE.BufferGeometry private readonly geometry: THREE.BufferGeometry
private readonly material: THREE.Material private readonly material: THREE.Material
private readonly dummy: THREE.Object3D = new THREE.Object3D() private readonly dummy: THREE.Object3D = new THREE.Object3D()
// itemId -> instanceId // itemId -> data
private __uuidMap = new Map<string, PointManageWrap>() private __uuidMap = new Map<string, PointManageWrap>()
// instanceId -> itemId
private __indexIdMap = new Map<number, string>() createPointSimple(id: string): PointManageWrap {
//@ts-ignore
const wrap = this.createPoint({
id: id,
name: id,
v: true,
tf: [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
dt: {}
})
return wrap
}
/** /**
* *
@ -25,21 +40,39 @@ export default class InstancePointManager {
* @returns ID (-1) * @returns ID (-1)
*/ */
createPoint(item: ItemJson): PointManageWrap { createPoint(item: ItemJson): PointManageWrap {
if (this.freeIndices.length === 0) { // Try existing blocks
system.showErrorDialog('InstancePointManager is full') let meshIndex = -1
let blockIndex = -1
for (const block of this.blocks) {
meshIndex = block.getFreeMeshIndex()
if (meshIndex >= 0) {
blockIndex = block.blockIndex
break
}
}
// 所有 block 都没有空闲索引,创建新的 block
if (meshIndex < 0) {
const block = this.createBlock()
meshIndex = block.getFreeMeshIndex()
blockIndex = block.blockIndex
}
if (meshIndex < 0) {
system.showErrorDialog('InstancePointManager: No free index available after creating new block')
return null return null
} }
const meshIndex = this.freeIndices.pop()! return new PointManageWrap(this, blockIndex, meshIndex, {
return new PointManageWrap(this, {
uuid: item.id, uuid: item.id,
name: item.name, name: item.name,
blockIndex: blockIndex,
meshIndex: meshIndex, meshIndex: meshIndex,
visible: item.v !== false, visible: item.v !== false,
//@ts-ignore //@ts-ignore
userData: { userData: {
t: item.t, t: item.t,
createType: 'point', createType: 'point',
blockIndex: blockIndex,
meshIndex: meshIndex,
entityId: item.id entityId: item.id
} }
}) })
@ -90,8 +123,12 @@ export default class InstancePointManager {
/** /**
* *
*/ */
findByMeshInstanceId(instanceId: number): PointManageWrap { findByMeshInstanceId(blockIndex: number, instanceId: number): PointManageWrap {
const uuid = this.__indexIdMap.get(instanceId) if (!this.blocks[blockIndex]) {
console.error('InstancePointManager: Invalid blockIndex', blockIndex)
return null
}
const uuid = this.blocks[blockIndex].__indexIdMap.get(instanceId)
if (!uuid) return if (!uuid) return
return this.__uuidMap.get(uuid) return this.__uuidMap.get(uuid)
} }
@ -110,13 +147,14 @@ export default class InstancePointManager {
} }
this.dummy.updateMatrix() this.dummy.updateMatrix()
const block = this.blocks[wrap.blockIndex]
if (!wrap.parent) { if (!wrap.parent) {
wrap.parent = this.instancedMesh wrap.parent = block.instancedMesh
this.__uuidMap.set(wrap.uuid, wrap) this.__uuidMap.set(wrap.uuid, wrap)
this.__indexIdMap.set(wrap.meshIndex, wrap.uuid) block.__indexIdMap.set(wrap.meshIndex, wrap.uuid)
} }
this.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix) block.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix)
this.instancedMesh.instanceMatrix.needsUpdate = true block.instancedMesh.instanceMatrix.needsUpdate = true
} }
/** /**
@ -125,18 +163,27 @@ export default class InstancePointManager {
*/ */
deletePoint(id: string): void { deletePoint(id: string): void {
const wrap = this.__uuidMap.get(id) const wrap = this.__uuidMap.get(id)
if (wrap === undefined) return if (!wrap) {
console.warn(`InstanceMeshManager: Wrap with id ${id} not found`)
return
}
const block = this.blocks[wrap.blockIndex]
if (!block) {
console.warn(`InstanceMeshManager: Block ${wrap.blockIndex} not found for wrap ${id}`)
return
}
// 隐藏实例 // 隐藏实例
this.dummy.scale.set(0, 0, 0) this.dummy.scale.set(0, 0, 0)
this.dummy.updateMatrix() this.dummy.updateMatrix()
this.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix) block.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix)
this.instancedMesh.instanceMatrix.needsUpdate = true block.instancedMesh.instanceMatrix.needsUpdate = true
// 回收索引 // 回收索引
this.freeIndices.push(wrap.meshIndex) block.freeIndices.push(wrap.meshIndex)
this.__uuidMap.delete(id) this.__uuidMap.delete(id)
this.__indexIdMap.delete(wrap.meshIndex) block.__indexIdMap.delete(wrap.meshIndex)
wrap.dispose() wrap.dispose()
} }
@ -146,65 +193,70 @@ export default class InstancePointManager {
viewport: Viewport, viewport: Viewport,
geometry: THREE.BufferGeometry, geometry: THREE.BufferGeometry,
material: THREE.Material, material: THREE.Material,
maxInstances: number = 1000 allowSelect: boolean, allowDrag: boolean
): InstancePointManager { ): InstancePointManager {
return new InstancePointManager(name, viewport, geometry, material, maxInstances) return new InstancePointManager(name, allowSelect, allowDrag, viewport, geometry, material)
} }
private constructor( private createBlock(): InstanceMeshBlock {
name: string, const block = new InstanceMeshBlock(
viewport: Viewport, this.name, this.allowSelect, this.allowDrag, this.viewport,
geometry: THREE.BufferGeometry, this.geometry,
material: THREE.Material, this.material, this.blocks.length, this.blockCapacity
maxInstances: number = 1000 )
) { this.blocks.push(block)
return block
}
private constructor(name: string, allowSelect: boolean, allowDrag: boolean, viewport: Viewport, geometry: THREE.BufferGeometry, material: THREE.Material) {
this.name = name
this.allowSelect = allowSelect
this.allowDrag = allowDrag
this.viewport = viewport this.viewport = viewport
this.geometry = geometry this.geometry = geometry
this.material = material this.material = material
this.maxInstanceCount = maxInstances
this.instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances)
this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
this.instancedMesh.name = name
console.log('InstancePointManager: [' + name + '] created with maxInstances:', maxInstances)
viewport.scene.add(this.instancedMesh)
this.viewport.entityManager._selectableObjects.push(this.instancedMesh)
this.viewport.entityManager._draggableObjects.push(this.instancedMesh)
this.instancedMesh.userData.t = name
this.instancedMesh.userData.entityId = 'InstancePointManager'
this.dummy.scale.set(0, 0, 0) // 创建第一个 block
for (let i = 0; i < maxInstances; i++) { this.createBlock()
this.dummy.updateMatrix()
this.instancedMesh.setMatrixAt(i, this.dummy.matrix)
this.freeIndices.push(i) // this.instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances)
} // this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
// this.instancedMesh.name = name
this.instancedMesh.instanceMatrix.needsUpdate = true // console.log('InstancePointManager: [' + name + '] created with maxInstances:', maxInstances)
// viewport.scene.add(this.instancedMesh)
// if (allowSelect) {
// this.viewport.entityManager._selectableObjects.push(this.instancedMesh)
// }
// if (allowDrag) {
// this.viewport.entityManager._draggableObjects.push(this.instancedMesh)
// }
// this.instancedMesh.userData.t = name
// this.instancedMesh.userData.entityId = 'InstancePointManager'
//
// this.dummy.scale.set(0, 0, 0)
// for (let i = 0; i < maxInstances; i++) {
// this.dummy.updateMatrix()
// this.instancedMesh.setMatrixAt(i, this.dummy.matrix)
// this.freeIndices.push(i)
// }
//
// this.instancedMesh.instanceMatrix.needsUpdate = true
} }
dispose() { dispose() {
this.viewport.scene.remove(this.instancedMesh) for (const block of this.blocks) {
this.instancedMesh.geometry.dispose() block.dispose()
if (this.instancedMesh.material) {
if (Array.isArray(this.instancedMesh.material)) {
this.instancedMesh.material.forEach(mat => mat.dispose())
} else {
this.instancedMesh.material.dispose()
}
} }
this.instancedMesh.dispose()
this.__uuidMap.clear() this.__uuidMap.clear()
this.__indexIdMap.clear() this.blocks.length = 0
this.freeIndices.length = 0
} }
} }
export class PointManageWrap { export class PointManageWrap {
readonly manager: InstancePointManager readonly manager: InstancePointManager
meshIndex: number = -1 readonly blockIndex: number = -1
readonly meshIndex: number = -1
parent: THREE.Object3D | null = null parent: THREE.Object3D | null = null
uuid: string uuid: string
@ -227,8 +279,9 @@ export class PointManageWrap {
return false return false
} }
constructor(pointManager: InstancePointManager, data: any, meshIndex: number = -1) { constructor(pointManager: InstancePointManager, blockIndex: number, meshIndex: number, data: any) {
this.manager = pointManager this.manager = pointManager
this.blockIndex = blockIndex
this.meshIndex = meshIndex this.meshIndex = meshIndex
_.extend(this, data) _.extend(this, data)
} }
@ -236,11 +289,24 @@ export class PointManageWrap {
dispose() { dispose() {
this.manager.deletePoint(this.uuid) this.manager.deletePoint(this.uuid)
this.parent = null this.parent = null
this.meshIndex = -1 }
applyMatrix4(matrix: THREE.Matrix4): PointManageWrap {
const position = new THREE.Vector3()
const quaternion = new THREE.Quaternion()
const scale = new THREE.Vector3()
matrix.decompose(position, quaternion, scale)
this.position.copy(position)
this.rotation.setFromQuaternion(quaternion)
this.scale.copy(scale)
this.manager.syncMeshObject3D(this)
return this
} }
createBox3(): THREE.Box3 { createBox3(): THREE.Box3 {
const instancedMesh = this.manager.instancedMesh const instancedMesh = this.manager.blocks[this.blockIndex]?.instancedMesh
const matrix = new THREE.Matrix4() const matrix = new THREE.Matrix4()
instancedMesh.getMatrixAt(this.meshIndex, matrix) instancedMesh.getMatrixAt(this.meshIndex, matrix)
// 创建包围盒并应用矩阵 // 创建包围盒并应用矩阵

19
src/core/manager/LabelManager.ts

@ -28,6 +28,8 @@ export interface LabelOption {
*/ */
padding?: number | string padding?: number | string
text?: string text?: string
format?: (distance: number) => string
} }
/** /**
@ -36,9 +38,13 @@ export interface LabelOption {
export default class LabelManager implements IControls { export default class LabelManager implements IControls {
viewport: Viewport viewport: Viewport
private labelMap: Map<string, Text | CSS2DObject> = new Map() private labelMap: Map<string, Text | CSS2DObject> = new Map()
private labelGroup: THREE.Group
init(viewport: Viewport): void { init(viewport: Viewport): void {
this.viewport = viewport this.viewport = viewport
this.labelGroup = new THREE.Group()
// this.labelGroup.visible = false
this.viewport.scene.add(this.labelGroup)
} }
createOrUpdateLabelByDistance(lineRef: Object3DLike, startPos: THREE.Vector3, endPos: THREE.Vector3, option: LabelOption): Text | CSS2DObject { createOrUpdateLabelByDistance(lineRef: Object3DLike, startPos: THREE.Vector3, endPos: THREE.Vector3, option: LabelOption): Text | CSS2DObject {
@ -54,7 +60,10 @@ export default class LabelManager implements IControls {
// 计算距离 // 计算距离
const distance = startPos.distanceTo(endPos) const distance = startPos.distanceTo(endPos)
const text = distance.toFixed(2) + ' m' let text = distance.toFixed(2) + ' m'
if (option.format) {
text = option.format(distance)
}
this.updateLabel(lineRef, text) this.updateLabel(lineRef, text)
return labelObj return labelObj
@ -64,7 +73,7 @@ export default class LabelManager implements IControls {
const labelObj = this.createLabelObject(option) const labelObj = this.createLabelObject(option)
parentObj.userData.labelObjectId = labelObj.uuid parentObj.userData.labelObjectId = labelObj.uuid
this.viewport.scene.add(labelObj) this.labelGroup.add(labelObj)
if (labelObj instanceof CSS2DObject) { if (labelObj instanceof CSS2DObject) {
labelObj.element.innerHTML = option.text labelObj.element.innerHTML = option.text
@ -99,7 +108,7 @@ export default class LabelManager implements IControls {
if (parentObj?.userData?.labelObjectId) { if (parentObj?.userData?.labelObjectId) {
const labelObj = this.labelMap.get(parentObj.userData.labelObjectId) const labelObj = this.labelMap.get(parentObj.userData.labelObjectId)
this.labelMap.delete(labelObj.uuid) this.labelMap.delete(labelObj.uuid)
this.viewport.scene.remove(labelObj) this.labelGroup.remove(labelObj)
labelObj.dispose() labelObj.dispose()
parentObj.userData.labelObjectId = undefined parentObj.userData.labelObjectId = undefined
} }
@ -108,7 +117,7 @@ export default class LabelManager implements IControls {
removeById(id: string): void { removeById(id: string): void {
const labelObj = this.labelMap.get(id) const labelObj = this.labelMap.get(id)
if (labelObj) { if (labelObj) {
this.viewport.scene.remove(labelObj) this.labelGroup.remove(labelObj)
this.labelMap.delete(id) this.labelMap.delete(id)
labelObj.dispose() labelObj.dispose()
} }
@ -160,6 +169,8 @@ export default class LabelManager implements IControls {
dispose(): void { dispose(): void {
// 清理资源 // 清理资源
this.viewport.scene.remove(this.labelGroup)
this.labelGroup.clear()
this.viewport = undefined this.viewport = undefined
} }

17
src/core/manager/LineSegmentManager.ts

@ -4,13 +4,15 @@ import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeome
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import type { Object3DLike, Vector3Like } from '@/types/ModelTypes.ts' import type { Object3DLike, Vector3Like } from '@/types/ModelTypes.ts'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { Line2 } from 'three/examples/jsm/lines/Line2'
/** /**
* 线 * 线
*/ */
export default class LineSegmentManager { export default class LineSegmentManager {
private readonly viewport: Viewport private readonly viewport: Viewport
private readonly lineGeometry: LineSegmentsGeometry private readonly lineGeometry: LineGeometry // LineSegmentsGeometry
private readonly lineMaterial: LineMaterial private readonly lineMaterial: LineMaterial
private readonly lineSegments: LineSegments2 private readonly lineSegments: LineSegments2
private readonly segments: Map<string, LineManageWrap> = new Map() private readonly segments: Map<string, LineManageWrap> = new Map()
@ -26,11 +28,12 @@ export default class LineSegmentManager {
* @param color 线 () * @param color 线 ()
* @param userData () * @param userData ()
*/ */
createLine(lineId: string, start: THREE.Vector3 | [number, number, number], end: Vector3Like, color?: THREE.Color | number | string, userData: Partial<UserData> = {}): LineManageWrap { createLine(lineId: string, start: Vector3Like, end: Vector3Like, color?: THREE.Color | number | string, userData: Partial<UserData> = {}): LineManageWrap {
const segment = this.segments.get(lineId) const segment = this.segments.get(lineId)
if (segment) { if (segment) {
console.error(`LineSegmentManager: Line with id ${lineId} already exists.`) // console.error(`LineSegmentManager: Line with id ${lineId} already exists.`)
return // 如果线段已存在,则更新其起点、终点和颜色
return this.updateLine(lineId, start, end, color)
} }
const startVec = start instanceof THREE.Vector3 const startVec = start instanceof THREE.Vector3
@ -241,7 +244,7 @@ export default class LineSegmentManager {
this.lineGeometry.setColors(this.colorArray) this.lineGeometry.setColors(this.colorArray)
// 设置实例计数为可见线段数量 // 设置实例计数为可见线段数量
this.lineGeometry.instanceCount = Array.from(this.segments.values()).filter(s => s.visible).length // this.lineGeometry.instanceCount = Array.from(this.segments.values()).filter(s => s.visible).length
this.needsUpdate = false this.needsUpdate = false
} }
@ -256,10 +259,10 @@ export default class LineSegmentManager {
this.viewport = viewport this.viewport = viewport
this.lineMaterial = lineMaterial this.lineMaterial = lineMaterial
this.lineGeometry = new LineSegmentsGeometry() this.lineGeometry = new LineGeometry() // new LineSegmentsGeometry()
// 创建线段的渲染对象 // 创建线段的渲染对象
this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) this.lineSegments = new Line2(this.lineGeometry, this.lineMaterial)
this.lineSegments.name = name this.lineSegments.name = name
this.lineSegments.frustumCulled = false this.lineSegments.frustumCulled = false
this.viewport.scene.add(this.lineSegments) this.viewport.scene.add(this.lineSegments)

4
src/core/manager/WorldModel.ts

@ -6,6 +6,8 @@ import Way from '@/modules/way'
import Gstore from '@/modules/gstore' import Gstore from '@/modules/gstore'
import Rack from '@/modules/rack' import Rack from '@/modules/rack'
import Pallet from "@/modules/pallet" import Pallet from "@/modules/pallet"
import Tote from "@/modules/tote"
import Carton from "@/modules/carton"
import Ptr from "@/modules/ptr" import Ptr from "@/modules/ptr"
import Clx from "@/modules/clx" import Clx from "@/modules/clx"
import Charger from "@/modules/charger" import Charger from "@/modules/charger"
@ -72,6 +74,8 @@ export default class WorldModel {
Gstore, Gstore,
Rack, Rack,
Pallet, Pallet,
Tote,
Carton,
Ptr, Ptr,
Clx, Clx,
Charger Charger

127
src/editor/BulkCopy.vue

@ -0,0 +1,127 @@
<script setup lang="ts">
import { reactive } from "vue";
import DataForm from "@/components/data-form/DataForm.vue";
import type { FormField } from "@/components/data-form/DataFormTypes.ts";
defineOptions({
name: 'BulkCopy',
});
interface BulkCopyConfig {
/** 行数 */
numberOfRows?: number;
/** 列数 */
numberOfColumns?: number;
/** 行间距 */
rowSpace?: number;
/** 列间距 */
columnSpacing?: number;
/** 双排 */
doubleRow?: boolean;
}
// Props
interface BulkCopyProps {
config: BulkCopyConfig;
}
// props
const props = withDefaults(defineProps<BulkCopyProps>(), {});
// State
interface BulkCopyState {
}
// state
const state = reactive<BulkCopyState>({});
// Data
interface BulkCopyData {
formFields?: Array<FormField>;
}
//
const data: BulkCopyData = {
formFields: [
{
dataPath: 'numberOfRows', label: '行数', input: 'InputNumber',
inputProps: {
placeholder: '请输入',
controlsPosition: 'right',
min: 1,
precision: 0,
},
},
{
dataPath: 'numberOfColumns', label: '列数', input: 'InputNumber',
inputProps: {
placeholder: '请输入',
controlsPosition: 'right',
min: 1,
precision: 0,
},
},
{
dataPath: 'rowSpace', label: '行间距', input: 'InputNumber',
inputProps: {
placeholder: '请输入',
controlsPosition: 'right',
min: 0,
// precision:
},
},
{
dataPath: 'columnSpacing', label: '列间距', input: 'InputNumber',
inputProps: {
placeholder: '请输入',
controlsPosition: 'right',
min: 0,
// precision:
},
},
{
dataPath: 'doubleRow', label: '单双排', input: 'Switch',
inputProps: {
// trueValue: true,
// falseValue: false,
inlinePrompt: true,
inactiveText: "单排",
activeText: "双排",
},
},
],
};
interface BulkCopyExpose {
state: BulkCopyState;
data: BulkCopyData;
}
const expose: BulkCopyExpose = {
state,
data,
};
//
defineExpose(expose);
export type {
BulkCopyConfig,
BulkCopyProps,
BulkCopyState,
}
</script>
<template>
<DataForm
:data="props.config"
:formFields="data.formFields"
:columnCount="2"
layout="onlyLabelFixed"
labelWidth="80px"
inputWidth=""
/>
</template>
<style scoped>
</style>

93
src/editor/Model2DEditor.vue

@ -17,6 +17,16 @@
:type="state?.view3DMode===Constract.Mode3D?'primary':''" :type="state?.view3DMode===Constract.Mode3D?'primary':''"
@click="state.view3DMode = Constract.Mode3D">3D @click="state.view3DMode = Constract.Mode3D">3D
</el-button> </el-button>
<el-button :icon="renderIcon('element Files')" link @click="showBulkCopy" :disabled="!selectedObject">
批量复制
</el-button>
<el-upload :on-change="addCADDxf"
:show-file-list="false" accept=".dxf" action="" :auto-upload="false">
<el-button :icon="renderIcon('element Files')" link >
添加CAD图纸
</el-button>
</el-upload>
</el-button-group> </el-button-group>
</div> </div>
<div class="section-content"> <div class="section-content">
@ -92,13 +102,17 @@
</template> </template>
<script> <script>
import * as THREE from 'three' import * as THREE from 'three'
import $ from 'jquery' import { renderIcon, setQueryParam } from '@/utils/webutils'
import { getQueryParams, renderIcon, setQueryParam } from '@/utils/webutils' import { createVNode, defineComponent, markRaw } from 'vue'
import { defineComponent, markRaw } from 'vue'
import Viewport from '@/core/engine/Viewport' import Viewport from '@/core/engine/Viewport'
import Constract from '@/core/Constract' import Constract from '@/core/Constract'
import EventBus from '@/runtime/EventBus' import EventBus from '@/runtime/EventBus'
import SceneHelp from '@/core/engine/SceneHelp' import SceneHelp from '@/core/engine/SceneHelp'
import BulkCopy from './BulkCopy.vue'
import lodash from "lodash";
import { DXFViewer } from 'three-dxf-viewer';
import cadFont from "@/assets/fonts/helvetiker_regular.typeface.json?url"
export default defineComponent({ export default defineComponent({
name: 'Model2DEditor', name: 'Model2DEditor',
@ -242,7 +256,75 @@ export default defineComponent({
}) })
},
showBulkCopy() {
const viewport = this.viewport;
const selectedItem = viewport?.state?.selectedItem;
if(!viewport || !selectedItem) return;
const config = {
numberOfRows: 1,
numberOfColumns: 1,
rowSpace: 0,
columnSpacing: 0,
doubleRow: false,
};
system.showDialog(createVNode(BulkCopy, {
config,
}), {
title: '批量复制',
width: 520,
height: 240,
showClose: true,
showMax: true,
showCancelButton: false,
showOkButton: true,
}).then(() => {
if(!config.numberOfRows || !config.numberOfColumns) return;
viewport.stateManager.update(({ getEntity, addEntity }) => {
const xAxle = selectedItem.tf[0][0];
const zAxle = selectedItem.tf[0][2];
const xSize = selectedItem.tf[2][0];
const zSize = selectedItem.tf[2][2];
console.log("item", JSON.stringify(selectedItem.tf));
for (let rCount = 0; rCount < config.numberOfRows; rCount++) {
for (let cCount = 0; cCount < config.numberOfColumns; cCount++) {
if(rCount===0 && cCount===0) continue;
const item = lodash.cloneDeep(selectedItem);
item.id = system.createUUID();
item.tf[0][0] = xAxle + cCount * (xSize + config.columnSpacing);
item.tf[0][2] = zAxle + rCount * (zSize + config.rowSpace);
addEntity(item);
console.log("item", JSON.stringify(item.tf));
}
}
});
console.log("config", config, selectedItem);
}).finally();
},
async addCADDxf(file) {
// 1
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const cube = new THREE.Mesh(geometry, material);
console.log('file', file)
if (!file) return
file = file.raw
const viewer = new DXFViewer();
let dxf = await viewer.getFromFile(file, cadFont);
dxf.scale.set(0.001, 0.001, 0.001)
dxf.rotation.x = -Math.PI / 2
// Add the geometry to the scene
const group = new THREE.Group();
group.add(dxf);
this.scene.add(group);
} }
}, },
computed: { computed: {
state() { state() {
@ -268,7 +350,10 @@ export default defineComponent({
label: item.label label: item.label
})) }))
})) }))
} },
selectedObject() {
return this.state?.selectedObject;
},
} }
}) })
</script> </script>

6
src/editor/ModelMain.less

@ -278,7 +278,7 @@
height: 30px; height: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: row;
.el-button { .el-button {
margin-left: 5px; margin-left: 5px;
} }
@ -289,7 +289,9 @@
background: #dcdcdc; background: #dcdcdc;
margin: 0 5px; margin: 0 5px;
} }
.el-button-group {
flex: 1;
}
&.section-bottom-toolbar { &.section-bottom-toolbar {
justify-content: space-between; justify-content: space-between;

18
src/editor/menus/Model3DView.ts

@ -1,6 +1,7 @@
import { defineMenu } from '@/runtime/DefineMenu.ts' import { defineMenu } from '@/runtime/DefineMenu.ts'
import Model3DView from '@/components/Model3DView.vue' import Model3DView from '@/components/Model3DView.vue'
import ThreePerfView from '@/components/ThreePerfView.vue' import ThreePerfView from '@/components/ThreePerfView.vue'
import ThreePerfView2 from '@/components/ThreePerfView2.vue'
export default defineMenu((menus) => { export default defineMenu((menus) => {
menus.insertChildren('tool', menus.insertChildren('tool',
@ -14,7 +15,7 @@ export default defineMenu((menus) => {
system.showDialog(Model3DView, { system.showDialog(Model3DView, {
title: '模型查看器', title: '模型查看器',
width: 950, width: 950,
height: 400, height: 950,
showClose: true, showClose: true,
showMax: true, showMax: true,
showCancelButton: false, showCancelButton: false,
@ -37,6 +38,21 @@ export default defineMenu((menus) => {
dialogClass: 'model-3d-view-wrap' dialogClass: 'model-3d-view-wrap'
}) })
} }
},
{
name: 'threePerfView2', label: '性能测试2', order: 3,
click: () => {
system.showDialog(ThreePerfView2, {
title: '性能测试2',
width: 950,
height: 400,
showClose: true,
showMax: true,
showCancelButton: false,
showOkButton: false,
dialogClass: 'model-3d-view-wrap'
})
}
} }
] ]
) )

99
src/editor/propEditors/BayEditor.vue

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import lodash from "lodash";
import { computed, reactive } from "vue"; import { computed, reactive } from "vue";
import { ElButton, ElDivider, ElFormItem, ElIcon, ElInputNumber, useFormItem } from "element-plus"; import { ElButton, ElDivider, ElFormItem, ElIcon, ElInputNumber, useFormItem } from "element-plus";
import { CopyDocument, Delete } from "@element-plus/icons-vue"; import { CopyDocument, Delete } from "@element-plus/icons-vue";
@ -36,10 +37,15 @@ const props = withDefaults(defineProps<BayEditorProps>(), {});
// State // State
interface BayEditorState { interface BayEditorState {
/** 列数 */
numberOfBays: number; numberOfBays: number;
/** 层数 */
numberOfLevels: number; numberOfLevels: number;
/** 列宽 */
widthOfBays: number; widthOfBays: number;
/** 层高 */
heightOfLevels: number; heightOfLevels: number;
/** 选中列 */
selectIdx?: number; selectIdx?: number;
} }
@ -61,26 +67,78 @@ const data: BayEditorData = {
labelWidth: 110, labelWidth: 110,
}; };
const { formItem } = useFormItem(); const { formItem } = useFormItem();
const list = computed(() => props.modelValue ?? []); const list = computed(getModelValue);
const selectDay = computed(() => { const selectDay = computed(() => {
if (Typeof.noValue(state.selectIdx)) return; if (Typeof.noValue(state.selectIdx)) return;
return props.modelValue?.[state.selectIdx]; return props.modelValue?.[state.selectIdx];
}); });
function setSelectIdx(idx: number) { function setSelectIdx(idx?: number) {
state.selectIdx = idx; state.selectIdx = idx;
} }
function applyBasicSettings() { function applyBasicSettings() {
const { numberOfBays, numberOfLevels, widthOfBays, heightOfLevels } = state;
const newValue = getModelValue();
newValue.length = 0;
for (let idx = 0; idx < numberOfBays; idx++) {
const bay: DtBay = {
bayWidth: widthOfBays,
offset: 0,
levelHeight: [],
};
for (let i = 0; i < numberOfLevels; i++) {
bay.levelHeight.push(heightOfLevels);
}
newValue.push(bay);
}
emit("update:modelValue", newValue);
} }
function deleteBay() { function deleteBay() {
const selectIdx = state.selectIdx;
if (Typeof.noValue(selectIdx)) return;
const newValue = getModelValue();
if (selectIdx >= newValue.length) return;
newValue.splice(selectIdx, 1);
emit("update:modelValue", newValue);
if (selectIdx >= newValue.length) {
setSelectIdx(undefined);
}
} }
function copyBay() { function copyBay() {
const selectIdx = state.selectIdx;
if (Typeof.noValue(selectIdx)) return;
const newValue = getModelValue();
const bay = newValue[selectIdx];
if (!bay) return;
newValue.splice(selectIdx + 1, 0, lodash.cloneDeep(bay));
emit("update:modelValue", newValue);
setSelectIdx(selectIdx + 1);
}
function updateBayWidth(bay: DtBay, bayWidth?: number) {
bay.bayWidth = bayWidth;
const newValue = getModelValue();
emit("update:modelValue", newValue);
}
function updateBayOffset(bay: DtBay, offset?: number) {
bay.offset = offset;
const newValue = getModelValue();
emit("update:modelValue", newValue);
}
function updateBayLevelHeight(bay: DtBay, idx: number, levelHeight?: number) {
if (!bay.levelHeight) bay.levelHeight = [];
bay.levelHeight[idx] = levelHeight;
const newValue = getModelValue();
emit("update:modelValue", newValue);
}
function getModelValue(): DtBays {
return props.modelValue ?? [];
} }
interface BayEditorExpose { interface BayEditorExpose {
@ -130,10 +188,10 @@ export type {
<div class="flex-row-container bay-editor-advanced"> <div class="flex-row-container bay-editor-advanced">
<div class="flex-item-fixed flex-column-container bay-editor-bay-list"> <div class="flex-item-fixed flex-column-container bay-editor-bay-list">
<div class="flex-item-fixed tools-button-container"> <div class="flex-item-fixed tools-button-container">
<ElIcon :class="['tools-button-icon', { 'tools-button-disabled': false }]" @click="copyBay"> <ElIcon :class="['tools-button-icon', { 'tools-button-disabled': !selectDay }]" @click="copyBay">
<CopyDocument/> <CopyDocument/>
</ElIcon> </ElIcon>
<ElIcon :class="['tools-button-icon', { 'tools-button-disabled': false }]" @click="deleteBay"> <ElIcon :class="['tools-button-icon', { 'tools-button-disabled': !selectDay }]" @click="deleteBay">
<Delete/> <Delete/>
</ElIcon> </ElIcon>
</div> </div>
@ -155,21 +213,34 @@ export type {
<div v-if="selectDay" class="flex-item-fill flex-column-container bay-editor-bay-info"> <div v-if="selectDay" class="flex-item-fill flex-column-container bay-editor-bay-info">
<div class="flex-item-fixed"> <div class="flex-item-fixed">
<ElFormItem label="Bay Width" :labelWidth="80"> <ElFormItem label="Bay Width" :labelWidth="80">
<ElInputNumber :controls="false" :modelValue="selectDay.bayWidth"/> <ElInputNumber
:controls="false"
:modelValue="selectDay.bayWidth"
@change="(width: number) => updateBayWidth(selectDay, width)"
/>
</ElFormItem> </ElFormItem>
<div style="height: 8px;"/> <div style="height: 8px;"/>
<ElFormItem label="Bay Offset" :labelWidth="80"> <ElFormItem label="Bay Offset" :labelWidth="80">
<ElInputNumber :controls="false" :modelValue="selectDay.offset"/> <ElInputNumber
:controls="false"
:modelValue="selectDay.offset"
@change="(offset: number) => updateBayOffset(selectDay, offset)"
/>
</ElFormItem> </ElFormItem>
</div> </div>
<div>Level Heights</div> <div>Level Heights</div>
<div class="flex-item-fill bay-editor-bay-info-level-height"> <div class="flex-item-fill bay-editor-bay-info-level-height">
<ElFormItem <ElFormItem
v-for="(levelHeight, idx) in selectDay.levelHeight" v-for="(levelHeight, idx) in selectDay.levelHeight"
labelPosition="left"
:label="`Level ${idx+1}`" :label="`Level ${idx+1}`"
:labelWidth="64" :labelWidth="64"
> >
<ElInputNumber :controls="false" :modelValue="levelHeight"/> <ElInputNumber
:controls="false"
:modelValue="levelHeight"
@change="(height: number) => updateBayLevelHeight(selectDay, idx, height)"
/>
</ElFormItem> </ElFormItem>
</div> </div>
</div> </div>
@ -183,6 +254,7 @@ export type {
<style scoped> <style scoped>
.bay-editor { .bay-editor {
width: 100%; width: 100%;
user-select: none;
} }
.bay-editor-title { .bay-editor-title {
@ -204,6 +276,7 @@ export type {
.bay-editor-bay-list-name { .bay-editor-bay-list-name {
border: 1px solid #dddddd; border: 1px solid #dddddd;
overflow-y: auto;
} }
.tools-button-container { .tools-button-container {
@ -215,6 +288,9 @@ export type {
gap: 4px; gap: 4px;
cursor: pointer; cursor: pointer;
width: 100%; width: 100%;
background-color: #f5f5f5;
border: 1px solid #dddddd;
border-bottom: none;
} }
.tools-button-container > .tools-button-icon { .tools-button-container > .tools-button-icon {
@ -247,7 +323,7 @@ export type {
} }
.list-item { .list-item {
padding: 4px 0 4px 2px; padding: 0 2px;
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
user-select: none; user-select: none;
@ -277,6 +353,7 @@ export type {
.bay-editor-bay-info-level-height { .bay-editor-bay-info-level-height {
border: 1px solid #dddddd; border: 1px solid #dddddd;
overflow-y: auto;
} }
.bay-editor-bay-info-level-height :deep(.el-input__wrapper) { .bay-editor-bay-info-level-height :deep(.el-input__wrapper) {
@ -291,6 +368,8 @@ export type {
.bay-editor-bay-info-level-height :deep(.el-form-item > .el-form-item__label) { .bay-editor-bay-info-level-height :deep(.el-form-item > .el-form-item__label) {
border-right: 1px solid #dddddd; border-right: 1px solid #dddddd;
background-color: #f0f0f0;
padding: 0 2px;
} }
.bay-editor-bay-info-level-height :deep(.el-input-number) { .bay-editor-bay-info-level-height :deep(.el-input-number) {

6
src/editor/propEditors/InOutCenterEditor.vue

@ -67,7 +67,7 @@ const canUpItem = computed(() => selectIdx.value > 0);
const canDownItem = computed(() => selectIdx.value < (list.value.length - 1)); const canDownItem = computed(() => selectIdx.value < (list.value.length - 1));
const canDeleteItem = computed(() => selectIdx.value < list.value.length); const canDeleteItem = computed(() => selectIdx.value < list.value.length);
function setSelectIdx(idx: number) { function setSelectIdx(idx?: number) {
selectIdx.value = idx; selectIdx.value = idx;
} }
@ -112,7 +112,7 @@ function deleteItem() {
list.splice(selectIdx.value, 1); list.splice(selectIdx.value, 1);
emit("update:modelValue", modelValue); emit("update:modelValue", modelValue);
if (selectIdx.value >= list.length) { if (selectIdx.value >= list.length) {
setSelectIdx(Math.max(0, selectIdx.value - 1)); setSelectIdx(undefined);
} }
} }
@ -256,7 +256,7 @@ export type {
} }
.list-item { .list-item {
padding: 4px 0 4px 2px; padding: 0 2px;
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
user-select: none; user-select: none;

12
src/editor/propEditors/TransformEditor.vue

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive } from "vue"; import { reactive } from "vue";
import { ElInputNumber, useFormItem } from "element-plus"; import { ElInputNumber, useFormItem } from "element-plus";
import { Typeof } from "@ease-forge/shared";
import { renderIcon } from "@/utils/webutils.js"; import { renderIcon } from "@/utils/webutils.js";
defineOptions({ defineOptions({
@ -69,9 +68,9 @@ function getPosition(idx: 0 | 1 | 2) {
} }
function getRotation(idx: 0 | 1 | 2) { function getRotation(idx: 0 | 1 | 2) {
const radian = props.modelValue?.[1]?.[idx]; return props.modelValue?.[1]?.[idx];
if (Typeof.noValue(radian)) return; // if (Typeof.noValue(radian)) return;
return Math.round(radian * 180 / Math.PI); // return Math.round(radian * 180 / Math.PI);
} }
function getScale(idx: 0 | 1 | 2) { function getScale(idx: 0 | 1 | 2) {
@ -79,8 +78,9 @@ function getScale(idx: 0 | 1 | 2) {
} }
function calcRotation(rotation?: number) { function calcRotation(rotation?: number) {
if (Typeof.noValue(rotation)) return; return rotation;
return rotation * Math.PI / 180; // if (Typeof.noValue(rotation)) return;
// return rotation * Math.PI / 180;
} }
interface TransformEditorExpose { interface TransformEditorExpose {

136
src/editor/widgets/property/PropertyPanel.vue

@ -9,23 +9,23 @@ import { defDataFormProps } from '@/editor/widgets/property/PropertyPanelConstan
import Viewport, { type ViewportState } from '@/core/engine/Viewport.ts' import Viewport, { type ViewportState } from '@/core/engine/Viewport.ts'
defineOptions({ defineOptions({
name: 'PropertyPanel' name: 'PropertyPanel'
}) })
// Props // Props
interface PropertyPanelProps { interface PropertyPanelProps {
/** 待编辑数据 */ /** 待编辑数据 */
data?: any; data?: any;
/** Viewport */ /** Viewport */
viewport: Viewport; viewport: Viewport;
/** ViewportState */ /** ViewportState */
viewportState: ViewportState; viewportState: ViewportState;
/** 默认的DataFormProps */ /** 默认的DataFormProps */
defDataFormProps?: DataFormProps; defDataFormProps?: DataFormProps;
/** 最上面平铺的设置器 */ /** 最上面平铺的设置器 */
flatten?: PropertyFlattenSetter; flatten?: PropertyFlattenSetter;
/** 设置器分组集合 */ /** 设置器分组集合 */
groups?: Array<PropertySetterGroup>; groups?: Array<PropertySetterGroup>;
} }
// props // props
@ -33,16 +33,16 @@ const props = withDefaults(defineProps<PropertyPanelProps>(), {})
// State // State
interface PropertyPanelState { interface PropertyPanelState {
/** 待编辑数据 */ /** 待编辑数据 */
data?: any; data?: any;
/** 已展开的分组 */ /** 已展开的分组 */
expandGroups: Array<string>; expandGroups: Array<string>;
} }
// state // state
const state = reactive<PropertyPanelState>({ const state = reactive<PropertyPanelState>({
data: lodash.cloneDeep(props.data), data: lodash.cloneDeep(props.data),
expandGroups: [] expandGroups: []
}) })
// Data // Data
@ -54,84 +54,84 @@ const data: PropertyPanelData = {}
const flattenFormProps = computed(() => getDefFormProps(props.flatten)) const flattenFormProps = computed(() => getDefFormProps(props.flatten))
function getDefFormProps(setter?: PropertyFlattenSetter) { function getDefFormProps(setter?: PropertyFlattenSetter) {
const formProps: DataFormProps = { const formProps: DataFormProps = {
...defDataFormProps, ...defDataFormProps,
...props.defDataFormProps ...props.defDataFormProps
} }
fillFormProps(setter) fillFormProps(setter)
return formProps return formProps
} }
function fillFormProps(formProps: DataFormProps, setter?: PropertyFlattenSetter) { function fillFormProps(formProps: DataFormProps, setter?: PropertyFlattenSetter) {
if (!setter) return if (!setter) return
if (setter.size) formProps.size = setter.size if (setter.size) formProps.size = setter.size
if (setter.labelWidth) formProps.labelWidth = setter.labelWidth if (setter.labelWidth) formProps.labelWidth = setter.labelWidth
} }
function getCollapseItemId(group: PropertySetterGroup, idx: number) { function getCollapseItemId(group: PropertySetterGroup, idx: number) {
return `_${idx}_${group.title}` return `_${idx}_${group.title}`
} }
function onDataChange(newData: any) { function onDataChange(newData: any) {
const viewport = props.viewport const viewport = props.viewport
if (!viewport) return if (!viewport) return
viewport.stateManager.update(({ getEntity, putEntity }) => { viewport.stateManager.update(({ getEntity, putEntity }) => {
const data = getEntity(props.data.id) const data = getEntity(props.data.id);
lodash.assign(data, newData) lodash.assign(data, newData);
// console.log('onDataChange@1', JSON.stringify(data.dt)) // console.log('onDataChange@1', JSON.stringify(data.dt))
putEntity(data) putEntity(data);
}) });
} }
interface PropertyPanelExpose { interface PropertyPanelExpose {
state: PropertyPanelState; state: PropertyPanelState;
data: PropertyPanelData; data: PropertyPanelData;
} }
const expose: PropertyPanelExpose = { const expose: PropertyPanelExpose = {
state, state,
data data
} }
// //
defineExpose(expose) defineExpose(expose)
export type { export type {
PropertyPanelProps, PropertyPanelProps,
PropertyPanelState PropertyPanelState
} }
</script> </script>
<template> <template>
<div class="property-panel"> <div class="property-panel">
<DataForm
v-if="props.flatten"
class="property-panel-form"
v-bind="flattenFormProps"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
/>
<ElCollapse v-if="props.groups" v-model="state.expandGroups">
<ElCollapseItem
v-for="(group, idx) in props.groups"
:name="getCollapseItemId(group, idx)"
:title="group.title"
>
<DataForm <DataForm
v-if="group" v-if="props.flatten"
class="property-panel-form" class="property-panel-form"
v-bind="getDefFormProps(group)" v-bind="flattenFormProps"
:data="state.data" :data="state.data"
:formFields="props.flatten.fields" :formFields="props.flatten.fields"
@dataChange="onDataChange" @dataChange="onDataChange"
/> />
</ElCollapseItem> <ElCollapse v-if="props.groups" v-model="state.expandGroups">
</ElCollapse> <ElCollapseItem
</div> v-for="(group, idx) in props.groups"
:name="getCollapseItemId(group, idx)"
:title="group.title"
>
<DataForm
v-if="group"
class="property-panel-form"
v-bind="getDefFormProps(group)"
:data="state.data"
:formFields="props.flatten.fields"
@dataChange="onDataChange"
/>
</ElCollapseItem>
</ElCollapse>
</div>
</template> </template>
<style scoped> <style scoped>
.property-panel { .property-panel {
padding: 8px 12px 16px 4px; padding: 8px 12px 16px 4px;
} }
</style> </style>

192
src/example/ExampleUtil.js

@ -1,9 +1,170 @@
export function buildRackPerformanceData(rows, cols) {
const spacingX = 20 // X轴间距
const spacingZ = 2.5 // Y轴间距
// 创建一个二维数组来存储点阵数据
const data = new Map()
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// 计算每个点的坐标
const node = {
id: 'wp_' + row + '_' + col,
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, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] }
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
// { bayWidth: 1.275, levelHeight: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4, 1.4] }
],
center: [],
in: [],
out: []
}
}
node.tf[0][0] = row * spacingX
node.tf[0][1] = 0.01
node.tf[0][2] = col * spacingZ
node.tf[1] = [0, 0, 0]
node.tf[2] = [1, 0.4, 1]
data.set(node.id, node)
}
}
return Array.from(data.values())
}
/**
* 构建点阵性能数据
* @param t
* @param rows
* @param cols
*/
export function buildPointPerformanceData(t, rows, cols) {
const spacingX = 1.5 // X轴间距
const spacingZ = 1.5 // Y轴间距
// 创建一个二维数组来存储点阵数据
const data = new Map()
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// 计算每个点的坐标
const node = createAgvNode(t, row, col)
node.tf[0][0] = row * spacingX
node.tf[0][1] = 0.01
node.tf[0][2] = col * spacingZ
node.tf[1] = [0, 0, 0]
node.tf[2] = [1, 1.5, 1]
data.set(node.id, node)
}
}
return Array.from(data.values())
}
/**
* 构建AGV性能数据
* @param rows 行数
* @param cols 列数
*/
export function buildAgvPerformanceData(t, rows, cols) {
const spacingX = 1.25 // X轴间距
const spacingZ = 1.25 // Y轴间距
// 创建一个二维数组来存储点阵数据
const data = new Map()
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// 计算每个点的坐标
const node = createAgvNode(t, row, col)
node.tf[0][0] = row * spacingX
node.tf[0][2] = col * spacingZ
data.set(node.id, node)
// 与前一个点进行连线
if (row > 0 && col > 0) {
const preXNode = data.get('wp_' + (row - 1) + '_' + (col))
node.dt.in.push(preXNode.id)
preXNode.dt.out.push(node.id)
const preYNode = data.get('wp_' + (row) + '_' + (col - 1))
node.dt.in.push(preYNode.id)
preYNode.dt.out.push(node.id)
} else if (row > 0) {
const preXNode = data.get('wp_' + (row - 1) + '_' + (col))
node.dt.in.push(preXNode.id)
preXNode.dt.out.push(node.id)
} else if (col > 0) {
const preYNode = data.get('wp_' + (row) + '_' + (col - 1))
node.dt.in.push(preYNode.id)
preYNode.dt.out.push(node.id)
}
}
}
return Array.from(data.values())
}
/** /**
* 构建AGV性能数据 * 构建AGV性能数据
* @param rows 行数 * @param rows 行数
* @param cols 列数 * @param cols 列数
*/ */
export function buildAgvPerformanceData(rows, cols) { export function buildCenterLinkPerformanceData(t, rows, cols) {
const spacingX = 1.25 // X轴间距 const spacingX = 1.25 // X轴间距
const spacingZ = 1.25 // Y轴间距 const spacingZ = 1.25 // Y轴间距
@ -12,7 +173,7 @@ export function buildAgvPerformanceData(rows, cols) {
for (let row = 0; row < rows; row++) { for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) { for (let col = 0; col < cols; col++) {
// 计算每个点的坐标 // 计算每个点的坐标
const node = createAgvNode(row, col) const node = createAgvNode(t, row, col)
node.tf[0][0] = row * spacingX node.tf[0][0] = row * spacingX
node.tf[0][2] = col * spacingZ node.tf[0][2] = col * spacingZ
data.set(node.id, node) data.set(node.id, node)
@ -41,17 +202,18 @@ export function buildAgvPerformanceData(rows, cols) {
} }
} }
data.get('wp_0_0').dt.center.push('wp_0_' + (cols - 1)) // 四个对角连线
data.get('wp_0_' + (cols - 1)).dt.center.push('wp_0_0') // data.get('wp_0_0').dt.center.push('wp_0_' + (cols - 1))
// data.get('wp_0_' + (cols - 1)).dt.center.push('wp_0_0')
data.get('wp_' + (rows - 1) + '_0').dt.center.push('wp_' + (rows - 1) + '_' + (cols - 1)) //
data.get('wp_' + (rows - 1) + '_' + (cols - 1)).dt.center.push('wp_' + (rows - 1) + '_0') // data.get('wp_' + (rows - 1) + '_0').dt.center.push('wp_' + (rows - 1) + '_' + (cols - 1))
// data.get('wp_' + (rows - 1) + '_' + (cols - 1)).dt.center.push('wp_' + (rows - 1) + '_0')
data.get('wp_' + (rows - 1) + '_0').dt.center.push('wp_0_0') //
data.get('wp_0_0').dt.center.push('wp_' + (rows - 1) + '_0') // data.get('wp_' + (rows - 1) + '_0').dt.center.push('wp_0_0')
// data.get('wp_0_0').dt.center.push('wp_' + (rows - 1) + '_0')
data.get('wp_' + (rows - 1) + '_' + (cols - 1)).dt.center.push('wp_0_' + (cols - 1)) //
data.get('wp_0_' + (cols - 1)).dt.center.push('wp_' + (rows - 1) + '_' + (cols - 1)) // data.get('wp_' + (rows - 1) + '_' + (cols - 1)).dt.center.push('wp_0_' + (cols - 1))
// data.get('wp_0_' + (cols - 1)).dt.center.push('wp_' + (rows - 1) + '_' + (cols - 1))
return Array.from(data.values()) return Array.from(data.values())
} }
@ -59,10 +221,10 @@ export function buildAgvPerformanceData(rows, cols) {
* *
* @returns {ItemJson} * @returns {ItemJson}
*/ */
export function createAgvNode(x, z) { export function createAgvNode(t, x, z) {
return { return {
id: 'wp_' + x + '_' + z, id: 'wp_' + x + '_' + z,
t: 'measure', t: t,
tf: [ tf: [
[x, 0.01, z], [x, 0.01, z],
[90, 0, 0], [90, 0, 0],

375
src/example/example1.js

@ -1,4 +1,4 @@
import { buildAgvPerformanceData } from '@/example/ExampleUtil.js' import { buildAgvPerformanceData, buildCenterLinkPerformanceData, buildPointPerformanceData, buildRackPerformanceData } from '@/example/ExampleUtil.js'
export default { export default {
project_uuid: 'example1', project_uuid: 'example1',
@ -97,31 +97,31 @@ export default {
id: '6UhIIw9QPYh6acwyW8OSGs', id: '6UhIIw9QPYh6acwyW8OSGs',
t: 'gstore', t: 'gstore',
v: true, v: true,
tf: [[-1, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[-1, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]],
dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } dt: { in: [], out: [], center: [] }
}, { }, {
id: '1D0WSRPj8JJJwIcmA0UMqG', id: '1D0WSRPj8JJJwIcmA0UMqG',
t: 'gstore', t: 'gstore',
v: true, v: true,
tf: [[0.75, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[0.75, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]],
dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } dt: { in: [], out: [], center: [] }
}, { }, {
id: 'gstore3', id: 'gstore333',
t: 'gstore', t: 'gstore',
v: true, v: true,
tf: [[3, 0.1, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[3, 0.1, 0.55], [0, 0, 0], [1.5, 0.1, 1.5]],
dt: { in: [], out: [], center: [], storeWidth: 1.4, storeDepth: 1.4 } dt: { in: [], out: [], center: [] }
}, { }, {
id: 'pallet1', id: 'pallet1',
t: 'pallet', t: 'pallet',
v: true, v: true,
tf: [[0.75, 0.075, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[0.75, 0.075, 0.55], [0, 0, 0], [1.0, 0.1, 1.2]],
dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 }
}, { }, {
id: 'pallet2', id: 'pallet2',
t: 'pallet', t: 'pallet',
v: true, v: true,
tf: [[3, 0.075, 0.55], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[3, 0.075, 0.55], [0, 0, 0], [1.0, 0.1, 1.2]],
dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 }
}, { }, {
id: 'ptr1', id: 'ptr1',
@ -139,13 +139,13 @@ export default {
id: 'pallet3', id: 'pallet3',
t: 'pallet', t: 'pallet',
v: true, v: true,
tf: [[3, 0.175, 1.88], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[3, 0.175, 1.88], [0, 0, 0], [1.5, 0.1, 1.2]],
dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 }
}, { }, {
id: 'pallet4', id: 'pallet4',
t: 'pallet', t: 'pallet',
v: true, v: true,
tf: [[0.75, 0.175, 3.5], [0, 0, 0], [1.5, 1.2, 0.1]], tf: [[0.75, 0.175, 3.5], [0, 0, 0], [1.5, 0.1, 1.2]],
dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 } dt: { in: [], out: [], center: [], palletWidth: 1, palletDepth: 1.2 }
} }
] ]
@ -157,7 +157,7 @@ export default {
id: 'rack1', id: 'rack1',
t: 'rack', t: 'rack',
v: true, v: true,
tf: [[4.196, 0.1, 5.882], [0, 270, 0], [1, 1, 1]], tf: [[4.196, 0.1, 5.882], [0, 90, 0], [1, 1, 1]],
dt: { dt: {
rackDepth: 1, rackDepth: 1,
rackWidth: 5.1, rackWidth: 5.1,
@ -323,8 +323,314 @@ export default {
}] }]
}, },
{ {
catalogCode: 'f3', t: 'floor',
items: [
{
id: 'carton1',
t: 'carton',
v: true,
tf: [[0, 0.1, 0], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: [], out: [], center: [] }
}
]
},
{
catalogCode: 'f4', t: 'floor',
items: [
// 双向连接路径
{
id: 'way11',
t: 'way',
v: true,
tf: [[2, 0.1, 1], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: ['way12'], out: ['way12'], center: [] }
},
{
id: 'way12',
t: 'way',
v: true,
tf: [[5, 0.1, 1], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: ['way11'], out: ['way11'], center: [] }
},
// 21->22 单向连接路径
{
id: 'way21',
t: 'way',
v: true,
tf: [[2, 0.1, 3], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: [], out: ['way22'], center: [] }
},
{
id: 'way22',
t: 'way',
v: true,
tf: [[5, 0.1, 3], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: ['way21'], out: [], center: [] }
},
// 32->31 单向连接路径
{
id: 'way31',
t: 'way',
v: true,
tf: [[2, 0.1, 5], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: ['way32'], out: [], center: [] }
},
{
id: 'way32',
t: 'way',
v: true,
tf: [[5, 0.1, 5], [0, 0, 0], [1.0, 1.0, 1.0]],
dt: { in: [], out: ['way31'], center: [] }
}
]
},
{
catalogCode: '__f1', t: 'floor', catalogCode: '__f1', t: 'floor',
items: buildAgvPerformanceData(100, 100) items: buildAgvPerformanceData('way', 100, 100)
},
{
catalogCode: '__f4', t: 'floor',
items: buildCenterLinkPerformanceData('measure', 100, 100)
},
{
catalogCode: '__f2', t: 'floor',
items: buildPointPerformanceData('pallet', 200, 500)
},
{
catalogCode: '__f3', t: 'floor',
items: buildRackPerformanceData(10, 200)
},
{
catalogCode: 'asrs1', t: 'floor',
items: [
{
id: 'asrs1_rack',
t: 'asrs_rack',
dt: {
bayCount: 15, // 列数
aisleCount: 2, // 巷道数
hideFloor: false, // 隐藏底板
bays: [ // 每列的配置
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 }
],
aisles: [ // 巷道的配置
{
rail: { width: 0.8 }, // 轨道, 主要是轨道宽度
railLeft: { // 轨道左侧
stores: [ // 轨道左侧货位
{ depth: 1.1 }, // 深位1的存储深度
{ depth: 1.1 } // 深位2的存储深度
]
},
railRight: { // 轨道左侧
stores: [ // 轨道左侧货位
{ depth: 1.1 }, // 深位1的存储深度
{ depth: 1.1 } // 深位2的存储深度
]
}
},
{
rail: { width: 0.8 },
railLeft: {
stores: [
{ depth: 1.1 },
{ depth: 1.1 }
]
},
railRight: {
stores: [
{ depth: 1.1 },
{ depth: 1.1 }
]
}
}
]
}
}
]
},
{
catalogCode: 'shuttle1', t: 'floor',
items: [
{
t: 'shuttle_rack', // 多穿库货架
dt: {
bayCount: 15, // 列数
aisleCount: 2, // 巷道数
hideFloor: false, // 隐藏底板
bayRail: [ // 横行巷道
{
afterBay: 2, // 横行巷道所在位置
railWidth: 1.4 // 横行巷道宽度
},
{ afterBay: 11, railWidth: 1.4 }
],
bays: [ // 每列的配置
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 },
{ bayWidth: 1.6 }
],
aisles: [ // 巷道的配置
{
rail: { railWidth: 0.8 }, // 轨道, 主要是轨道宽度
railLeft: { // 轨道左侧
stores: [ // 轨道左侧货位
{ depth: 1.1 }, // 深位1的存储深度
{ depth: 1.1 } // 深位2的存储深度
]
},
railRight: { // 轨道左侧
stores: [ // 轨道左侧货位
{ depth: 1.1 }, // 深位1的存储深度
{ depth: 1.1 } // 深位2的存储深度
]
}
},
{
rail: { railWidth: 0.8 },
railLeft: {
stores: [
{ depth: 1.1 },
{ depth: 1.1 }
]
},
railRight: {
stores: [
{ depth: 1.1 },
{ depth: 1.1 }
]
}
}
]
}
}
]
},
{
catalogCode: 'flash1', t: 'floor',
items: [
{
id: 'flash_rack1',
t: 'flash_rack',
dt: async () => import('./flash.js')
}
]
},
{
catalogCode: 'flash2', t: 'floor',
items: [
{
id: 'flash_rack1',
t: 'flash_rack',
dt: async () => import('./flash.js')
}
]
},
{
catalogCode: 'flash3', t: 'floor',
items: [
{
id: 'flash_rack1',
t: 'flash_rack',
dt: async () => import('./flash.js')
}
]
},
{
catalogCode: 'de1', t: 'side',
floorHeight: [ // 侧面视图的各层面高度
1.3, 1.3, 1.3, 1.3, 1.3,
1.3, 1.3, 1.3, 1.3, 1.3,
1.3
],
items: [
{
id: 'elevation1',
t: 'elevation',
dt: {
lift: { // 升降机
enable: true, // 是否启用
width: 1.2, // 升降机侧视图宽度
depth: 1.0 // 侧视图深度
},
liftLeft: {
width: 1.1, depth: 1.0, // 侧视图深度
items: [
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ enable: false }, // 升降机[0]层 左侧的第1个位置
{ enable: false },
{ enable: false }
]
},
liftRight: {
width: 1.1,
items: [
{ enable: false },
{ enable: false },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ pdId: true, pdOut: true, carArrive: true },
{ enable: false },
{ enable: false }
]
}
}
},
{
id: 'elevation2',
t: 'elevation',
dt: {}
},
{
id: 'elevation3',
t: 'elevation',
dt: {}
}
]
} }
], ],
elevator: [], // 电梯 elevator: [], // 电梯
@ -334,40 +640,45 @@ export default {
{ {
label: '仓库楼层', // 目录分组名 label: '仓库楼层', // 目录分组名
items: [ items: [
{ catalogCode: '__f1', label: '地下室 (-f1)' }, // 目录项 { catalogCode: '__f1', label: '路径压力测试 (-f1)' },
{ catalogCode: '__f2', label: '箱子压力测试 (-f2)' },
{ catalogCode: '__f3', label: '货架压力测试 (-f3)' },
{ catalogCode: '__f4', label: '标尺压力测试 (-f4)' },
{ catalogCode: 'f1', label: '一楼 (f1)' }, { catalogCode: 'f1', label: '一楼 (f1)' },
{ catalogCode: 'f2', label: '二楼 (f2)' }, { catalogCode: 'f2', label: '二楼 (f2)' },
{ catalogCode: 'f3', label: '三楼 (f3)' },
{ catalogCode: 'f4', label: 'AGV测试区 (f4)' },
{ catalogCode: 'OUT', label: '外场 (OUT)' }, { catalogCode: 'OUT', label: '外场 (OUT)' },
{ catalogCode: 'fe', label: '楼层电梯 (fe)' } { catalogCode: 'fe', label: '楼层电梯 (fe)' }
] ]
}, },
{ {
label: '密集库区域', label: '立体库',
items: [ items: [
{ catalogCode: 'm1', label: 'M1 (m1)' }, { catalogCode: 'asrs1', label: 'D1 (asrs1)' },
{ catalogCode: 'm2', label: 'M2 (m2)' }, { catalogCode: 'asrs2', label: 'D2 (asrs2)' },
{ catalogCode: 'm3', label: 'M3 (m3)' }, { catalogCode: 'asrs3', label: 'D3 (asrs3)' },
{ catalogCode: 'm4', label: 'M4 (m4)' }, { catalogCode: 'asrs4', label: 'D4 (asrs4)' },
{ catalogCode: 'me', label: '提升机 (me)' } { catalogCode: 'de1', label: '提升机 (de1)' }
] ]
}, },
{ {
label: '多穿库A', label: '密集库区域',
items: [ items: [
{ catalogCode: 'd1', label: 'D1 (d1)' }, { catalogCode: 'flash1', label: '1层 (flash1)' },
{ catalogCode: 'd2', label: 'D2 (d2)' }, { catalogCode: 'flash2', label: '2层 (flash2)' },
{ catalogCode: 'd3', label: 'D3 (d3)' }, { catalogCode: 'flash3', label: '3层 (flash3)' },
{ catalogCode: 'd4', label: 'D4 (d4)' }, { catalogCode: 'flash4', label: '4层 (flash4)' },
{ catalogCode: 'de1', label: '提升机 (de1)' } { catalogCode: 'me', label: '提升机 (me)' }
] ]
}, },
{ {
label: '多穿库B', label: '多穿库',
items: [ items: [
{ catalogCode: 'e1', label: 'E1 (e1)' }, { catalogCode: 'shuttle1', label: 'E1 (shuttle1)' },
{ catalogCode: 'e2', label: 'E2 (e2)' }, { catalogCode: 'shuttle2', label: 'E2 (shuttle2)' },
{ catalogCode: 'e3', label: 'E3 (e3)' }, { catalogCode: 'shuttle3', label: 'E3 (shuttle3)' },
{ catalogCode: 'e4', label: 'E4 (e4)' }, { catalogCode: 'shuttle4', label: 'E4 (shuttle4)' },
{ catalogCode: 'ee1', label: '提升机 (ee1)' } { catalogCode: 'ee1', label: '提升机 (ee1)' }
] ]
} }

47942
src/example/flash.js

File diff suppressed because it is too large

77
src/model/itemType/ItemType.ts

@ -1,77 +0,0 @@
import * as THREE from 'three'
import type WorldModel from '@/model/WorldModel.ts'
import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts'
import type { ItemJson } from '@/model/WorldModelType.ts'
import type Viewport from '@/designer/Viewport.ts'
import type Toolbox from '@/model/itemType/Toolbox.ts'
export default abstract class ItemType {
/**
*
*/
pointArray: THREE.Object3D[] = []
name: string
option: ItemTypeDefineOption
worldModel: WorldModel
public init(worldModel: WorldModel) {
this.worldModel = worldModel
// 初始化方法,子类可以重写
return Promise.resolve()
}
abstract loadFromJson(item: ItemJson): undefined | THREE.Object3D
abstract createToolbox(viewport: Viewport): Toolbox
abstract getDefaultScale(): THREE.Vector3
abstract getDefaultRotation(): THREE.Vector3
abstract createPoint(position: THREE.Vector3, itemJson: ItemJson): THREE.Object3D
beforeLoad(): THREE.Object3D[] {
return []
}
afterLoadPoint(point: THREE.Object3D): void {
}
afterUpdatePoint(point: THREE.Object3D, updatedLines: THREE.Object3D[]): void {
}
afterLoadGroup(group: THREE.Group): void {
}
afterLoadComplete(objects: THREE.Object3D[]): THREE.Object3D[] {
return []
}
/**
* scene
*/
afterAddScene(viewport: Viewport, scene: THREE.Scene, objects: THREE.Object3D[]): void {
}
/**
* viewport
*/
afterAddViewport(viewport: Viewport): void {
//viewport.dragControl.setDragObjects(this.pointArray, 'push')
const toolbox = this.createToolbox(viewport)
viewport.toolbox[this.name] = toolbox
}
/**
* EsDragControls
*/
abstract dragPointStart(viewport: Viewport, point: THREE.Mesh)
/**
* EsDragControls
*/
abstract dragPointComplete(viewport: Viewport)
}

106
src/model/itemType/ItemTypeDefine.ts

@ -1,106 +0,0 @@
import _ from 'lodash'
import type ItemType from '@/model/itemType/ItemType.ts'
import * as THREE from 'three'
const itemTypes: Record<string, ItemTypeDefineOption> = {}
window['itemTypes'] = itemTypes
/**
*
*/
export function defineItemType(option: ItemTypeDefineOption) {
itemTypes[option.name] = option
option.clazz.name = option.name
option.clazz.option = option
return option
}
export function getItemTypeByName(type: string): ItemTypeDefineOption {
const itemType = _.get(itemTypes, type)
if (!itemType) {
console.warn(`未找到物流单元类型定义: ${type}`)
return
}
return itemType
}
export function getAllItemTypes(): ItemTypeDefineOption[] {
return Object.values(itemTypes)
}
/**
* "点"
*/
export const BASIC_META_OF_POINT: ItemTypeMeta = [
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true },
{ field: 'name', editor: 'TextInput', label: '名称' },
{ field: 'userData.label', editor: 'TextInput', label: '标签' },
{ editor: 'Transform' },
{ field: 'color', editor: 'Color', label: '颜色' },
{ editor: '-' },
{ editor: 'IN_OUT_CENTER' }
]
/**
* "物流运输单元",
*/
export const BASIC_META_OF_POINT2: ItemTypeMeta = [
{ field: 'userData.selectable', editor: 'Switch', label: '可选中' },
{ field: 'userData.protected', editor: 'Switch', label: '受保护' },
{ field: 'visible', editor: 'Switch', label: '可见' }
]
/**
* "线"
*/
export const BASIC_META_OF_LINE: ItemTypeMeta = []
/**
* "线",
*/
export const BASIC_META_OF_LINE2: ItemTypeMeta = []
export type ActionType =
/**
* 线
*/
'ln' |
/**
*
*/
'pt' |
/**
*
*/
'fl' |
/**
*
*/
'gp'
export interface ItemTypeDefineOption {
name: string
label: string
actionType: ActionType
clazz: ItemType
getMeta(object: THREE.Object3D): ItemTypeMeta
}
/**
*
*/
export type ItemTypeMeta = ItemTypeMetaItem[]
export type ItemTypeMetaItem = {
field?: string
label?: string
labelWidth?: number
readonly?: boolean
editor: '-' |
'TextInput' | 'Number' | 'Switch' | 'Select' | 'ButtonGroup' |
'UUID' | 'Color' | 'Transform' | 'IN_OUT_CENTER'
}

256
src/model/itemType/ItemTypeLine.ts

@ -1,256 +0,0 @@
import * as THREE from 'three'
import ItemType from '@/model/itemType/ItemType.ts'
import type { ItemJson } from '@/model/WorldModelType.ts'
import type WorldModel from '@/model/WorldModel.ts'
import type Viewport from '@/designer/Viewport.ts'
import { findObject3DByCondition, findObject3DById } from '@/model/ModelUtils.ts'
let pmFn
/**
* ILineType 线
* 线, ,
*/
export default abstract class ItemTypeLine extends ItemType {
private relationPoints: THREE.Mesh[] = []
private dragViewport: Viewport | undefined
private dragPoint: THREE.Mesh | undefined
abstract createPointBasic(position: THREE.Vector3): THREE.Object3D
abstract createLineBasic(isTemplate?: boolean): THREE.Mesh
public init(worldModel: WorldModel) {
return super.init(worldModel).then(() => {
})
}
afterCreateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void {
}
afterUpdateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void {
}
createLine(viewport: Viewport, scene: THREE.Scene, startPoint: THREE.Object3D, endPoint: THREE.Object3D): THREE.Mesh {
const line = this.createLineBasic()
const geom = line.geometry
geom.setFromPoints([startPoint.position, endPoint.position])
if (!line.userData) {
line.userData = {}
}
line.userData.lineStartId = startPoint.uuid
line.userData.lineEndId = endPoint.uuid
if (startPoint.parent) {
startPoint.parent.add(line)
} else {
scene.add(line)
}
if (!startPoint.userData.lines) {
startPoint.userData.lines = []
}
startPoint.userData.lines.push(line.uuid)
if (!endPoint.userData.lines) {
endPoint.userData.lines = []
}
endPoint.userData.lines.push(line.uuid)
this.afterCreateLine(line, startPoint, endPoint)
//@ts-ignore
if (typeof line.computeLineDistances === 'function') {
// const canvas = viewport.renderer.domElement
//@ts-ignore
// this.lineMaterial.resolution.set(canvas.width, canvas.height)
//@ts-ignore
line.computeLineDistances()
}
return line
}
/**
* center 线
*/
afterAddScene(viewport: Viewport, scene: THREE.Scene, objects: THREE.Object3D[]) {
super.afterAddScene(viewport, scene, objects)
// 为所有的 pointArray 连接线
for (let i = 0; i < this.pointArray.length; i++) {
const startPoint = this.pointArray[i]
// 找到这个元素的 userData.center 数组
const linkArray: string[] = startPoint.userData.center || []
for (let j = 0; j < linkArray.length; j++) {
const linkId = linkArray[j]
// 在 pointArray 中查找对应的点
const endPoint = findObject3DById(scene, linkId)
if (!endPoint) {
console.warn('not found link point uuid=${}', linkId)
continue
}
const line = this.createLine(viewport, scene, startPoint, endPoint)
}
}
}
/**
* ,
*/
createPoint(position: THREE.Vector3, item: ItemJson): THREE.Object3D {
const point = this.createPointBasic(position)
if (item.name) {
point.name = item.name
}
point.uuid = item.id || THREE.MathUtils.generateUUID()
point.userData = _.cloneDeep(item.dt) || {}
_.extend(point.userData, {
type: item.t,
actionType: item.a,
label: item.l,
color: item.c,
selectable: true,
protected: false
})
point.rotation.set(
THREE.MathUtils.degToRad(item.tf[1][0]),
THREE.MathUtils.degToRad(item.tf[1][1]),
THREE.MathUtils.degToRad(item.tf[1][2])
)
point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2])
this.pointArray.push(point)
this.afterLoadPoint(point)
return point
}
/**
* Json item
*/
override loadFromJson(item: ItemJson): undefined | THREE.Object3D {
if (item.a === 'gp') {
// gp 是为了分组而存在的
const group = new THREE.Group()
group.name = item.name
group.uuid = item.id || THREE.MathUtils.generateUUID()
group.userData = _.cloneDeep(item.dt) || {}
group.userData.type = item.t
group.userData.actionType = item.a
group.userData.label = item.l
group.userData.color = item.c
this.afterLoadGroup(group)
return group
}
// 其他情况都是 ln
else if (item.a === 'ln') {
const position = new THREE.Vector3(
item.tf[0][0],
item.tf[0][1],
item.tf[0][2]
)
return this.createPoint(position, item)
}
console.error('ItemTypeLineBase.loadFromJson: Unsupported', item)
}
/**
* DragControls
*/
dragPointStart(viewport: Viewport, point: THREE.Mesh) {
console.log('dragPoint:', point)
this.dragViewport = viewport
this.dragPoint = point
const canvas = viewport.renderer.domElement
// 收集相关联的线段和标签
const relationPoints = new Set<THREE.Object3D>()
// 找到与 point 有关联的节点, 无论是 a->b 还是 b->a 都需要收集
findObject3DByCondition(viewport.scene, (targetObj: THREE.Object3D) => {
if (targetObj.uuid === point.uuid) {
return false
}
// 无论 a->b 还是 b->a 都需要收集
if (_.includes(targetObj.userData?.center, point.uuid) || _.includes(point.userData?.center, targetObj.uuid)) {
relationPoints.add(targetObj)
}
})
//@ts-ignore
this.relationPoints = Array.from(relationPoints)
// 监听move事件
pmFn = this.redrawMousemove.bind(this)
canvas.addEventListener('pointermove', pmFn)
}
/**
* DragControls
*/
redrawMousemove(e: MouseEvent) {
if (!this.dragViewport || !this.dragPoint) return
const updateLines: THREE.Object3D[] = []
// 更新所有相关线段
_.forEach(this.relationPoints, (targetPoint, idx) => {
if (targetPoint.uuid === this.dragPoint.uuid) {
return
}
// 判断谁是起点,谁是终点
let startPoint: THREE.Object3D
let endPoint: THREE.Object3D
if (_.includes(targetPoint.userData?.center, this.dragPoint.uuid)) {
startPoint = targetPoint
endPoint = this.dragPoint
} else {
startPoint = this.dragPoint
endPoint = targetPoint
}
// 找到 startPoint 与 this.dragPoint 之间的线段
const line = findObject3DByCondition(this.dragViewport.scene, (obj) => {
return obj.userData.lineStartId === startPoint.uuid && obj.userData.lineEndId === endPoint.uuid
})[0] as THREE.Mesh
if (!line) {
// line = this.createLine(this.dragViewport.scene, startPoint, endPoint)
console.warn('Line not found between points:', startPoint.uuid, endPoint.uuid)
debugger
}
// 更新线段几何体
const geometry = line.geometry as THREE.BufferGeometry
geometry.setFromPoints([startPoint.position, endPoint.position])
geometry.attributes.position.needsUpdate = true
this.afterUpdateLine(line, startPoint, endPoint)
updateLines.push(line)
})
this.afterUpdatePoint(this.dragPoint, updateLines)
}
/**
* DragControls
*/
dragPointComplete(viewport: Viewport) {
// 删除鼠标事件监听
viewport.renderer.domElement.removeEventListener('pointermove', pmFn)
pmFn = undefined
}
}

323
src/model/itemType/Toolbox.ts

@ -1,323 +0,0 @@
import * as THREE from 'three'
import type Viewport from '@/designer/Viewport.ts'
import type ItemType from '@/model/itemType/ItemType.ts'
import type { ItemJson } from '@/model/WorldModelType.ts'
let pdFn, pmFn, puFn
/**
*
*/
export default abstract class Toolbox {
/**
* Three.js .
* :
* - viewport.scene
* - viewport.renderer
* - viewport.controls
* - viewport.camera
* - viewport.raycaster 线
* - viewport.dragControl
* - viewport.measure
*/
viewport: Viewport
/**
*
*/
isCompleted = false
/**
*
*/
mouseMoved = false
/**
* , viewport.renderer.domElement
*/
canvas: HTMLCanvasElement
/**
*
*/
tempPointMarker?: THREE.Mesh
/**
*
*/
startPoint?: THREE.Object3D = undefined
/**
* 便
* @protected
*/
lastClickTime: number = 0
/**
*
*/
lastMovePosition: THREE.Vector3 | undefined = undefined
_itemType: any
get itemType(): ItemType {
return this._itemType
}
mode: string
static TMP_TYPE = '_TMP'
/**
*
*/
abstract getTempPointName(): string
addToScene(object: THREE.Object3D) {
this.viewport.scene.add(object)
}
deletePoint(point: THREE.Object3D): void {
const deletedPoints = _.remove(this.itemType.pointArray, (p) => p.uuid === point.uuid)
if (!deletedPoints || deletedPoints.length !== 1) {
console.warn('没有找到要删除的点:', point.uuid)
return
}
// 删除点
this.removeFromScene(point)
// 如果是起始点,则清除起始点
if (this.startPoint === point) {
this.startPoint = undefined
}
}
removeFromScene(object: THREE.Object3D) {
if (object?.parent) {
object.parent.remove(object)
} else {
this.viewport.scene.remove(object)
}
}
/**
*
*/
init(viewport: any, itemType: ItemType): void {
this.viewport = viewport
this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement
this._itemType = itemType
}
/**
* , ,
*/
start(startPoint?: THREE.Object3D) {
pdFn = this.mousedown.bind(this)
this.canvas.addEventListener('pointerdown', pdFn)
pmFn = this.mousemove.bind(this)
this.canvas.addEventListener('pointermove', pmFn)
puFn = this.mouseup.bind(this)
this.canvas.addEventListener('pointerup', puFn)
this.isCompleted = false
this.viewport.viewerDom.style.cursor = 'crosshair'
this.mode = this.viewport.state.cursorMode
this.startPoint = startPoint
system.msg('新建 [' + this.itemType.name + '] 模式')
}
/**
* , 线.
*/
stop(): void {
system.msg('退出新建模式')
const viewerDom = this.viewport.viewerDom
this.isCompleted = true
viewerDom.style.cursor = ''
this.canvas.removeEventListener('pointerdown', pdFn)
pdFn = undefined
this.canvas.removeEventListener('pointermove', pmFn)
pmFn = undefined
this.canvas.removeEventListener('pointerup', puFn)
puFn = undefined
// 清空所有临时点
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker)
this.tempPointMarker = undefined
}
/**
*
*/
createTempPointMarker(position?: THREE.Vector3): THREE.Mesh {
const p = position
const scale = this.itemType.getDefaultScale()
const rotation = this.itemType.getDefaultRotation()
const tt = new THREE.BoxGeometry(1, 1, 1)
const t2 = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
const obj = new THREE.Mesh(tt, t2)
obj.scale.set(scale.x, scale.y, scale.x)
obj.rotation.set(
THREE.MathUtils.degToRad(rotation.x),
THREE.MathUtils.degToRad(rotation.y),
THREE.MathUtils.degToRad(rotation.z)
)
if (p) {
obj.position.set(p.x, p.y, p.z)
}
obj.name = this.getTempPointName()
obj.userData = {
mode: this.mode,
type: Toolbox.TMP_TYPE
}
return obj
}
/**
*
*/
mousedown() {
this.mouseMoved = false
}
/**
* 线
*/
mousemove(e: MouseEvent): THREE.Vector3 | undefined {
if (this.isCompleted) return
this.mouseMoved = true
// 当前鼠标所在的点
const point = this.viewport.getClosestIntersection(e)
if (!point) {
return
}
// 如果按下了 shift 键,则 point 的位置必须位于 startPoint 正上下方,或者正左右方
if (this.startPoint && e.shiftKey) {
const startPos = this.startPoint.position
const dx = Math.abs(point.x - startPos.x)
const dz = Math.abs(point.z - startPos.z)
if (dx > dz) {
point.z = startPos.z
} else {
point.x = startPos.x
}
}
this.lastMovePosition = point
// 在鼠标移动时绘制临时点
if (this.tempPointMarker) {
this.tempPointMarker.position.set(point.x, point.y, point.z)
} else {
this.tempPointMarker = this.createTempPointMarker(point)
this.viewport.scene.add(this.tempPointMarker)
}
// this.viewport.dispatchSignal('sceneGraphChanged')
return point
}
/**
*
*/
mouseup(e: MouseEvent) {
// 如果mouseMoved是true,那么它可能在移动,而不是点击
if (!this.mouseMoved) {
if (e.button === 2) {
// 右键点击, 完成绘图操作
this.viewport.state.cursorMode = 'normal'
} else if (e.button === 0) {
// 左键点击, 添加点
this.onMouseClicked(e)
}
}
}
onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined {
if (this.isCompleted) {
return
}
// 获取鼠标点击位置的三维坐标
const point = this.lastMovePosition
if (!point) {
return
}
// 双击触发两次点击事件,我们需要避免这里的第二次点击
const now = Date.now()
if (this.lastClickTime && (now - this.lastClickTime < 50)) {
return
}
this.lastClickTime = now
const defaultScale = this.itemType.getDefaultScale()
const defaultRotation = this.itemType.getDefaultRotation()
// 添加正式点
const itemJson = {
t: this.itemType.name,
a: 'ln',
tf: [
[point.x, point.y, point.z],
[defaultRotation.x, defaultRotation.y, defaultRotation.z],
[defaultScale.x, defaultScale.y, defaultScale.z]
],
dt: {
in: [] as string[],
out: [] as string[],
center: [] as string[]
}
} as ItemJson
const marker = this.itemType.createPoint(point, itemJson)
this.addToScene(marker)
// 把点加入拖拽控制器
//this.viewport.dragControl.setDragObjects([marker], 'push')
if (this.startPoint) {
this.afterAddPoint(this.startPoint, marker)
}
// 更新起始点为新添加的点
this.startPoint = marker
// 删除临时线
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker)
this.tempPointMarker = undefined
return point
}
/**
*
*
*/
afterAddPoint(startPoint: THREE.Object3D, endPoint: THREE.Object3D): void {
// 默认实现不做任何操作
// 子类可以重写此方法来处理添加点后的逻辑
}
/**
* ,
*/
destory() {
}
}

149
src/model/itemType/ToolboxLine.ts

@ -1,149 +0,0 @@
import * as THREE from 'three'
import Toolbox from '@/model/itemType/Toolbox.ts'
import type ItemTypeLine from '@/model/itemType/ItemTypeLine.ts'
import { findObject3DById, getAllControlPoints } from '@/model/ModelUtils.ts'
import EventBus from '@/runtime/EventBus'
/**
* 线
*/
export default class ToolboxLine extends Toolbox {
/**
* 线
*/
tempLine?: THREE.Mesh
get itemType(): ItemTypeLine {
return this._itemType
}
getTempPointName(): string {
return '_measure_temp_point'
}
afterMoveTemplateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) {
}
afterDeleteLine(line: THREE.Object3D, point: THREE.Object3D) {
}
stop() {
super.stop()
this.tempLine && this.removeFromScene(this.tempLine)
this.tempLine = undefined
}
afterAddPoint(startPoint: THREE.Object3D, point: THREE.Object3D) {
// 如果起始点存在,则将新点添加到起始点的链接中
startPoint.userData.center.push(point.uuid)
this.itemType.createLine(this.viewport, this.viewport.scene, this.startPoint, point)
}
/**
*
*/
deletePoint(point: THREE.Object3D) {
const allPoints = getAllControlPoints()
const deletedPoints = _.remove(getAllControlPoints(), (p) => p.uuid === point.uuid)
if (!deletedPoints || deletedPoints.length !== 1) {
console.warn('没有找到要删除的点:', point.uuid)
return
}
if (this.viewport.state.selectedObject === point) {
// 如果当前选中的对象是要删除的点,则清除选中状态
this.viewport.state.selectedObject = undefined
EventBus.dispatch('objectChanged', {
viewport: this,
object: null
})
}
// 找出与这个点相关的其他点
allPoints.forEach(p => {
if (p.userData.center) {
_.remove(p.userData.center, i => i === point.uuid)
_.remove(p.userData.in, i => i === point.uuid)
_.remove(p.userData.out, i => i === point.uuid)
}
})
// 找出与点相关的所有线
_.forEach(point.userData.lines, (line) => {
const lineObject = findObject3DById(this.viewport.scene, line)
this.removeFromScene(lineObject)
this.afterDeleteLine(lineObject, point)
})
// 从场景中删除点
this.removeFromScene(point)
// 如果是起始点,则清除起始点
if (this.startPoint === point) {
this.startPoint = undefined
}
}
mousemove(e: MouseEvent): THREE.Vector3 | undefined {
const point = super.mousemove(e)
if (!point) {
return
}
// 移动时绘制临时线
if (this.startPoint) {
// 获取最后一个点
if (!this.tempLine) {
this.tempLine = this.itemType.createLineBasic(true)
this.viewport.scene.add(this.tempLine)
}
const p0 = this.startPoint.position
const line = this.tempLine
const geom = line.geometry
geom.setFromPoints([p0, point])
this.afterMoveTemplateLine(line, this.startPoint, this.tempPointMarker)
}
}
onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined {
const point = this.lastMovePosition
// 如果正式的点命中到同类型的节点上,则不添加新的点,只牵线到该点
if (point) {
let catchPoint = null
const vv = this.itemType.pointArray.some(p => {
if (p.position.x === point.x && p.position.z === point.z) {
catchPoint = p
return true
}
})
if (catchPoint) {
if (this.startPoint === catchPoint) {
// 自己连接自己,忽略
return
}
// 如果捕获到点,则将线条连接到该点
if (this.startPoint) {
this.afterAddPoint(this.startPoint, catchPoint)
}
this.tempLine && this.removeFromScene(this.tempLine)
this.tempLine = undefined
return
}
}
const r = super.onMouseClicked(e)
if (!r) {
return
}
this.tempLine && this.removeFromScene(this.tempLine)
this.tempLine = undefined
return r
}
}

7
src/model/itemType/line/LineMeta.ts

@ -1,7 +0,0 @@
import { defineItem } from '@/runtime/DefineItem.ts'
export default defineItem({
name: 'line',
label: '辅助线',
category: 'line'
})

124
src/model/itemType/line/conveyor/Conveyor.ts

@ -1,124 +0,0 @@
import * as THREE from 'three'
import ItemTypeLine from '@/model/itemType/ItemTypeLine.ts'
import WorldModel from '@/model/WorldModel.ts'
import Viewport from '@/designer/Viewport.ts'
import ToolboxLine from '@/model/itemType/ToolboxLine.ts'
import ConveyorToolbox from './ConveyorToolbox.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 _ from 'lodash'
export default class Conveyor extends ItemTypeLine {
defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25)
defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
pointMaterial!: THREE.Material
lineMaterial!: LineMaterial
lineMaterialTemplate!: LineMaterial
lineMaterialOutline!: LineMaterial
static POINT_NAME = 'conveyor_point'
static LINE_NAME = 'conveyor_line'
async init(worldModel: WorldModel): Promise<void> {
await super.init(worldModel)
try {
this.pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
this.lineMaterial = new LineMaterial({
alphaToCoverage: true,
side: THREE.DoubleSide,
color: 0x0088ff,
dashed: true,
resolution: new THREE.Vector2(1, 1), // 需要在afterAddScene中设置
dashOffset: 0,
linewidth: 0.8,
dashScale: 1,
dashSize: 0.2,
gapSize: 0.2,
worldUnits: true
// linewidth: 10,
// worldUnits: false,
// dashSize: 0.2,
// gapSize: 0.2,
// dashScale: 1
})
this.lineMaterialTemplate = new LineMaterial({
color: 0x0088ff,
linewidth: 0.8,
worldUnits: true,
opacity: 0.5,
transparent: true,
alphaToCoverage: true,
depthWrite: false, // 避免深度冲突
blending: THREE.NormalBlending
})
window['lineMaterial'] = this.lineMaterial // 方便调试查看
} catch (error) {
system.showErrorDialog('Texture loading failed:' + error)
}
}
afterAddScene(viewport: Viewport, scene: THREE.Scene, objects: THREE.Object3D[]) {
super.afterAddScene(viewport, scene, objects)
_.defer(() => {
const canvas = viewport.renderer.domElement
// this.lineMaterial.resolution.set(canvas.width, canvas.height)
})
}
getDefaultScale(): THREE.Vector3 {
return this.defaultScale
}
getDefaultRotation(): THREE.Vector3 {
return this.defaultRotation
}
createToolbox(viewport: Viewport): ToolboxLine {
const toolbox = new ConveyorToolbox()
toolbox.init(viewport, this)
//@ts-ignore
return toolbox
}
/**
*
*/
createPointBasic(position?: THREE.Vector3): THREE.Object3D {
const p = position
const scale = 0.25
const tt = new THREE.BoxGeometry(1, 1, 1)
const obj = new THREE.Mesh(tt, this.pointMaterial)
obj.scale.set(scale, 0.1, scale)
if (p) {
obj.position.set(p.x, p.y, p.z)
}
obj.name = Conveyor.POINT_NAME
return obj
}
/**
* 线
*/
createLineBasic(isTemplate?: boolean): THREE.Mesh {
const geom = new LineGeometry()
let obj: THREE.Mesh
if (isTemplate) {
obj = new Line2(geom, this.lineMaterialTemplate)
} else {
obj = new Line2(geom, this.lineMaterial)
}
obj.frustumCulled = false
obj.name = Conveyor.LINE_NAME
obj.uuid = THREE.MathUtils.generateUUID()
return obj
}
}

9
src/model/itemType/line/conveyor/ConveyorMeta.ts

@ -1,9 +0,0 @@
import { defineItemType } from '@/model/itemType/ItemTypeDefine.ts'
import Conveyor from './Conveyor.ts'
export default defineItemType({
name: 'conveyor',
label: '输送线',
actionType: 'ln',
clazz: new Conveyor()
})

16
src/model/itemType/line/conveyor/ConveyorToolbox.ts

@ -1,16 +0,0 @@
import ToolboxLine from '@/model/itemType/ToolboxLine.ts'
import type Conveyor from './Conveyor.ts'
/**
*
*/
export default class ConveyorToolbox extends ToolboxLine {
constructor() {
super()
}
get conveyor(): Conveyor {
return this._itemType
}
}

202
src/model/itemType/measure/Measure.ts

@ -1,202 +0,0 @@
import * as THREE from 'three'
import { Material } from 'three'
import ItemTypeLine from '@/model/itemType/ItemTypeLine.ts'
import WorldModel from '@/model/WorldModel.ts'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { numberToString } from '@/utils/webutils.ts'
import { findObject3DById } from '@/model/ModelUtils.ts'
import Viewport from '@/designer/Viewport.ts'
import ToolboxLine from '@/model/itemType/ToolboxLine.ts'
import MeasureToolbox from '@/model/itemType/measure/MeasureToolbox.ts'
import Toolbox from '@/model/itemType/Toolbox.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 Measure extends ItemTypeLine {
/**
* , 线. 线
*/
group: THREE.Group
defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25)
defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
pointMaterial!: Material
lineMaterial!: LineMaterial
static GROUP_NAME = 'measure-group'
static LABEL_NAME = 'measure_label'
static POINT_NAME = 'measure_point'
static LINE_NAME = 'measure_line'
override init(worldModel: WorldModel): Promise<void> {
super.init(worldModel)
// this.lineMaterial = new THREE.LineBasicMaterial({
// color: 0xE63C17,
// linewidth: 2,
// opacity: 0.9,
// transparent: true,
// side: THREE.DoubleSide,
// depthWrite: false,
// depthTest: false
// })
this.lineMaterial = new LineMaterial({
color: 0xE63C17, // 主颜色
linewidth: 2, // 实际可用的线宽
vertexColors: true, // 启用顶点颜色
dashed: false,
alphaToCoverage: true
})
this.pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
return Promise.resolve()
}
getDefaultScale(): THREE.Vector3 {
return this.defaultScale
}
getDefaultRotation(): THREE.Vector3 {
return this.defaultRotation
}
beforeLoad(): THREE.Object3D[] {
this.group = null
return []
}
createToolbox(viewport: Viewport): ToolboxLine {
const toolbox = new MeasureToolbox(this.group)
toolbox.init(viewport, this)
//@ts-ignore
return toolbox
}
afterLoadGroup(group: THREE.Group) {
super.afterLoadGroup(group)
if (this.group) {
// 如果已经有 group,则忽略
return
}
this.group = group
}
afterLoadComplete(objects: THREE.Object3D[]): THREE.Object3D[] {
// 如果没有 group,则创建一个新的 group
if (!this.group) {
this.group = new THREE.Group()
this.group.name = Measure.GROUP_NAME
}
return [this.group]
}
/**
*
*/
createPointBasic(position?: THREE.Vector3): THREE.Object3D {
const p = position
const scale = 0.25
const tt = new THREE.BoxGeometry(1, 1, 1)
const obj = new THREE.Mesh(tt, this.pointMaterial)
obj.scale.set(scale, 0.1, scale)
if (p) {
obj.position.set(p.x, p.y, p.z)
}
obj.name = Measure.POINT_NAME
return obj
}
/**
* 线
*/
createLineBasic(): Line2 {
const geom = new LineGeometry()
const obj = new Line2(geom, this.lineMaterial)
obj.frustumCulled = false
obj.name = Measure.LINE_NAME
obj.uuid = THREE.MathUtils.generateUUID()
return obj
}
// 创建完线之后,创建 label
afterCreateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) {
super.afterCreateLine(line, startPoint, endPoint)
if (!startPoint.userData.center) {
startPoint.userData.center = []
}
if (!startPoint.userData.center.includes(endPoint.uuid)) {
startPoint.userData.center.push(endPoint.uuid)
}
const p0 = startPoint.position
const p1 = endPoint.position
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)} m`
const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5)
const labelObj = this.createLabel(label)
labelObj.name = Measure.LABEL_NAME
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
line.userData.labelId = labelObj.uuid
this.group.add(labelObj)
}
afterUpdateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) {
super.afterUpdateLine(line, startPoint, endPoint)
const p0 = startPoint.position
const p1 = endPoint.position
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)}`
const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5)
const labelObj: CSS2DObject = findObject3DById(this.group, line.userData.labelId) as CSS2DObject
if (labelObj) {
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
} else {
// 如果没有找到,则创建新的标签
const newLabelObj = this.createLabel(label)
newLabelObj.position.set(position.x, position.y, position.z)
newLabelObj.element.innerHTML = label
line.userData.labelId = labelObj.uuid
this.group.add(newLabelObj)
}
}
/**
*
*/
createLabel(text: string): CSS2DObject {
const div = document.createElement('div')
div.className = 'css2dObjectLabel'
div.innerHTML = text
div.style.padding = '5px 8px'
div.style.color = '#fff'
div.style.fontSize = '14px'
div.style.position = 'absolute'
div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)'
div.style.borderRadius = '12px'
div.style.top = '0px'
div.style.left = '0px'
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
const obj = new CSS2DObject(div)
obj.name = MeasureToolbox.TMP_LABEL_NAME
obj.userData = {
type: Toolbox.TMP_TYPE
}
return obj
}
}

36
src/model/itemType/measure/MeasureMeta.ts

@ -1,36 +0,0 @@
import * as THREE from 'three'
import Measure from '@/model/itemType/measure/Measure.ts'
import {
defineItemType,
BASIC_META_OF_POINT,
BASIC_META_OF_POINT2,
BASIC_META_OF_LINE,
BASIC_META_OF_LINE2,
type ItemTypeMeta
} from '@/model/itemType/ItemTypeDefine.ts'
export default defineItemType({
name: 'measure',
label: '测量距离',
actionType: 'ln',
clazz: new Measure(),
/**
*
*/
getMeta(object: THREE.Object3D): ItemTypeMeta {
if (object.name === Measure.LINE_NAME) {
return [
...BASIC_META_OF_LINE,
...BASIC_META_OF_LINE2
]
} else if (object.name === Measure.POINT_NAME) {
return [
...BASIC_META_OF_POINT,
...BASIC_META_OF_POINT2
]
}
return []
}
})

94
src/model/itemType/measure/MeasureToolbox.ts

@ -1,94 +0,0 @@
import * as THREE from 'three'
import ToolboxLine from '@/model/itemType/ToolboxLine.ts'
import { numberToString } from '@/utils/webutils.ts'
import type Measure from './Measure.ts'
import type { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import Toolbox from '@/model/itemType/Toolbox.ts'
import { findObject3DById } from '@/model/ModelUtils.ts'
/**
*
*/
export default class MeasureToolbox extends ToolboxLine {
group: THREE.Group
/**
* ,
*/
tempLabel?: CSS2DObject
static TMP_LABEL_NAME = '_measure_temp_label'
constructor(group: THREE.Group) {
super()
this.group = group
}
stop() {
super.stop()
// 清除临时标签
this.tempLabel && this.removeFromScene(this.tempLabel)
this.tempLabel = undefined
}
onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined {
const r = super.onMouseClicked(e)
if (!r) {
return
}
this.tempLabel && this.removeFromScene(this.tempLabel)
this.tempLabel = undefined
}
get measure(): Measure {
return this._itemType
}
addToScene(object: THREE.Object3D) {
this.measure.group.add(object)
}
afterMoveTemplateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) {
super.afterMoveTemplateLine(line, startPoint, endPoint)
const p0 = startPoint.position
const point = endPoint.position
const dist = p0.distanceTo(point)
const label = `${numberToString(dist)} m`
const position = new THREE.Vector3().addVectors(p0, point).multiplyScalar(0.5)
this.addOrUpdateTempLabel(line, label, position)
}
afterDeleteLine(line: THREE.Object3D, point: THREE.Object3D) {
super.afterDeleteLine(line, point)
// 删除临时标签
if (line?.userData?.labelId) {
const label = findObject3DById(this.measure.group, line.userData.labelId)
this.removeFromScene(label)
}
}
/**
*
*/
addOrUpdateTempLabel(line: THREE.Mesh, label: string, position: THREE.Vector3) {
if (!this.tempLabel) {
this.tempLabel = this.measure.createLabel(label)
this.tempLabel.name = MeasureToolbox.TMP_LABEL_NAME
this.tempLabel.uuid = THREE.MathUtils.generateUUID()
this.tempLabel.userData = {
mode: this.mode,
type: Toolbox.TMP_TYPE
}
line.userData.labelId = this.tempLabel.uuid
this.addToScene(this.tempLabel)
}
this.tempLabel.position.set(position.x, position.y, position.z)
this.tempLabel.element.innerHTML = label
}
}

7
src/model/itemType/point/PointMeta.ts

@ -1,7 +0,0 @@
import { defineItem } from '@/runtime/DefineItem.ts'
export default defineItem({
name: 'point',
label: '辅助点',
category: 'point'
})

7
src/model/itemType/store/QueueMeta.ts

@ -1,7 +0,0 @@
import { defineItem } from '@/runtime/DefineItem.ts'
export default defineItem({
name: 'queue',
label: '暂存区',
category: 'store'
})

5
src/modules/carton/CartonEntity.ts

@ -0,0 +1,5 @@
import BaseEntity from '@/core/base/BaseItemEntity.ts'
export default class PalletEntity extends BaseEntity {
}

22
src/modules/carton/CartonInteraction.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
}
}

20
src/modules/carton/CartonPropertySetter.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;

87
src/modules/carton/CartonRenderer.ts

@ -0,0 +1,87 @@
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_GLB_File from '@/assets/Models/carton.glb?url'
import MODULE_3DS_TEX from '@/assets/Models/carton.jpg?url'
import { load3DModule, loadByUrl, loadGlbModule, loadTexture } from '@/core/ModelUtils.ts'
/**
*
*/
export default class PalletRenderer extends BaseRenderer {
static POINT_NAME = 'carton_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: 0xc29a70
}
cartonGeometry: THREE.BufferGeometry
cartonMaterial: THREE.Material
init() {
return Promise.all([
super.init(),
loadGlbModule(MODULE_GLB_File),
loadTexture(MODULE_3DS_TEX)
]).then(([_, glbGroup, cartonTexture]) => {
const mesh = glbGroup.children[0] as THREE.Mesh
this.cartonGeometry = mesh.geometry
this.cartonMaterial = new THREE.MeshPhongMaterial({ color: 0xc29a70 }) // mesh.material as THREE.Material
this.cartonGeometry.scale(0.01, 0.01, 0.01)
// this.cartonGeometry.rotateX(-Math.PI / 2)
this.cartonGeometry.center()
this.cartonGeometry.translate(0, 0.3, 0)
cartonTexture.flipY = true
cartonTexture.wrapS = THREE.RepeatWrapping
cartonTexture.wrapT = THREE.RepeatWrapping
cartonTexture.repeat.set(1, -1)
cartonTexture.offset.y = 1
//@ts-ignore
this.cartonMaterial.map = cartonTexture
//@ts-ignore
this.cartonMaterial.color.set(this.defaultUserData.color)
this.cartonMaterial.needsUpdate = true
})
}
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.cartonGeometry,
this.cartonMaterial,
true, true)
)
}
dispose() {
super.dispose()
if (this.cartonGeometry) {
this.cartonGeometry.dispose()
this.cartonGeometry = undefined
}
if (this.cartonMaterial) {
this.cartonMaterial.dispose()
this.cartonMaterial = undefined
}
}
}

15
src/modules/carton/index.ts

@ -0,0 +1,15 @@
import { defineModule } from '@/core/manager/ModuleManager.ts'
import CartonRenderer from './CartonRenderer.ts'
import CartonEntity from './CartonEntity.ts'
import CartonInteraction from './CartonInteraction.ts'
import propertySetter from "./CartonPropertySetter.ts";
export const ITEM_TYPE_NAME = 'carton'
export default defineModule({
name: ITEM_TYPE_NAME,
renderer: new CartonRenderer(ITEM_TYPE_NAME),
interaction: new CartonInteraction(ITEM_TYPE_NAME),
setter: propertySetter,
entity: CartonEntity,
})

26
src/modules/gstore/GstorePropertySetter.ts

@ -1,12 +1,20 @@
import type { PropertySetter } from "@/core/base/PropertyTypes.ts"; import type { PropertySetter } from '@/core/base/PropertyTypes.ts'
import { basicFieldsSetter } from "@/editor/widgets/property/PropertyPanelConstant.ts"; import { basicFieldsSetter } from '@/editor/widgets/property/PropertyPanelConstant.ts'
const propertySetter: PropertySetter = { const propertySetter: PropertySetter = {
flatten: { flatten: {
fields: [ fields: [
...basicFieldsSetter, ...basicFieldsSetter,
], {
}, dataPath: 'dt.strokeColor', label: '边线颜色', input: 'ColorPicker',
}; inputProps: {}
},
{
dataPath: 'dt.strokeWidth', label: '边线宽度', input: 'InputNumber',
inputProps: {}
}
]
}
}
export default propertySetter; export default propertySetter

160
src/modules/gstore/GstoreRenderer.ts

@ -1,18 +1,18 @@
import * as THREE from 'three' import * as THREE from 'three'
import BaseRenderer from '@/core/base/BaseRenderer.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 Constract from '@/core/Constract.ts'
import { type Object3DLike } from '@/types/ModelTypes.ts'
import InstancePointManager from '@/core/manager/InstancePointManager.ts'
import LineSegmentManager from '@/core/manager/LineSegmentManager.ts'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
/** /**
* *
* 使 InstanceMesh
*/ */
export default class GstoreRenderer extends BaseRenderer { export default class GstoreRenderer extends BaseRenderer {
static POINT_NAME = 'ground_store' static POINT_NAME = 'ground_store'
pointMaterial: THREE.Material
/** /**
* , * ,
*/ */
@ -20,31 +20,83 @@ export default class GstoreRenderer extends BaseRenderer {
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1.5, 1.2, 0.1) readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1.5, 1.2, 0.1)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
readonly defaultLineWidth: number = 0.05 readonly defaultLineWidth: number = 0.05
readonly defaultPointOption = {
constructor(itemTypeName: string) { weight: 0.1,
super(itemTypeName) width: 1.5,
depth: 1.3,
strokeColor: 0x038217,
strokeWidth: 0.08
} }
/** pointGeometry = new THREE.PlaneGeometry(
* 使 storeWidth/storeDepth, TF无效 1, 1
*/ ).rotateX(-Math.PI / 2)
override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: THREE.Object3D) { pointMaterial: THREE.Material = new THREE.MeshBasicMaterial({
super.afterCreateOrUpdatePoint(item, option, object) color: 0xffffff,
transparent: true,
opacity: 0.5
})
strokeMaterial = new LineMaterial({
color: this.defaultPointOption.strokeColor,
transparent: false,
linewidth: this.defaultPointOption.strokeWidth,
gapSize: 0,
worldUnits: true
})
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.pointGeometry,
this.pointMaterial,
true, true)
)
}
const group = object get lineSegmentManager(): LineSegmentManager {
group.position.y = this.defulePositionY if (!this.tempViewport) {
group.scale.set(item.dt.storeWidth, this.defaultScale.y, item.dt.storeDepth) throw new Error('tempViewport is not set.')
group.rotation.set( }
THREE.MathUtils.degToRad(item.tf[1][0]), return this.tempViewport.getOrCreateLineManager(this.itemTypeName, () =>
THREE.MathUtils.degToRad(item.tf[1][1]), // 构建 LineSegment.points 代理对象
THREE.MathUtils.degToRad(item.tf[1][2]) LineSegmentManager.create(this.itemTypeName,
this.tempViewport,
this.strokeMaterial)
) )
}
createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike {
return this.pointManager.createPoint(item)
}
// const planeMesh = group.children[0] as THREE.Mesh afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: Object3DLike) {
// planeMesh.geometry.dispose() super.afterCreateOrUpdatePoint(item, option, object)
//
// const newGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth) // 画边线
// planeMesh.geometry = newGeometry const center = [item.tf[0][0], item.tf[0][2]]
const h = (item.tf[0][1] || this.defulePositionY) + 0.01
const widthHalf = item.tf[2][0] / 2
const depthHalf = item.tf[2][2] / 2
const lwHalf = (item.dt.strokeWidth || this.defaultPointOption.strokeWidth) / 2
// 左上角
const p1 = [center[0] - widthHalf + lwHalf, h, center[1] - depthHalf + lwHalf]
// 右上角
const p2 = [center[0] + widthHalf - lwHalf, h, center[1] - depthHalf + lwHalf]
// 右下角
const p3 = [center[0] + widthHalf - lwHalf, h, center[1] + depthHalf - lwHalf]
// 左下角
const p4 = [center[0] - widthHalf + lwHalf, h, center[1] + depthHalf - lwHalf]
this.lineSegmentManager.createLine(item.id + '_l1', p1, p2, this.strokeMaterial.color)
this.lineSegmentManager.createLine(item.id + '_l2', p2, p3, this.strokeMaterial.color)
this.lineSegmentManager.createLine(item.id + '_l3', p3, p4, this.strokeMaterial.color)
this.lineSegmentManager.createLine(item.id + '_l4', p4, p1, this.strokeMaterial.color)
} }
createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D {
@ -55,62 +107,10 @@ export default class GstoreRenderer extends BaseRenderer {
throw new Error('not allow store line.') 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) {
system.showErrorDialog('地堆货位缺少 storeWidth 或 storeDepth 属性')
return null
}
const group = new THREE.Group()
group.name = GstoreRenderer.POINT_NAME
// 绘制背景矩形框
const planeGeometry = new THREE.PlaneGeometry(1, 1)
planeGeometry.rotateX(Math.PI / 2)
const planeMaterial = new THREE.MeshBasicMaterial({
color: '#dee8ee',
transparent: true, // 启用透明
opacity: 0.5, // 50%透明度
depthWrite: false, // 防止深度冲突
side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
})
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
group.add(planeMesh)
// 绘制边框
const lineXLen = item.dt.storeWidth - this.defaultLineWidth
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)
])
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)
group.add(line as THREE.Object3D)
// 设置位置
group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2])
return group
}
dispose() { dispose() {
super.dispose() super.dispose()
this.pointMaterial?.dispose() this.pointGeometry.dispose()
this.pointMaterial.dispose()
this.strokeMaterial.dispose()
} }
} }

136
src/modules/measure/MeasureRenderer.ts

@ -9,17 +9,11 @@ import type { Object3DLike } from '@/types/ModelTypes.ts'
/** /**
* *
* InstanceMesh LineSegment
*/ */
export default class MeasureRenderer extends BaseRenderer { export default class MeasureRenderer extends BaseRenderer {
/**
* , 线. 线
*/
group: THREE.Group
static GROUP_NAME = 'measure_group'
static LABEL_NAME = 'measure_label' static LABEL_NAME = 'measure_label'
static POINT_NAME = 'measure_point' static POINT_NAME = 'measure_point'
static LINE_NAME = 'measure_line'
public useHtmlLabel = false public useHtmlLabel = false
@ -40,14 +34,6 @@ export default class MeasureRenderer extends BaseRenderer {
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.25, 0.1) readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.25, 0.1)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0)
createPointManager(): InstancePointManager {
return InstancePointManager.create(this.itemTypeName, this.tempViewport, this.pointGeometry, this.pointMaterial, Constract.MAX_MEASURE_INSTANCES)
}
createLineSegmentManager(): LineSegmentManager {
return LineSegmentManager.create(this.itemTypeName, this.tempViewport, this.lineMaterial)
}
/** /**
* 使, * 使,
*/ */
@ -64,31 +50,6 @@ export default class MeasureRenderer extends BaseRenderer {
) )
} }
// createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D {
// const geom = new LineGeometry()
// const obj = new Line2(geom, this.lineMaterial)
// obj.frustumCulled = false
// obj.name = MeasureRenderer.LINE_NAME
// obj.uuid = getLineId(start.id, end.id, type)
//
// return obj
// }
//
// createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D {
// // const tt = new THREE.BoxGeometry(1, 1, 1)
// // const obj = new THREE.Mesh(tt, this.movelinePoint)
// // obj.name = MeasureRenderer.POINT_NAME
// // obj.uuid = item.id
// //
// // return [obj]
//
// // 创建平面几何体
// // const obj = new THREE.Mesh(this.pointGeometry,this.pointMaterial)
// // obj.name = MeasureRenderer.POINT_NAME
// // return obj
// }
createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike {
// 不允许改变高度/角度/大小 // 不允许改变高度/角度/大小
item.tf = [ item.tf = [
@ -104,33 +65,6 @@ export default class MeasureRenderer extends BaseRenderer {
return this.lineSegmentManager.createLine(lineId, start.tf[0], end.tf[0], this.lineMaterial.color) return this.lineSegmentManager.createLine(lineId, start.tf[0], end.tf[0], this.lineMaterial.color)
} }
// createLine(start: ItemJson, end: ItemJson, type: LinkType): Object3DLike {
// const lineId = getLineId(start.id, end.id, type)
// return this.lineSegmentManager.createLine(lineId, start.tf[0], end.tf[0], this.lineMaterial.color, {
// lineId: lineId,
// startId: start.id,
// endId: end.id
// })
// }
// appendToScene(...objects: THREE.Object3D[]) {
// if (!this.group || this.group.parent !== this.tempViewport.scene.scene) {
// if (this.group && this.group.parent !== this.tempViewport.scene.scene) {
// // 幻影加载问题
// this.group.parent.removeFromParent()
// }
//
// this.group = new THREE.Group()
// this.group.name = MeasureRenderer.GROUP_NAME
// this.tempViewport?.scene.add(this.group)
// }
//
// const dragObjects = objects.filter(obj => !!obj.userData.draggable)
// //this.tempViewport.dragControl.setDragObjects(dragObjects, 'push')
//
// this.group.add(...objects)
// }
afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) { afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) {
super.afterCreateOrUpdateLine(start, end, type, option, object) super.afterCreateOrUpdateLine(start, end, type, option, object)
@ -142,54 +76,42 @@ export default class MeasureRenderer extends BaseRenderer {
fontSize: 0.4, fontSize: 0.4,
color: '#333333' color: '#333333'
}) })
// const p0 = startPoint.position
// const p1 = endPoint.position
//
// const dist = p0.distanceTo(p1)
// const label = numberToString(dist) + ' m'
//
// const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5)
// let labelObj: Text | CSS2DObject | undefined = object.userData.labelObj
// if (!labelObj || !labelObj.parent) {
// labelObj = this.createLabel(label)
// this.appendToScene(labelObj)
// object.userData.labelObj = labelObj
// }
//
// labelObj.position.set(position.x, position.y + 0.3, position.z - 0.2)
//
// if (this.useHtmlLabel) {
// labelObj.element.innerHTML = label
//
// } else {
// // 让文本朝向摄像机
// labelObj.quaternion.copy(this.tempViewport.camera.quaternion)
// labelObj.text = label
// labelObj.sync()
// }
} }
afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) { afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption) {
super.afterDeleteLine(start, end, type, option, object) const lineId = getLineId(start.id, end.id, type)
const object = this.tempViewport.entityManager.findLineObjectById(lineId)
this.tempViewport.labelManager.removeLabel(object) this.tempViewport.labelManager.removeLabel(object)
// 删除标签
// const labelObj = object.userData?.labelObj
// if (labelObj && labelObj.parent) {
// labelObj.parent.remove(labelObj)
// }
} }
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.pointGeometry,
this.pointMaterial,
true, true)
)
}
dispose() { get lineSegmentManager(): LineSegmentManager {
super.dispose() if (!this.tempViewport) {
throw new Error('tempViewport is not set.')
if (this.group && this.group.parent) {
this.group.parent.remove(this.group)
} }
this.group = undefined return this.tempViewport.getOrCreateLineManager(this.itemTypeName, () =>
// 构建 LineSegment.points 代理对象
LineSegmentManager.create(this.itemTypeName,
this.tempViewport,
this.lineMaterial)
)
}
dispose() {
super.dispose()
this.pointMaterial?.dispose() this.pointMaterial?.dispose()
this.lineMaterial?.dispose() this.lineMaterial?.dispose()
} }

129
src/modules/pallet/PalletRenderer.ts

@ -1,18 +1,17 @@
import * as THREE from 'three' import * as THREE from 'three'
import BaseRenderer from '@/core/base/BaseRenderer.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 { decimalSumBy } from '@/core/ModelUtils'
import Constract from '@/core/Constract.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/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 { export default class PalletRenderer extends BaseRenderer {
static POINT_NAME = 'pallet' static POINT_NAME = 'pallet_point'
pointMaterial: THREE.Material
/** /**
* , * ,
@ -20,91 +19,57 @@ export default class PalletRenderer extends BaseRenderer {
readonly defulePositionY: number = Constract.HEIGHT_WAY readonly defulePositionY: number = Constract.HEIGHT_WAY
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1) readonly defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
readonly defaultLineWidth: number = 0.15 readonly defaultUserData = {
color: 0xcfc195
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])
)
} }
palletGeometry: THREE.BufferGeometry
createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { palletMaterial: THREE.Material
throw new Error('not allow store line.')
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
//@ts-ignore
this.palletMaterial.color.set(this.defaultUserData.color)
//@ts-ignore
this.palletMaterial.map = queueTexture
})
} }
updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) { createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike {
throw new Error('not allow store line.') return this.pointManager.createPoint(item)
} }
get pointManager(): InstancePointManager {
createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D { if (!this.tempViewport) {
// 创建平面几何体 throw new Error('tempViewport is not set.')
if (!item.dt.palletWidth || !item.dt.palletDepth) {
system.showErrorDialog('field palletWidth / palletDepth is null!')
return null
} }
return this.tempViewport.getOrCreatePointManager(this.itemTypeName, () =>
const group = new THREE.Group() // 构建 InstanceMesh 代理对象
group.name = PalletRenderer.POINT_NAME InstancePointManager.create(this.itemTypeName,
this.tempViewport,
// 绘制背景矩形框 this.palletGeometry,
const planeGeometry = new THREE.PlaneGeometry(item.dt.palletWidth - this.defaultLineWidth, item.dt.palletDepth - this.defaultLineWidth) this.palletMaterial,
planeGeometry.rotateX(Math.PI / 2) true, true)
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
} }
dispose() { dispose() {
super.dispose() super.dispose()
this.pointMaterial?.dispose() if (this.palletGeometry) {
} this.palletGeometry.dispose()
this.palletGeometry = undefined
createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D { }
throw new Error('Pallet createPointBasic not allow!') if (this.palletMaterial) {
this.palletMaterial.dispose()
this.palletMaterial = undefined
}
} }
} }

607
src/modules/rack/RackRenderer.ts

@ -1,10 +1,14 @@
import * as THREE from 'three' import * as THREE from 'three'
import { BufferGeometry } from 'three'
import BaseRenderer from '@/core/base/BaseRenderer.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 { decimalSumBy } from '@/core/ModelUtils' import { decimalSumBy } from '@/core/ModelUtils'
import Constract from '@/core/Constract.ts' import Constract from '@/core/Constract.ts'
import Plastic_Rough_JPG from '@/assets/Models/Plastic_Rough.jpg'
import storageBar_PNG from '@/assets/Models/storageBar.png'
import { Material } from 'three/src/materials/Material'
import { InstancedMesh } from 'three/src/objects/InstancedMesh'
//@ts-ignore
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js'
/** /**
* *
@ -71,59 +75,73 @@ export default class RackRenderer extends BaseRenderer {
heights.push(bayHeight) heights.push(bayHeight)
} }
const rackHeight = _.max(heights) const rackHeight = _.max(heights)
// 绘制背景矩形框 // // 绘制背景矩形框
const planeGeometry = new THREE.PlaneGeometry(rackWidth, item.dt.rackDepth) // const planeGeometry = new THREE.PlaneGeometry(rackWidth, item.dt.rackDepth)
//
// planeGeometry.rotateX(Math.PI / 2)
//
// const planeMaterial = new THREE.MeshBasicMaterial({
// color: '#9a9090',
// transparent: true, // 启用透明
// opacity: 0.5, // 50%透明度
// depthWrite: false, // 防止深度冲突
// side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
// })
// const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
// group.add(planeMesh)
//
//
// // 绘制边框
// const lineXLen = rackWidth - this.defaultLineWidth
// const lineYLen = item.dt.rackDepth - 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: '#0d89a5',
// 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)
//
// let lineDistanceX = 0
//
// for (let i = 0; item.dt.bays.length > 1 && i < item.dt.bays.length - 1; i++) {
// const bay = item.dt.bays[i]
// lineDistanceX += bay.bayWidth
// const lineGeometryT = new LineGeometry().setPositions([
// -(lineDistanceX) + (lineXLen / 2), 0, lineYLen / 2,
// -(lineDistanceX) + (lineXLen / 2), 0, -(lineYLen / 2)
// ])
// const lineT = new Line2(lineGeometryT, lineMaterial)
// group.add(lineT as THREE.Object3D)
// }
planeGeometry.rotateX(Math.PI / 2) const meshes = this.createRack(item, option)
const planeMaterial = new THREE.MeshBasicMaterial({ meshes.forEach(mesh => {
color: '#9a9090', group.add(mesh)
transparent: true, // 启用透明
opacity: 0.5, // 50%透明度
depthWrite: false, // 防止深度冲突
side: THREE.DoubleSide // 双面渲染:ml-citation{ref="5,8" data="citationList"}
}) })
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
group.add(planeMesh)
// 绘制边框
const lineXLen = rackWidth - this.defaultLineWidth
const lineYLen = item.dt.rackDepth - 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: '#0d89a5',
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)
let lineDistanceX = 0
for (let i = 0; item.dt.bays.length > 1 && i < item.dt.bays.length - 1; i++) {
const bay = item.dt.bays[i]
lineDistanceX += bay.bayWidth
const lineGeometryT = new LineGeometry().setPositions([
-(lineDistanceX) + (lineXLen / 2), 0, lineYLen / 2,
-(lineDistanceX) + (lineXLen / 2), 0, -(lineYLen / 2)
])
const lineT = new Line2(lineGeometryT, lineMaterial)
group.add(lineT as THREE.Object3D)
}
// 设置位置 // 设置位置
group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2])
group.rotation.set(
THREE.MathUtils.degToRad(item.tf[1][0]),
THREE.MathUtils.degToRad(item.tf[1][1]),
THREE.MathUtils.degToRad(item.tf[1][2])
)
//
item.dt.rackWidth = rackWidth item.dt.rackWidth = rackWidth
item.dt.rackHeight = rackHeight item.dt.rackHeight = rackHeight
return group return group
@ -137,4 +155,497 @@ export default class RackRenderer extends BaseRenderer {
createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D { createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D {
throw new Error('Rack createPointBasic not allow!') throw new Error('Rack createPointBasic not allow!')
} }
rackVerticalBarWidth = 0.1
rackVerticalBarDepth = 0.08
rackVerticalBarColor = 0xFF35499C
rackVerticalBarGeometry: BufferGeometry = null
rackVerticalBarMaterial: Material = null
rackLinkBarColor = 0xFF35499C
rackLinkBarGeometry: BufferGeometry = null
rackLinkBarMaterial: Material = null
rackHorizontalBarWidth = 0.1
rackHorizontalBarDepth = 0.08
rackHorizontalBarColor = 0xFFF97F27
rackHorizontalBarGeometry: BufferGeometry = null
rackHorizontalBarMaterial: Material = null
bottomBarHeight = 0.2
bottomLinkHeight = 0.2
barSectionPoints = [
{ x: -0.05, y: -0.05 },
{ x: -0.025, y: -0.05 },
{ x: -0.01, y: -0.045 },
{ x: 0.05, y: -0.045 },
{ x: 0.025, y: -0.05 },
{ x: 0.05, y: -0.05 },
{ x: 0.05, y: 0.042 },
{ x: 0.042, y: 0.05 },
{ x: 0.025, y: 0.05 },
{ x: 0.025, y: 0.042 },
{ x: 0.042, y: 0.042 },
{ x: 0.042, y: -0.042 },
{ x: -0.042, y: -0.042 },
{ x: -0.042, y: 0.042 },
{ x: -0.025, y: 0.042 },
{ x: -0.025, y: 0.05 },
{ x: -0.042, y: 0.05 },
{ x: -0.05, y: 0.042 },
{ x: -0.05, y: -0.05 }
]
linkSectionPoints = [
{ x: -0.05, y: -0.05 },
{ x: -0.05, y: 0.05 },
{ x: 0, y: 0.05 },
{ x: 0, y: 0.06 },
{ x: -0.06, y: 0.06 },
{ x: -0.06, y: -0.05 },
{ x: -0.05, y: -0.05 }
]
linkBarSectionPoints = [
{ x: -0.025, y: -0.025 },
{ x: 0.025, y: -0.025 },
{ x: 0.025, y: 0.025 },
{ x: -0.025, y: 0.025 },
{ x: -0.025, y: -0.025 }
]
createVerticalBar(x, y, z, length): THREE.BufferGeometry {
// 创建一个形状 柱子的截面形状
const shape = new THREE.Shape()
shape.moveTo(this.barSectionPoints[0].x, this.barSectionPoints[0].y)
for (let i = 1; i < this.barSectionPoints.length; i++) {
shape.lineTo(this.barSectionPoints[i].x, this.barSectionPoints[i].y)
}
// 拉伸轨迹线
const curve = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, length, 0)],
false, // 闭合曲线
'catmullrom',
0
)
// 挤出几何图形 参数
const options = {
steps: 1,
bevelEnabled: false,
extrudePath: curve // 设置挤出轨迹
}
// 创建挤出几何体
const geometry = new THREE.ExtrudeGeometry(shape, options)
// 调整uv方便正确贴图
this.resetUVs(geometry)
return geometry
}
createVerticalBarMaterial(): THREE.Material {
let textureLoader = new THREE.TextureLoader()
// 加载纹理
const textureHole = textureLoader.load(storageBar_PNG) // 孔洞
const textureMaterial = textureLoader.load(Plastic_Rough_JPG) // 表面材质
textureHole.repeat.set(10, 18) // X轴重复,Y轴重复
textureMaterial.repeat.set(2, 2) // X轴重复,Y轴重复
// textureHole.offset.set(0.5, 0)
// textureHole.center.set(0.5, 0)
// 必须设置包裹模式为重复
textureHole.wrapS = THREE.RepeatWrapping
textureHole.wrapT = THREE.RepeatWrapping
textureMaterial.wrapS = THREE.RepeatWrapping
textureMaterial.wrapT = THREE.RepeatWrapping
const material = new THREE.MeshPhongMaterial()
material.alphaMap = textureHole
material.normalMap = textureMaterial
material.color.setHex(this.rackVerticalBarColor, 'srgb')
material.specular.setHex(0xff6d6d6d, 'srgb')
material.transparent = true
material.needsUpdate = true
return material
}
createLinkBar(x, y, z, vBarLength, depth, bottomDistance, topDistance): THREE.BufferGeometry {
const bgs: BufferGeometry[] = []
const top = vBarLength - topDistance
// 创建一个形状 柱子的截面形状
const shape = new THREE.Shape()
shape.moveTo(this.linkBarSectionPoints[0].x, this.linkBarSectionPoints[0].y)
for (let i = 1; i < this.linkBarSectionPoints.length; i++) {
shape.lineTo(this.linkBarSectionPoints[i].x, this.linkBarSectionPoints[i].y)
}
// 拉伸轨迹线 横向 底部
const curveHBottom = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0, bottomDistance, 0), new THREE.Vector3(0, bottomDistance, depth)],
false, // 闭合曲线
'catmullrom',
0
)
// 挤出几何图形 参数
const optionsHBottom = {
steps: 1,
bevelEnabled: false,
extrudePath: curveHBottom // 设置挤出轨迹
}
// 拉伸轨迹线 横向 底部
const curveHTop = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0, top, 0), new THREE.Vector3(0, top, depth)],
false, // 闭合曲线
'catmullrom',
0
)
// 挤出几何图形 参数
const optionsHTop = {
steps: 1,
bevelEnabled: false,
extrudePath: curveHTop // 设置挤出轨迹
}
// 创建挤出几何体
const geometryHBottom = new THREE.ExtrudeGeometry(shape, optionsHBottom)
const geometryHTop = new THREE.ExtrudeGeometry(shape, optionsHTop)
bgs.push(geometryHBottom, geometryHTop)
let remainingHeight = vBarLength - bottomDistance - topDistance
// 需要创建斜杆
for (let i = 0; i < Math.floor(remainingHeight / depth); i++) {
// 拉伸轨迹线 斜向
const curveD = new THREE.CatmullRomCurve3(
(i % 2 == 0) ? [new THREE.Vector3(0, bottomDistance + depth * i, 0), new THREE.Vector3(0, bottomDistance + depth * (i + 1), depth)]
: [new THREE.Vector3(0, bottomDistance + depth * (i + 1), 0), new THREE.Vector3(0, bottomDistance + depth * (i), depth)],
false, // 闭合曲线
'catmullrom',
0
)
const optionsD = {
steps: 1,
bevelEnabled: false,
extrudePath: curveD // 设置挤出轨迹
}
const geometryD = new THREE.ExtrudeGeometry(shape, optionsD)
bgs.push(geometryD)
}
if (vBarLength - bottomDistance - topDistance > depth) {
}
// 调整uv方便正确贴图
// this.resetUVs(geometry);
return mergeGeometries(bgs)
}
createLinkBarMaterial(): THREE.Material {
const material = new THREE.MeshPhongMaterial()
material.color.setHex(this.rackLinkBarColor, 'srgb')
material.specular.setHex(0xff6d6d6d, 'srgb')
material.transparent = true
material.needsUpdate = true
return material
}
createHorizontalBar(x, y, z, length): THREE.BufferGeometry {
// 创建一个形状 柱子的截面形状
const shape = new THREE.Shape()
shape.moveTo(this.barSectionPoints[0].x, this.barSectionPoints[0].y)
for (let i = 1; i < this.barSectionPoints.length; i++) {
shape.lineTo(this.barSectionPoints[i].x, this.barSectionPoints[i].y)
}
// 拉伸轨迹线
const curve = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0.05, 0, 0), new THREE.Vector3(length - 0.05, 0, 0)],
false, // 闭合曲线
'catmullrom',
0
)
// 挤出几何图形 参数
const options = {
steps: 1,
bevelEnabled: false,
extrudePath: curve // 设置挤出轨迹
}
// 创建挤出几何体
const geometry = new THREE.ExtrudeGeometry(shape, options)
const linkShapeL = new THREE.Shape()
const linkShapeR = new THREE.Shape()
linkShapeL.moveTo(this.linkSectionPoints[0].x, this.linkSectionPoints[0].y)
linkShapeR.moveTo(this.linkSectionPoints[0].x + (length), this.linkSectionPoints[0].y)
for (let i = 1; i < this.linkSectionPoints.length; i++) {
linkShapeL.lineTo(this.linkSectionPoints[i].x, this.linkSectionPoints[i].y)
linkShapeR.lineTo(this.linkSectionPoints[i].x + (length), this.linkSectionPoints[i].y)
}
// 拉伸轨迹线
const linkCurve = new THREE.CatmullRomCurve3(
[new THREE.Vector3(0, 0, -0.08), new THREE.Vector3(0, 0, 0.08)],
false, // 闭合曲线
'catmullrom',
0
)
// 挤出几何图形 参数
const linkOptions = {
steps: 1,
bevelEnabled: false,
extrudePath: linkCurve // 设置挤出轨迹
}
// 创建挤出几何体
const linkGeometryL = new THREE.ExtrudeGeometry(linkShapeL, linkOptions)
linkGeometryL.rotateZ(-Math.PI / 2)
const linkGeometryR = new THREE.ExtrudeGeometry(linkShapeR, linkOptions)
linkGeometryR.rotateX(-Math.PI)
linkGeometryR.rotateZ(-Math.PI / 2)
// 调整uv方便正确贴图
// this.resetUVs(geometry);
return mergeGeometries([geometry, linkGeometryL, linkGeometryR])
}
createHorizontalBarMaterial(): THREE.Material {
const material = new THREE.MeshPhongMaterial()
material.color.setHex(this.rackHorizontalBarColor, 'srgb')
material.specular.setHex(0xff6d6d6d, 'srgb')
material.transparent = true
material.needsUpdate = true
return material
}
createRack(item: ItemJson, option?: RendererCudOption): InstancedMesh[] {
if (!item.dt.bays || !item.dt.rackDepth) {
system.showErrorDialog('RackRenderer field bays / rackDepth is null!')
return null
}
const rackPoint = {
x: item.tf[0][0],
y: item.tf[0][1],
z: item.tf[0][2]
}
const rackWidth = decimalSumBy(item.dt.bays, (b: any) => b.bayWidth)
const rackDepth = item.dt.rackDepth
const heights = []
for (let i = 0; i < item.dt.bays.length; i++) {
const bay = item.dt.bays[i]
const bayHeight = decimalSumBy(bay.levelHeight)
heights.push(bayHeight)
}
const rackHeight = _.max(heights)
// 计算立住坐标点和长度
const vBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = []
// 计算
const linkBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = []
let distanceX = 0, distanceY = 0
for (let i = -1; i < item.dt.bays.length; i++) {
if (i >= 0) {
const bay = item.dt.bays[i]
distanceX += bay.bayWidth
}
vBarMatrix.push({
x: distanceX - rackWidth/2,
y: 0,
z: -rackDepth/2,
sx: 0.8,
sy: 1,
sz: 1,
rx: 0,
ry: Math.PI / 2,
rz: 0,
l: rackHeight
})
vBarMatrix.push({
x: distanceX - rackWidth/2,
y: 0,
z: item.dt.rackDepth - rackDepth/2,
sx: 0.8,
sy: 1,
sz: 1,
rx: 0,
ry: -Math.PI / 2,
rz: 0,
l: rackHeight
})
linkBarMatrix.push({
x: distanceX - rackWidth/2,
y: 0,
z: i % 2 == 0 ? (item.dt.rackDepth - rackDepth/2) : -rackDepth/2,
sx: 1,
sy: 1,
sz: 1,
rx: 0,
ry: i % 2 == 0 ? Math.PI : 0,
rz: 0,
l: rackHeight
})
}
// 计算横梁数量
const hBarMatrix: { x: number, y: number, z: number, sx: number, sy: number, sz: number, rx: number, ry: number, rz: number, l: number }[] = []
distanceX = 0
for (let i = 0; i < item.dt.bays.length; i++) {
distanceY = this.bottomBarHeight
const bay = item.dt.bays[i]
for (let j = 0; j < bay.levelHeight.length; j++) {
const levelHeight = bay.levelHeight[j]
if (distanceY <= 0) {
continue
}
hBarMatrix.push({
x: distanceX - rackWidth/2,
y: distanceY,
z: -rackDepth/2,
sx: 1,
sy: 0.8,
sz: 1,
rx: Math.PI / 2,
ry: 0,
rz: 0,
l: bay.bayWidth
})
hBarMatrix.push({
x: distanceX - rackWidth/2,
y: distanceY,
z: item.dt.rackDepth-rackDepth/2,
sx: 1,
sy: 0.8,
sz: 1,
rx: -Math.PI / 2,
ry: 0,
rz: 0,
l: bay.bayWidth
})
distanceY += levelHeight
}
distanceX += bay.bayWidth
}
const meshes: InstancedMesh[] = []
if (vBarMatrix.length > 0) {
if (!this.rackVerticalBarGeometry) {
this.rackVerticalBarGeometry = this.createVerticalBar(vBarMatrix[0].x, vBarMatrix[0].y, vBarMatrix[0].z, vBarMatrix[0].l)
}
if (!this.rackVerticalBarMaterial) {
this.rackVerticalBarMaterial = this.createVerticalBarMaterial()
}
const dummy = new THREE.Object3D()
const vBarMesh = new THREE.InstancedMesh(this.rackVerticalBarGeometry, this.rackVerticalBarMaterial, vBarMatrix.length)
for (let i = 0; i < vBarMatrix.length; i++) {
const vp = vBarMatrix[i]
dummy.position.set(vp.x, vp.y, vp.z)
dummy.rotation.set(vp.rx, vp.ry, vp.rz)
dummy.scale.set(vp.sx, vp.sy, vp.sz)
dummy.updateMatrix()
vBarMesh.setMatrixAt(i, dummy.matrix)
}
meshes.push(vBarMesh)
}
if (linkBarMatrix.length > 0) {
if (!this.rackLinkBarGeometry) {
this.rackLinkBarGeometry = this.createLinkBar(linkBarMatrix[0].x, linkBarMatrix[0].y, linkBarMatrix[0].z, rackHeight, item.dt.rackDepth, this.bottomLinkHeight, 0.2)
}
if (!this.rackLinkBarMaterial) {
this.rackLinkBarMaterial = this.createLinkBarMaterial()
}
const dummy = new THREE.Object3D()
const linkBarMesh = new THREE.InstancedMesh(this.rackLinkBarGeometry, this.rackLinkBarMaterial, linkBarMatrix.length)
for (let i = 0; i < linkBarMatrix.length; i++) {
const lp = linkBarMatrix[i]
dummy.position.set(lp.x, lp.y, lp.z)
dummy.rotation.set(lp.rx, lp.ry, lp.rz)
dummy.scale.set(lp.sx, lp.sy, lp.sz)
dummy.updateMatrix()
linkBarMesh.setMatrixAt(i, dummy.matrix)
}
meshes.push(linkBarMesh)
}
if (hBarMatrix.length > 0) {
if (!this.rackHorizontalBarGeometry) {
this.rackHorizontalBarGeometry = this.createHorizontalBar(hBarMatrix[0].x, hBarMatrix[0].y, hBarMatrix[0].z, hBarMatrix[0].l)
}
if (!this.rackHorizontalBarMaterial) {
this.rackHorizontalBarMaterial = this.createHorizontalBarMaterial()
}
const dummy = new THREE.Object3D()
const hBarMesh = new THREE.InstancedMesh(this.rackHorizontalBarGeometry, this.rackHorizontalBarMaterial, hBarMatrix.length)
for (let i = 0; i < hBarMatrix.length; i++) {
const hp = hBarMatrix[i]
dummy.position.set(hp.x, hp.y, hp.z)
dummy.rotation.set(hp.rx, hp.ry, hp.rz)
dummy.scale.set(hp.sx, hp.sy, hp.sz)
dummy.updateMatrix()
hBarMesh.setMatrixAt(i, dummy.matrix)
}
meshes.push(hBarMesh)
}
return meshes
}
resetUVs(geometry: THREE.ExtrudeGeometry) {
if (geometry == undefined) return
const pos = geometry.getAttribute('position'),
nor = geometry.getAttribute('normal'),
uvs = geometry.getAttribute('uv')
for (let i = 0; i < pos.count; i++) {
let x = 0, y = 0
const nx = Math.abs(nor.getX(i)), ny = Math.abs(nor.getY(i)), nz = Math.abs(nor.getZ(i))
// if facing X
if (nx >= ny && nx >= nz) {
x = pos.getZ(i)
y = pos.getY(i)
}
// if facing Y
if (ny >= nx && ny >= nz) {
x = pos.getX(i)
y = pos.getZ(i)
}
// if facing Z
if (nz >= nx && nz >= ny) {
x = pos.getX(i)
y = pos.getY(i)
}
uvs.setXY(i, x, y)
}
}
} }

5
src/modules/tote/ToteEntity.ts

@ -0,0 +1,5 @@
import BaseEntity from '@/core/base/BaseItemEntity.ts'
export default class PalletEntity extends BaseEntity {
}

22
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
}
}

20
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;

77
src/modules/tote/ToteRenderer.ts

@ -0,0 +1,77 @@
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
//@ts-ignore
this.toteMaterial.map = queueTexture
//@ts-ignore
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,
true, true)
)
}
dispose() {
super.dispose()
if (this.toteGeometry) {
this.toteGeometry.dispose()
this.toteGeometry = undefined
}
if (this.toteMaterial) {
this.toteMaterial.dispose()
this.toteMaterial = undefined
}
}
}

15
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
})

413
src/modules/way/WayRenderer.ts

@ -1,177 +1,302 @@
import * as THREE from 'three' import * as THREE from 'three'
import BaseRenderer from '@/core/base/BaseRenderer.ts' import BaseRenderer from '@/core/base/BaseRenderer.ts'
import { Text } from 'troika-three-text'
import MoveLinePointPng from '@/assets/images/moveline_point.png' import MoveLinePointPng from '@/assets/images/moveline_point.png'
import SimSunTTF from '@/assets/fonts/simsunb.ttf' import { createLinkPlaneMatrix4, getCargoLineId, getLinkDirection } from '@/core/ModelUtils.ts'
import { getLineId } from '@/core/ModelUtils.ts'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { numberToString } from '@/utils/webutils.ts'
import Constract from '@/core/Constract.ts' import Constract from '@/core/Constract.ts'
import type { ExtrudeGeometryOptions } from 'three/src/geometries/ExtrudeGeometry' import InstancePointManager from '@/core/manager/InstancePointManager.ts'
import type { Object3DLike } from '@/types/ModelTypes.ts'
import TriangleUrl from '@/assets/images/conveyor/shapes/triangle.png'
import Triangle2Url from '@/assets/images/conveyor/shapes/triangle2.png'
import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts'
/** /**
* * AGV行走路线渲染器 point
*/ */
export default class WayRenderer extends BaseRenderer { export default class WayRenderer extends BaseRenderer {
static LABEL_NAME = 'way_label' static LABEL_NAME = 'way_label'
static POINT_NAME = 'way_point' static POINT_NAME = 'way_point'
static LINE_NAME = 'way_line' static LINE_NAME = 'way_line'
static GUIDEWAY_LINE_NAME = 'guideway'
pointGeometry: THREE.BufferGeometry
pointMaterial: THREE.Material pointMaterial: THREE.Material
// lineMaterial: LineMaterial = new LineMaterial({
// color: 0xa0cfff,
// linewidth: 0.8,
// vertexColors: false,
// dashed: false,
// gapSize: 0,
// worldUnits: true
// })
lineGeometry: THREE.BufferGeometry = new THREE.PlaneGeometry(1, 1).rotateX(-Math.PI / 2)
lineMaterial = new THREE.MeshBasicMaterial({ lineMaterial = new THREE.MeshBasicMaterial({
color: 0xa0cfff, color: 0xa0cfff,
transparent: true, transparent: true,
opacity: 0.2, opacity: 0.2,
side: THREE.DoubleSide side: THREE.DoubleSide
}) })
dirGeometry: THREE.PlaneGeometry
dirMaterial: THREE.Material
dir2Material: THREE.Material
/** /**
* , * ,
*/ */
readonly defulePositionY: number = Constract.HEIGHT_WAY readonly defulePositionY: number = Constract.HEIGHT_WAY
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.25, 0.1) readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.5, 0.1, 0.5)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
readonly rendererOption = {
constructor(itemTypeName: string) { lineWidth: 0.8
super(itemTypeName)
} }
async init() { async init() {
return Promise.all([ return Promise.all([
super.init(), super.init(),
this.loadFont() new THREE.TextureLoader().loadAsync(MoveLinePointPng),
]) new THREE.TextureLoader().loadAsync(TriangleUrl),
} new THREE.TextureLoader().loadAsync(Triangle2Url)
async loadFont() { ]).then(([_, texture, dirTexture, dir2Texture]) => {
return new Promise<void>((resolve, reject) => { texture.flipY = false
new THREE.TextureLoader().load(
MoveLinePointPng, this.pointGeometry = new THREE.PlaneGeometry(1, 1).rotateX(-Math.PI / 2)
(texture) => { this.pointGeometry.center()
this.pointMaterial = new THREE.SpriteMaterial({
map: texture, this.pointMaterial = new THREE.MeshBasicMaterial({
transparent: true, map: texture,
side: THREE.DoubleSide transparent: true,
}) depthWrite: false,
resolve() side: THREE.DoubleSide
}, })
undefined, this.pointMaterial.needsUpdate = true
function(err) {
reject(err) this.dirGeometry = new THREE.PlaneGeometry(1, 1)
} this.dirGeometry.rotateX(-Math.PI / 2)
) .rotateY(-Math.PI / 2)
this.dirGeometry.center()
this.dirMaterial = new THREE.MeshBasicMaterial({
map: dirTexture,
transparent: true,
opacity: 1,
depthWrite: false,
side: THREE.DoubleSide
})
this.dir2Material = new THREE.MeshBasicMaterial({
map: dir2Texture,
transparent: true,
opacity: 1,
depthWrite: false,
side: THREE.DoubleSide
})
}) })
} }
/** createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike {
* 使, // 不允许改变高度/角度/大小
*/ item.tf = [
override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: THREE.Object3D) { [item.tf[0][0], this.defulePositionY, item.tf[0][2]],
super.afterCreateOrUpdatePoint(item, option, object) [this.defaultRotation.x, this.defaultRotation.y, this.defaultRotation.z],
[this.defaultScale.x, this.defaultScale.y, this.defaultScale.z]
const point = object ]
point.position.y = this.defulePositionY return this.pointManager.createPoint(item)
point.scale.set(this.defaultScale.x, this.defaultScale.y, this.defaultScale.z)
point.rotation.set(
THREE.MathUtils.degToRad(this.defaultRotation.x),
THREE.MathUtils.degToRad(this.defaultRotation.y),
THREE.MathUtils.degToRad(this.defaultRotation.z)
)
} }
private _createOrUpdateLine(startPosition: THREE.Vector3, endPosition: THREE.Vector3, type: LinkType) { createLine(start: ItemJson, end: ItemJson, type: LinkType): Object3DLike {
const width = 1 if (start.t === this.itemTypeName && end.t === this.itemTypeName) {
const halfWidth = width / 2 return this._createOrUpdateGuideway(start, end, type)
} else {
// throw new Error('目前只支持二维码站点之间的连接')
}
}
const path = new THREE.LineCurve3( updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) {
new THREE.Vector3(startPosition.x, startPosition.z, halfWidth - Constract.HEIGHT_WAY_LINE), if (start.t === this.itemTypeName && end.t === this.itemTypeName) {
new THREE.Vector3(endPosition.x, endPosition.z, halfWidth - Constract.HEIGHT_WAY_LINE) this.deleteLine(start, end, type)
) this._createOrUpdateGuideway(start, end, type)
const shape = new THREE.Shape() } else {
shape.moveTo(halfWidth, -halfWidth) // throw new Error('目前只支持二维码站点之间的连接')
shape.lineTo(halfWidth, halfWidth) }
}
const extrudeSettings: ExtrudeGeometryOptions = { // 如果起点和终点类型, 都是 二维码站点, 就画 Guideway
steps: 2, // 沿路径的分段数 private _createOrUpdateGuideway(start: ItemJson, end: ItemJson, type: LinkType) {
depth: 1, // 实际由路径长度决定 const { lineId, direction } = getLinkDirection(this.tempViewport, WayRenderer.GUIDEWAY_LINE_NAME, start, end)
bevelEnabled: false, // 禁用倒角 if (!direction) {
extrudePath: path // 挤出路径 // 忽略创建
return
} }
const extrudedGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings) const startPosition = new THREE.Vector3(start.tf[0][0], this.defulePositionY, start.tf[0][2])
extrudedGeometry.rotateX(Math.PI / 2) const endPosition = new THREE.Vector3(end.tf[0][0], this.defulePositionY, end.tf[0][2])
const wrap = this.guidewayManager.create(lineId, {})
wrap.uuid = lineId
const matrix = createLinkPlaneMatrix4(startPosition, endPosition, this.rendererOption.lineWidth, direction)
wrap.setMatrix4(matrix)
return new THREE.Mesh(extrudedGeometry, this.lineMaterial)
}
createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { const length = startPosition.distanceTo(endPosition)
const startPosition = new THREE.Vector3(start.tf[0][0], 0, start.tf[0][2]) if (length < 0.1) {
const endPosition = new THREE.Vector3(end.tf[0][0], 0, end.tf[0][2]) // 如果两点距离小于 0.1m,则不添加方向指示器
return
} else if (length < 3) {
// 如果两点距离小于 3m,则在中间添加一个方向指示器
let dirWrap
if (direction === '<->') {
dirWrap = this.dir2PointManager.create(lineId + '_dir')
} else {
dirWrap = this.dirPointManager.create(lineId + '_dir')
}
const group = new THREE.Group() const dummy = new THREE.Object3D()
const lineMesh = this._createOrUpdateLine(startPosition, endPosition, type) dummy.position.setFromMatrixPosition(matrix)
group.add(lineMesh) // dummy.rotation.setFromRotationMatrix(matrix)
if (direction === '<->') {
dummy.lookAt(endPosition)
} else if (direction === '->') {
dummy.lookAt(endPosition)
} else if (direction === '<-') {
dummy.lookAt(startPosition)
}
const midPoint = new THREE.Vector3() dummy.scale.set(0.4, 0.01, 0.2)
.addVectors(startPosition, endPosition) dummy.updateMatrix()
.multiplyScalar(0.5) dirWrap.setMatrix4(dummy.matrix)
wrap.userData.dirWraps = [dirWrap.uuid]
const distance = (startPosition.distanceTo(endPosition) * 1000).toFixed(0) } else {
// 否则每隔 3m 添加一个方向指示器
for (let i = 1; i < length - 0.1; i += 3) {
let dirWrap
if (direction === '<->') {
dirWrap = this.dir2PointManager.create(lineId + '_dir_' + i)
} else {
dirWrap = this.dirPointManager.create(lineId + '_dir_' + i)
}
const label = new Text() const position = startPosition.clone().lerp(endPosition, i / length)
label.text = distance const dummy = new THREE.Object3D()
label.font = SimSunTTF dummy.position.copy(position)
label.fontSize = 0.2
label.color = '#5f5f5f'
label.opacity = 0.8
label.anchorX = 'center'
label.anchorY = 'middle'
label.depthOffset = 1
label.material.depthTest = false
label.name = WayRenderer.LABEL_NAME
label.quaternion.copy(this.tempViewport.camera.quaternion)
label.sync()
label.position.set(midPoint.x, Constract.HEIGHT_WAY_LABEL, midPoint.z)
group.add(label) if (direction === '<->') {
dummy.lookAt(endPosition)
} else if (direction === '->') {
dummy.lookAt(endPosition)
} else if (direction === '<-') {
dummy.lookAt(startPosition)
}
return group dummy.scale.set(0.4, 0.01, 0.2)
} dummy.updateMatrix()
dirWrap.setMatrix4(dummy.matrix)
if (!wrap.userData.dirWraps) {
wrap.userData.dirWraps = []
}
wrap.userData.dirWraps.push(dirWrap.uuid)
}
}
updateLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) {
super.updateLine(start, end, type, option)
const startPosition = new THREE.Vector3(start.tf[0][0], 0.01, start.tf[0][2]) /**
const endPosition = new THREE.Vector3(end.tf[0][0], 0.01, end.tf[0][2]) *
* way11 <-> way12
* way21 -> way22
* way31 <- way32
*/
this.tempViewport.entityManager.appendLineObject(lineId, wrap)
// console.log(start.id + direction + end.id)
return wrap
}
const lineId = getLineId(start.id, end.id, type)
const group = this.tempViewport.entityManager.findLineObjectById(lineId)
// 清空group里的元素 deleteLine(start: ItemJson, end: ItemJson, type: LinkType, option?: RendererCudOption) {
const label: Text = group.children[1] if ((start.t === this.itemTypeName || start.t === 'unknown') && (end.t === this.itemTypeName || end.t === 'unknown')) {
group.clear() // 二维码站点之间的连接
const lineId = getCargoLineId(WayRenderer.GUIDEWAY_LINE_NAME, start.id, end.id)
const object = this.tempViewport.entityManager.findLineObjectById(lineId)
const lineMesh = this._createOrUpdateLine(startPosition, endPosition, type) // 删除箭头
group.add(lineMesh) if (object?.userData.dirWraps) {
object.userData.dirWraps.forEach((uuid: string) => {
this.dirPointManager.delete(uuid)
this.dir2PointManager.delete(uuid)
})
}
const midPoint = new THREE.Vector3() // 删除这条线
.addVectors(startPosition, endPosition) this.guidewayManager.delete(lineId)
.multiplyScalar(0.5) this.tempViewport.entityManager.deleteLineObjectOnly(lineId)
const distance = (startPosition.distanceTo(endPosition) * 1000).toFixed(0) } else {
label.text = distance // throw new Error('目前只支持二维码站点之间的连接')
label.quaternion.copy(this.tempViewport.camera.quaternion) }
label.sync()
label.position.set(midPoint.x, midPoint.y, midPoint.z)
group.add(label)
} }
createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D { afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: Object3DLike) {
const obj = new THREE.Sprite(this.pointMaterial as THREE.SpriteMaterial) const startPosition = new THREE.Vector3(start.tf[0][0], this.defulePositionY, start.tf[0][2])
obj.name = WayRenderer.POINT_NAME const endPosition = new THREE.Vector3(end.tf[0][0], this.defulePositionY, end.tf[0][2])
return obj
// =============== 下面这一段不要删除,用来显示数字标签 =============================
// this.tempViewport.labelManager.createOrUpdateLabelByDistance(object, startPoint.position, endPoint.position, {
// useHtmlLabel: false,
// fontSize: 0.2,
// color: '#333333',
// format: (distance) => {
// return (distance * 1000).toFixed(0)
// }
// })
// ==========================================================================
// const matrix = linkPlaneByPoint(startPosition, endPosition, this.rendererOption.lineWidth)
//
// if (object.userData.dirWraps) {
// // 清空之前的箭头
// object.userData.dirWraps.forEach((uuid: string) => {
// this.dirPointManager.delete(uuid)
// })
// }
//
// const length = startPosition.distanceTo(endPosition)
// if (length < 0.1) {
// // 如果两点距离小于 0.1m,则不添加方向指示器
// return
//
// } else if (length < 3) {
// // 如果两点距离小于 3m,则在中间添加一个方向指示器
// const dirWrap = this.dirPointManager.create(object.uuid + '_dir')
//
// const dummy = new THREE.Object3D()
// dummy.position.setFromMatrixPosition(matrix)
// dummy.rotation.setFromRotationMatrix(matrix)
// dummy.scale.set(0.4, 0.01, 0.2)
// dummy.updateMatrix()
// dirWrap.setMatrix4(dummy.matrix)
// object.userData.dirWraps = [dirWrap.uuid]
//
// } else {
// // 否则每隔 3m 添加一个方向指示器
// for (let i = 0; i < length; i += 3) {
// const dirWrap = this.dirPointManager.create(object.uuid + '_dir_' + i)
// const position = startPosition.clone().lerp(endPosition, i / length)
// const dummy = new THREE.Object3D()
// dummy.position.copy(position)
// dummy.lookAt(endPosition)
// dummy.scale.set(0.4, 0.01, 0.2)
// dummy.updateMatrix()
//
// dirWrap.setMatrix4(dummy.matrix)
//
// if (!object.userData.dirWraps) {
// object.userData.dirWraps = []
// }
// object.userData.dirWraps.push(dirWrap.uuid)
// }
// }
} }
dispose() { dispose() {
@ -179,4 +304,60 @@ export default class WayRenderer extends BaseRenderer {
this.pointMaterial?.dispose() this.pointMaterial?.dispose()
this.lineMaterial?.dispose() this.lineMaterial?.dispose()
} }
get guidewayManager(): InstanceMeshManager {
if (!this.tempViewport) {
throw new Error('tempViewport is not set.')
}
const name = WayRenderer.GUIDEWAY_LINE_NAME
return this.tempViewport.getOrCreateMeshManager(name, () => {
// 构建 LineSegment.points 代理对象
return new InstanceMeshManager(name, this.tempViewport, this.lineGeometry, this.lineMaterial,
false, false)
})
}
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.pointGeometry,
this.pointMaterial,
true, true)
)
}
get dirPointManager(): InstanceMeshManager {
if (!this.tempViewport) {
throw new Error('tempViewport is not set.')
}
return this.tempViewport.getOrCreateMeshManager(this.itemTypeName + '_dir', () =>
// 构建 InstanceMesh 代理对象
new InstanceMeshManager(this.itemTypeName + '_dir',
this.tempViewport,
this.dirGeometry,
this.dirMaterial,
false, false)
)
}
get dir2PointManager(): InstanceMeshManager {
if (!this.tempViewport) {
throw new Error('tempViewport is not set.')
}
return this.tempViewport.getOrCreateMeshManager(this.itemTypeName + '_dir2', () =>
// 构建 InstanceMesh 代理对象
new InstanceMeshManager(this.itemTypeName + '_dir2',
this.tempViewport,
this.dirGeometry,
this.dir2Material,
false, false)
)
}
} }

32
src/types/ModelTypes.ts

@ -2,6 +2,7 @@ import { Object3D } from 'three'
import LineSegmentManager, { LineManageWrap } from '@/core/manager/LineSegmentManager.ts' import LineSegmentManager, { LineManageWrap } from '@/core/manager/LineSegmentManager.ts'
import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePointManager.ts' import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePointManager.ts'
import * as THREE from 'three' import * as THREE from 'three'
import { InstanceMeshWrap } from '@/core/manager/InstanceMeshManager.ts'
// //
// /** // /**
// * 点数据接口, 用于平衡 Object3D 一致的取数方式 // * 点数据接口, 用于平衡 Object3D 一致的取数方式
@ -38,6 +39,24 @@ import * as THREE from 'three'
// end: THREE.Vector3; // end: THREE.Vector3;
// } // }
/**
*
*/
export const BasePlane = {
LEFT: 0b000010,
RIGHT: 0b000001,
FRONT: 0b001000,
BEHIND: 0b000100,
TOP: 0b100000,
BOTTOM: 0b010000,
TRANSVERSE: 0b10000000,
LONGITUDINAL: 0b01000000,
THROW: 0b00100000,
toArray: () => {
return [BasePlane.LEFT, BasePlane.BEHIND, BasePlane.RIGHT, BasePlane.FRONT, BasePlane.BOTTOM, BasePlane.TOP, BasePlane.TRANSVERSE, BasePlane.LONGITUDINAL, BasePlane.THROW]
}
}
export interface LineManageReference { export interface LineManageReference {
manager: LineSegmentManager manager: LineSegmentManager
id: string id: string
@ -48,9 +67,18 @@ export interface PointManagerReference {
id: string id: string
} }
export type Object3DLike = Object3D | LineManageWrap | PointManageWrap export type Object3DLike = Object3D | LineManageWrap | PointManageWrap | InstanceMeshWrap
/** /**
* , THREE.Vector3 * , THREE.Vector3
*/ */
export type Vector3Like = THREE.Vector3 | [number, number, number] export type Vector3Like = THREE.Vector3 | number[]
/**
*
*/
export const MaterialQualityEnum = {
High: 0,
Middle: 1,
Low: 2
}

1
src/types/Types.d.ts

@ -1,3 +1,4 @@
type LinkDirection = '<-' | '->' | '<->' | ''
type CursorMode = type CursorMode =
'normal' 'normal'
| 'ALink' | 'ALink'

1
vite.config.ts

@ -17,6 +17,7 @@ export default defineConfig({
} }
}, },
server: { server: {
host: '0.0.0.0',
port: 7791, port: 7791,
open: false, open: false,
proxy: { proxy: {

Loading…
Cancel
Save