@operato/scene-storage 10.0.0-beta.32 → 10.0.0-beta.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/crane-3d.d.ts +14 -0
- package/dist/crane-3d.js +238 -0
- package/dist/crane-3d.js.map +1 -0
- package/dist/crane.d.ts +157 -0
- package/dist/crane.js +440 -0
- package/dist/crane.js.map +1 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/mobile-storage-rack.d.ts +17 -0
- package/dist/mobile-storage-rack.js +55 -0
- package/dist/mobile-storage-rack.js.map +1 -0
- package/dist/rack-column.d.ts +35 -0
- package/dist/rack-column.js +258 -0
- package/dist/rack-column.js.map +1 -0
- package/dist/rack-grid-3d.d.ts +13 -0
- package/dist/rack-grid-3d.js +94 -0
- package/dist/rack-grid-3d.js.map +1 -0
- package/dist/rack-grid-cell.d.ts +341 -0
- package/dist/rack-grid-cell.js +321 -0
- package/dist/rack-grid-cell.js.map +1 -0
- package/dist/rack-grid-helpers.d.ts +28 -0
- package/dist/rack-grid-helpers.js +71 -0
- package/dist/rack-grid-helpers.js.map +1 -0
- package/dist/rack-grid-location.d.ts +37 -0
- package/dist/rack-grid-location.js +227 -0
- package/dist/rack-grid-location.js.map +1 -0
- package/dist/rack-grid.d.ts +80 -0
- package/dist/rack-grid.js +829 -0
- package/dist/rack-grid.js.map +1 -0
- package/dist/stock.d.ts +78 -0
- package/dist/stock.js +333 -0
- package/dist/stock.js.map +1 -0
- package/dist/{rack-cell-3d.d.ts → storage-cell-3d.d.ts} +1 -1
- package/dist/{rack-cell-3d.js → storage-cell-3d.js} +3 -3
- package/dist/storage-cell-3d.js.map +1 -0
- package/dist/{rack-cell.d.ts → storage-cell.d.ts} +12 -6
- package/dist/{rack-cell.js → storage-cell.js} +9 -9
- package/dist/storage-cell.js.map +1 -0
- package/dist/{asrs-rack-3d.d.ts → storage-rack-3d.d.ts} +1 -1
- package/dist/{asrs-rack-3d.js → storage-rack-3d.js} +4 -4
- package/dist/storage-rack-3d.js.map +1 -0
- package/dist/{asrs-rack.d.ts → storage-rack.d.ts} +22 -16
- package/dist/{asrs-rack.js → storage-rack.js} +32 -26
- package/dist/storage-rack.js.map +1 -0
- package/dist/templates/index.d.ts +60 -0
- package/dist/templates/index.js +59 -17
- package/dist/templates/index.js.map +1 -1
- package/package.json +3 -3
- package/src/crane-3d.ts +273 -0
- package/src/crane.ts +538 -0
- package/src/index.ts +13 -6
- package/src/mobile-storage-rack.ts +56 -0
- package/src/rack-column.ts +340 -0
- package/src/rack-grid-3d.ts +128 -0
- package/src/rack-grid-cell.ts +404 -0
- package/src/rack-grid-helpers.ts +77 -0
- package/src/rack-grid-location.ts +286 -0
- package/src/rack-grid.ts +994 -0
- package/src/stock.ts +426 -0
- package/src/{rack-cell-3d.ts → storage-cell-3d.ts} +2 -2
- package/src/{rack-cell.ts → storage-cell.ts} +19 -13
- package/src/{asrs-rack-3d.ts → storage-rack-3d.ts} +3 -3
- package/src/{asrs-rack.ts → storage-rack.ts} +31 -25
- package/src/templates/index.ts +59 -17
- package/test/test-rack-grid-crane.ts +212 -0
- package/test/test-rack-grid.ts +77 -0
- package/test/{test-asrs-crane.ts → test-storage-rack-crane.ts} +8 -8
- package/translations/en.json +55 -7
- package/translations/ja.json +52 -4
- package/translations/ko.json +52 -4
- package/translations/ms.json +55 -7
- package/translations/zh.json +52 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/asrs-crane-3d.d.ts +0 -17
- package/dist/asrs-crane-3d.js +0 -181
- package/dist/asrs-crane-3d.js.map +0 -1
- package/dist/asrs-crane.d.ts +0 -98
- package/dist/asrs-crane.js +0 -216
- package/dist/asrs-crane.js.map +0 -1
- package/dist/asrs-rack-3d.js.map +0 -1
- package/dist/asrs-rack.js.map +0 -1
- package/dist/rack-cell-3d.js.map +0 -1
- package/dist/rack-cell.js.map +0 -1
- package/src/asrs-crane-3d.ts +0 -211
- package/src/asrs-crane.ts +0 -275
- /package/icons/{asrs-crane.png → crane.png} +0 -0
- /package/icons/{asrs-rack.png → storage-rack.png} +0 -0
package/src/stock.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Component, Model, RealObject } from '@hatiolab/things-scene'
|
|
6
|
+
import * as THREE from 'three'
|
|
7
|
+
|
|
8
|
+
const STOCK_COLOR = '#ccaa76'
|
|
9
|
+
const _color = new THREE.Color()
|
|
10
|
+
const _matrix = new THREE.Matrix4()
|
|
11
|
+
const _position = new THREE.Vector3()
|
|
12
|
+
const _quaternion = new THREE.Quaternion()
|
|
13
|
+
const _scale = new THREE.Vector3()
|
|
14
|
+
|
|
15
|
+
function createStockMaterial(color: string | number, opts?: { opacity?: number; transparent?: boolean }): THREE.MeshStandardMaterial {
|
|
16
|
+
const mat = new THREE.MeshStandardMaterial({
|
|
17
|
+
color,
|
|
18
|
+
side: THREE.FrontSide,
|
|
19
|
+
metalness: 0,
|
|
20
|
+
roughness: 1,
|
|
21
|
+
envMapIntensity: 0
|
|
22
|
+
})
|
|
23
|
+
if (opts?.opacity !== undefined) {
|
|
24
|
+
mat.opacity = opts.opacity
|
|
25
|
+
mat.transparent = opts.transparent ?? true
|
|
26
|
+
}
|
|
27
|
+
return mat
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Stock material/legend 정보를 제공하는 컴포넌트 인터페이스.
|
|
32
|
+
* Visualizer와 RackTable 모두 이를 구현한다.
|
|
33
|
+
*/
|
|
34
|
+
export interface StockMaterialProvider {
|
|
35
|
+
_stock_materials: THREE.Material[]
|
|
36
|
+
_default_material?: THREE.Material
|
|
37
|
+
_empty_material?: THREE.Material
|
|
38
|
+
legendTarget?: Component
|
|
39
|
+
hideEmptyStock?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class Stock extends RealObject<THREE.Mesh> {
|
|
43
|
+
static defaultMaterial = createStockMaterial(STOCK_COLOR) as THREE.MeshStandardMaterial
|
|
44
|
+
static defaultEmptyMaterial = createStockMaterial(STOCK_COLOR, { opacity: 0.33, transparent: true })
|
|
45
|
+
static stockGeometry = new THREE.BoxGeometry(1, 1, 1)
|
|
46
|
+
|
|
47
|
+
_hideEmptyStock: boolean = false
|
|
48
|
+
_focused: boolean = false
|
|
49
|
+
declare _focusedAt?: number
|
|
50
|
+
|
|
51
|
+
// InstancedMesh 모드
|
|
52
|
+
_instancedMesh?: THREE.InstancedMesh
|
|
53
|
+
_instanceIndex: number = -1
|
|
54
|
+
_basePosition?: { x: number; y: number; z: number }
|
|
55
|
+
_baseScale?: { x: number; y: number; z: number }
|
|
56
|
+
|
|
57
|
+
model: any
|
|
58
|
+
_data?: any
|
|
59
|
+
|
|
60
|
+
constructor(component: Component, model: any) {
|
|
61
|
+
super(component)
|
|
62
|
+
this.model = model
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get isInstanced(): boolean {
|
|
66
|
+
return !!this._instancedMesh && this._instanceIndex >= 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
protected getObject3dInstance() {
|
|
70
|
+
return new THREE.Mesh()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 가장 가까운 StockMaterialProvider 조상을 찾는다 (RackTable 또는 Visualizer).
|
|
75
|
+
*/
|
|
76
|
+
get provider(): StockMaterialProvider | undefined {
|
|
77
|
+
let component = this.component
|
|
78
|
+
|
|
79
|
+
while (component) {
|
|
80
|
+
if ('_stock_materials' in component) {
|
|
81
|
+
return component as unknown as StockMaterialProvider
|
|
82
|
+
}
|
|
83
|
+
component = component.parent
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private get _legendStatus(): any | undefined {
|
|
88
|
+
return this.provider?.legendTarget?.getState('status') || undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* InstancedMesh의 해당 인스턴스 색상을 설정한다.
|
|
93
|
+
*/
|
|
94
|
+
private _setInstanceColor(color: string | number) {
|
|
95
|
+
if (!this._instancedMesh) return
|
|
96
|
+
_color.set(color)
|
|
97
|
+
this._instancedMesh.setColorAt(this._instanceIndex, _color)
|
|
98
|
+
this._instancedMesh.instanceColor!.needsUpdate = true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 색상을 결정하고 InstancedMesh에 반영한다.
|
|
103
|
+
*/
|
|
104
|
+
private _applyMaterialColor(index: number) {
|
|
105
|
+
const status = this._legendStatus
|
|
106
|
+
if (!status) {
|
|
107
|
+
this._setInstanceColor(this._getDefaultColor())
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const range = status.ranges?.[index]
|
|
112
|
+
if (!(range && range.color)) {
|
|
113
|
+
this._setInstanceColor(this._getDefaultColor())
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this._setInstanceColor(range.color)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private _getDefaultColor(): string | number {
|
|
121
|
+
const status = this._legendStatus
|
|
122
|
+
return status?.defaultColor || STOCK_COLOR
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 하위 호환: 개별 mesh 모드 material 접근
|
|
126
|
+
getMaterial(index: number) {
|
|
127
|
+
const status = this._legendStatus
|
|
128
|
+
if (!status) return this.userDefineDefaultMaterial
|
|
129
|
+
|
|
130
|
+
const range = status.ranges?.[index]
|
|
131
|
+
if (!(range && range.color)) {
|
|
132
|
+
this.stockMaterials[index] = this.userDefineDefaultMaterial
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!this.stockMaterials[index]) {
|
|
136
|
+
this.stockMaterials[index] = createStockMaterial(range.color)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var alpha = range.color.replace(/^.*,(.+)\)/, '$1')
|
|
140
|
+
if (alpha > 0 && alpha < 1) {
|
|
141
|
+
this.stockMaterials[index].opacity = alpha
|
|
142
|
+
this.stockMaterials[index].transparent = true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this.stockMaterials[index]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get stockMaterials(): THREE.Material[] {
|
|
149
|
+
return this.provider?._stock_materials ?? []
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get userDefineDefaultMaterial(): THREE.Material {
|
|
153
|
+
const p = this.provider
|
|
154
|
+
|
|
155
|
+
if (p) {
|
|
156
|
+
if (!p._default_material) {
|
|
157
|
+
const status = this._legendStatus
|
|
158
|
+
const defaultColor = status?.defaultColor
|
|
159
|
+
if (!defaultColor) return Stock.defaultMaterial
|
|
160
|
+
|
|
161
|
+
var alpha = Number(defaultColor.replace(/^.*,(.+)\)/, '$1'))
|
|
162
|
+
var hasAlpha = alpha > 0 && alpha < 1
|
|
163
|
+
p._default_material = createStockMaterial(defaultColor, hasAlpha ? { opacity: alpha, transparent: true } : undefined)
|
|
164
|
+
}
|
|
165
|
+
return p._default_material
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return Stock.defaultMaterial
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
get emptyMaterial(): THREE.Material {
|
|
172
|
+
const p = this.provider
|
|
173
|
+
|
|
174
|
+
if (p) {
|
|
175
|
+
if (!p._empty_material) {
|
|
176
|
+
const status = this._legendStatus
|
|
177
|
+
const defaultColor = status?.defaultColor || STOCK_COLOR
|
|
178
|
+
|
|
179
|
+
var alpha = Number(defaultColor.replace(/^.*,(.+)\)/, '$1'))
|
|
180
|
+
var opacity = (alpha > 0 && alpha < 1) ? alpha : 0.33
|
|
181
|
+
p._empty_material = createStockMaterial(defaultColor, { opacity, transparent: true })
|
|
182
|
+
}
|
|
183
|
+
return p._empty_material
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return Stock.defaultEmptyMaterial
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
build() {
|
|
190
|
+
super.build()
|
|
191
|
+
|
|
192
|
+
// InstancedMesh 모드에서는 개별 mesh를 생성하지 않음
|
|
193
|
+
if (this.isInstanced) return
|
|
194
|
+
|
|
195
|
+
var { width, height, depth } = this.model
|
|
196
|
+
|
|
197
|
+
this._hideEmptyStock = !!this.provider?.hideEmptyStock
|
|
198
|
+
|
|
199
|
+
this.createStock(width, height, depth)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
createStock(w: number, h: number, d: number) {
|
|
203
|
+
this.object3d.geometry = Stock.stockGeometry
|
|
204
|
+
this.object3d.material = this._hideEmptyStock ? this.emptyMaterial : this.userDefineDefaultMaterial
|
|
205
|
+
|
|
206
|
+
this.object3d.scale.set(w, d, h)
|
|
207
|
+
|
|
208
|
+
this.object3d.receiveShadow = true
|
|
209
|
+
|
|
210
|
+
this.object3d.castShadow = true
|
|
211
|
+
this.object3d.onBeforeRender = () => {
|
|
212
|
+
this.onBeforeRender()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
onchangeStockData(data: any) {
|
|
217
|
+
this._data = data
|
|
218
|
+
|
|
219
|
+
if (this.isInstanced) {
|
|
220
|
+
this._onchangeStockDataInstanced(data)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 개별 mesh 모드 (하위 호환)
|
|
225
|
+
this.object3d.userData = {
|
|
226
|
+
...this.object3d.userData,
|
|
227
|
+
data
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const stockStatus = this._legendStatus as
|
|
231
|
+
| { field: string; aggregation?: string; ranges: { min?: string | number; max?: string | number; color?: string }[] }
|
|
232
|
+
| undefined
|
|
233
|
+
if (!stockStatus) return
|
|
234
|
+
|
|
235
|
+
var statusField = stockStatus.field
|
|
236
|
+
var aggregation = stockStatus.aggregation || 'sum'
|
|
237
|
+
var ranges = stockStatus.ranges
|
|
238
|
+
|
|
239
|
+
if (!(statusField && ranges)) return
|
|
240
|
+
|
|
241
|
+
var items = data.items
|
|
242
|
+
|
|
243
|
+
if (!items) {
|
|
244
|
+
this.object3d.material = this._hideEmptyStock ? this.emptyMaterial : this.userDefineDefaultMaterial
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var statusValues: any[] = items
|
|
249
|
+
.map((item: any) => item && item[statusField])
|
|
250
|
+
.filter((value: any) => value !== undefined && value !== null)
|
|
251
|
+
|
|
252
|
+
if (statusValues.length === 0) {
|
|
253
|
+
this.object3d.material = this._hideEmptyStock ? this.emptyMaterial : this.userDefineDefaultMaterial
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
var status = this._aggregate(statusValues, aggregation)
|
|
258
|
+
|
|
259
|
+
var isInRanges = ranges.some((range, index) => {
|
|
260
|
+
let { min: minValue, max: maxValue } = range
|
|
261
|
+
|
|
262
|
+
var min: number | undefined =
|
|
263
|
+
minValue !== undefined && minValue !== null && minValue !== '' ? Number(minValue) : undefined
|
|
264
|
+
var max: number | undefined =
|
|
265
|
+
maxValue !== undefined && maxValue !== null && maxValue !== '' ? Number(maxValue) : undefined
|
|
266
|
+
|
|
267
|
+
var maxCheck = max === undefined ? true : max > status
|
|
268
|
+
var minCheck = min === undefined ? true : min <= status
|
|
269
|
+
|
|
270
|
+
if (maxCheck && minCheck) {
|
|
271
|
+
this.object3d.material = this.getMaterial(index)
|
|
272
|
+
return true
|
|
273
|
+
} else {
|
|
274
|
+
this.object3d.material = this._hideEmptyStock ? this.emptyMaterial : this.userDefineDefaultMaterial
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
if (!isInRanges) {
|
|
279
|
+
this.object3d.material = this.userDefineDefaultMaterial
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* InstancedMesh 모드에서의 데이터 변경 처리.
|
|
285
|
+
* 개별 mesh의 material 대신 instanceColor를 변경한다.
|
|
286
|
+
*/
|
|
287
|
+
private _onchangeStockDataInstanced(data: any) {
|
|
288
|
+
const stockStatus = this._legendStatus as
|
|
289
|
+
| { field: string; aggregation?: string; ranges: { min?: string | number; max?: string | number; color?: string }[] }
|
|
290
|
+
| undefined
|
|
291
|
+
if (!stockStatus) return
|
|
292
|
+
|
|
293
|
+
var statusField = stockStatus.field
|
|
294
|
+
var aggregation = stockStatus.aggregation || 'sum'
|
|
295
|
+
var ranges = stockStatus.ranges
|
|
296
|
+
|
|
297
|
+
if (!(statusField && ranges)) return
|
|
298
|
+
|
|
299
|
+
var items = data.items
|
|
300
|
+
|
|
301
|
+
if (!items) {
|
|
302
|
+
this._setInstanceColor(this._getDefaultColor())
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
var statusValues: any[] = items
|
|
307
|
+
.map((item: any) => item && item[statusField])
|
|
308
|
+
.filter((value: any) => value !== undefined && value !== null)
|
|
309
|
+
|
|
310
|
+
if (statusValues.length === 0) {
|
|
311
|
+
this._setInstanceColor(this._getDefaultColor())
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
var status = this._aggregate(statusValues, aggregation)
|
|
316
|
+
|
|
317
|
+
var matched = false
|
|
318
|
+
for (let index = 0; index < ranges.length; index++) {
|
|
319
|
+
const range = ranges[index]
|
|
320
|
+
let { min: minValue, max: maxValue } = range
|
|
321
|
+
|
|
322
|
+
var min: number | undefined =
|
|
323
|
+
minValue !== undefined && minValue !== null && minValue !== '' ? Number(minValue) : undefined
|
|
324
|
+
var max: number | undefined =
|
|
325
|
+
maxValue !== undefined && maxValue !== null && maxValue !== '' ? Number(maxValue) : undefined
|
|
326
|
+
|
|
327
|
+
var maxCheck = max === undefined ? true : max > status
|
|
328
|
+
var minCheck = min === undefined ? true : min <= status
|
|
329
|
+
|
|
330
|
+
if (maxCheck && minCheck) {
|
|
331
|
+
this._applyMaterialColor(index)
|
|
332
|
+
matched = true
|
|
333
|
+
break
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!matched) {
|
|
338
|
+
this._setInstanceColor(this._getDefaultColor())
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private _aggregate(values: any[], aggregation: string): number {
|
|
343
|
+
switch (aggregation) {
|
|
344
|
+
case 'sum':
|
|
345
|
+
return values.reduce((sum: number, val: any) => sum + Number(val), 0)
|
|
346
|
+
case 'avg':
|
|
347
|
+
return values.reduce((sum: number, val: any) => sum + Number(val), 0) / values.length
|
|
348
|
+
case 'min':
|
|
349
|
+
return Math.min(...values.map((val: any) => Number(val)))
|
|
350
|
+
case 'max':
|
|
351
|
+
return Math.max(...values.map((val: any) => Number(val)))
|
|
352
|
+
default:
|
|
353
|
+
return values.reduce((sum: number, val: any) => sum + Number(val), 0)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
onBeforeRender = () => {
|
|
358
|
+
if (!this.isInstanced) {
|
|
359
|
+
// 개별 mesh 모드
|
|
360
|
+
if (this._focused) {
|
|
361
|
+
var lastTime = performance.now() - this._focusedAt!
|
|
362
|
+
var progress = lastTime / 2000
|
|
363
|
+
this.object3d.rotation.y = 2 * Math.PI * progress
|
|
364
|
+
this.component.invalidate()
|
|
365
|
+
} else if (this._focusedAt) {
|
|
366
|
+
delete this._focusedAt
|
|
367
|
+
this.object3d.rotation.y = 0
|
|
368
|
+
this.component.invalidate()
|
|
369
|
+
}
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// InstancedMesh 모드: 해당 인스턴스의 matrix만 갱신
|
|
374
|
+
if (this._focused && this._instancedMesh && this._basePosition && this._baseScale) {
|
|
375
|
+
var lastTime = performance.now() - this._focusedAt!
|
|
376
|
+
var progress = lastTime / 2000
|
|
377
|
+
_quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 2 * Math.PI * progress)
|
|
378
|
+
_matrix.compose(
|
|
379
|
+
_position.set(this._basePosition.x, this._basePosition.y, this._basePosition.z),
|
|
380
|
+
_quaternion,
|
|
381
|
+
_scale.set(this._baseScale.x, this._baseScale.y, this._baseScale.z)
|
|
382
|
+
)
|
|
383
|
+
this._instancedMesh.setMatrixAt(this._instanceIndex, _matrix)
|
|
384
|
+
this._instancedMesh.instanceMatrix.needsUpdate = true
|
|
385
|
+
this.component.invalidate()
|
|
386
|
+
} else if (this._focusedAt && this._instancedMesh && this._basePosition && this._baseScale) {
|
|
387
|
+
delete this._focusedAt
|
|
388
|
+
_matrix.compose(
|
|
389
|
+
_position.set(this._basePosition.x, this._basePosition.y, this._basePosition.z),
|
|
390
|
+
_quaternion.identity(),
|
|
391
|
+
_scale.set(this._baseScale.x, this._baseScale.y, this._baseScale.z)
|
|
392
|
+
)
|
|
393
|
+
this._instancedMesh.setMatrixAt(this._instanceIndex, _matrix)
|
|
394
|
+
this._instancedMesh.instanceMatrix.needsUpdate = true
|
|
395
|
+
this.component.invalidate()
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
onmouseup(e: MouseEvent, _context: unknown, callback: (arg: { data: any; location: string }) => void) {
|
|
400
|
+
const data = this._data || this.object3d?.userData?.data
|
|
401
|
+
|
|
402
|
+
if (callback && typeof callback == 'function') {
|
|
403
|
+
callback({
|
|
404
|
+
...data,
|
|
405
|
+
color: this._getCurrentColorHex()
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private _getCurrentColorHex(): string {
|
|
411
|
+
if (this.isInstanced && this._instancedMesh) {
|
|
412
|
+
this._instancedMesh.getColorAt(this._instanceIndex, _color)
|
|
413
|
+
return '#' + _color.getHexString()
|
|
414
|
+
}
|
|
415
|
+
return '#' + ((this.object3d.material as any).color?.getHexString() || 'ccaa76')
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// all update functions should be intentionally empty. important!
|
|
419
|
+
updateTransform() {}
|
|
420
|
+
updateDimension() {}
|
|
421
|
+
updateAlpha() {}
|
|
422
|
+
updateFillStyle() {}
|
|
423
|
+
updateStrokeStyle() {}
|
|
424
|
+
updateHidden() {}
|
|
425
|
+
updateText() {}
|
|
426
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* RackCell has no geometry of its own. Its sole 3D purpose is to provide
|
|
8
8
|
* an Object3D that carriers can be attached to (via Three.js `.attach()`),
|
|
9
9
|
* placed at the exact cell position within the rack. The position is derived
|
|
10
|
-
* from the parent
|
|
10
|
+
* from the parent Rack's CellMap (by cellId), not from 2D state fields —
|
|
11
11
|
* rack cells occupy 3D levels that have no 2D analogue.
|
|
12
12
|
*
|
|
13
13
|
* updateTransform() override: things-scene's standard updateTransform
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
import * as THREE from 'three'
|
|
20
20
|
import { RealObjectGroup } from '@hatiolab/things-scene'
|
|
21
21
|
|
|
22
|
-
export class
|
|
22
|
+
export class StorageCell3D extends RealObjectGroup {
|
|
23
23
|
build() {
|
|
24
24
|
super.build()
|
|
25
25
|
this._repositionFromCellMap()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*
|
|
4
|
-
* RackCell — a single storage slot within an
|
|
4
|
+
* RackCell — a single storage slot within an Rack.
|
|
5
5
|
*
|
|
6
6
|
* A RackCell is a virtual component: it occupies a specific (bay, row, level)
|
|
7
7
|
* coordinate within the parent rack and acts as a CarrierHolder for one carrier
|
|
8
8
|
* (or several, depending on `cellType`).
|
|
9
9
|
*
|
|
10
|
-
* The crane (
|
|
10
|
+
* The crane (or any picker) navigates toward a RackCell as its `place()` destination —
|
|
11
11
|
* because the rack cell is a discrete component, Mover.moveTo() can target it
|
|
12
12
|
* directly and arrive at exactly the right bay × level position.
|
|
13
13
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* positioned within the rack's coordinate space (RackCell3D handles
|
|
17
17
|
* this via updateTransform override).
|
|
18
18
|
*
|
|
19
|
-
* Lifecycle:
|
|
19
|
+
* Lifecycle: Rack._buildCells() instantiates RackCell children.
|
|
20
20
|
* Do not create RackCell components manually — they are managed by the rack.
|
|
21
21
|
*
|
|
22
22
|
* Domain aliases:
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import type { State, Material3D } from '@hatiolab/things-scene'
|
|
36
36
|
import { CarrierHolder, type AttachFrame } from '@operato/scene-base'
|
|
37
37
|
|
|
38
|
-
import {
|
|
38
|
+
import { StorageCell3D } from './storage-cell-3d.js'
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* How many carriers a cell can hold simultaneously.
|
|
@@ -43,13 +43,19 @@ import { RackCell3D } from './rack-cell-3d.js'
|
|
|
43
43
|
* - multi: small stack (up to 4, e.g. a multi-deep tray)
|
|
44
44
|
* - bulk: unlimited (e.g. a floor area measured in slots)
|
|
45
45
|
*/
|
|
46
|
-
export type
|
|
46
|
+
export type StorageCellType = 'single' | 'multi' | 'bulk'
|
|
47
47
|
|
|
48
48
|
/** RackCell 컴포넌트 state */
|
|
49
|
-
export interface
|
|
49
|
+
export interface StorageCellState extends State {
|
|
50
50
|
// ── 식별 ──
|
|
51
51
|
cellId?: string
|
|
52
|
-
cellType?:
|
|
52
|
+
cellType?: StorageCellType
|
|
53
|
+
/**
|
|
54
|
+
* 자동 할당된 location ID — RackTable.assignLocations() 가 set. 외부 시스템
|
|
55
|
+
* (WMS, picker 명령 등) 이 cell 을 지칭하는 사람-친화 ID. RackTable 없이
|
|
56
|
+
* 단독으로 Rack 을 사용할 땐 unset.
|
|
57
|
+
*/
|
|
58
|
+
locationId?: string
|
|
53
59
|
|
|
54
60
|
// ── 3D 재질 ──
|
|
55
61
|
material3d?: Material3D
|
|
@@ -79,11 +85,11 @@ const NATURE: ComponentNature = {
|
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
],
|
|
82
|
-
help: 'scene/component/
|
|
88
|
+
help: 'scene/component/storage-cell'
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/**
|
|
86
|
-
* RackCell — single-slot storage cell inside an
|
|
92
|
+
* RackCell — single-slot storage cell inside an Rack.
|
|
87
93
|
*
|
|
88
94
|
* Mixin chain: CarrierHolder(ContainerAbstract)
|
|
89
95
|
* - CarrierHolder: publishes attachPointFor(), gates containable() to Carriables
|
|
@@ -93,9 +99,9 @@ const NATURE: ComponentNature = {
|
|
|
93
99
|
* CellMap (via updateTransform override), bypassing things-scene's standard
|
|
94
100
|
* 2D→3D coordinate mapping which cannot express 3D levels.
|
|
95
101
|
*/
|
|
96
|
-
@sceneComponent('
|
|
102
|
+
@sceneComponent('storage-cell')
|
|
97
103
|
export default class RackCell extends CarrierHolder(ContainerAbstract) {
|
|
98
|
-
declare state:
|
|
104
|
+
declare state: StorageCellState
|
|
99
105
|
|
|
100
106
|
// ── Identification ────────────────────────────────────────────────────────
|
|
101
107
|
|
|
@@ -103,7 +109,7 @@ export default class RackCell extends CarrierHolder(ContainerAbstract) {
|
|
|
103
109
|
return this.state.cellId ?? ''
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
get cellType():
|
|
112
|
+
get cellType(): StorageCellType {
|
|
107
113
|
return this.state.cellType ?? 'single'
|
|
108
114
|
}
|
|
109
115
|
|
|
@@ -226,7 +232,7 @@ export default class RackCell extends CarrierHolder(ContainerAbstract) {
|
|
|
226
232
|
// ── 3D ───────────────────────────────────────────────────────────────────
|
|
227
233
|
|
|
228
234
|
buildRealObject(): RealObject | undefined {
|
|
229
|
-
return new
|
|
235
|
+
return new StorageCell3D(this)
|
|
230
236
|
}
|
|
231
237
|
}
|
|
232
238
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Rack 3D — multi-level storage shelf system.
|
|
5
5
|
*
|
|
6
6
|
* LO-POLY but visually unambiguous as a rack. The signature parts:
|
|
7
7
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* this is a load-bearing storage rack, not just a generic frame)
|
|
14
14
|
*
|
|
15
15
|
* No floor / ceiling panels — the rack is open by design (cells are accessed
|
|
16
|
-
* by
|
|
16
|
+
* by a picker from the front side).
|
|
17
17
|
*
|
|
18
18
|
* Cargo (pallets, boxes) added as children render at their own z position.
|
|
19
19
|
* The rack itself is purely structural geometry.
|
|
@@ -27,7 +27,7 @@ const POST_COLOR = 0x6a7080
|
|
|
27
27
|
const BEAM_COLOR = 0x556070
|
|
28
28
|
const BRACE_COLOR = 0x556070
|
|
29
29
|
|
|
30
|
-
export class
|
|
30
|
+
export class StorageRack3D extends RealObjectGroup {
|
|
31
31
|
build() {
|
|
32
32
|
super.build()
|
|
33
33
|
|