@operato/scene-storage 10.0.0-beta.48 → 10.0.0-beta.50

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.d.ts +9 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/picking-station-3d.d.ts +20 -0
  6. package/dist/picking-station-3d.js +162 -0
  7. package/dist/picking-station-3d.js.map +1 -0
  8. package/dist/picking-station.d.ts +56 -0
  9. package/dist/picking-station.js +212 -0
  10. package/dist/picking-station.js.map +1 -0
  11. package/dist/rack-capability.d.ts +11 -0
  12. package/dist/rack-capability.js +25 -0
  13. package/dist/rack-capability.js.map +1 -0
  14. package/dist/rack-grid.js +3 -10
  15. package/dist/rack-grid.js.map +1 -1
  16. package/dist/spot.d.ts +19 -1
  17. package/dist/spot.js +63 -1
  18. package/dist/spot.js.map +1 -1
  19. package/dist/stockpile-3d.d.ts +55 -0
  20. package/dist/stockpile-3d.js +387 -0
  21. package/dist/stockpile-3d.js.map +1 -0
  22. package/dist/stockpile-grid-3d.d.ts +30 -0
  23. package/dist/stockpile-grid-3d.js +301 -0
  24. package/dist/stockpile-grid-3d.js.map +1 -0
  25. package/dist/stockpile-grid.d.ts +88 -0
  26. package/dist/stockpile-grid.js +429 -0
  27. package/dist/stockpile-grid.js.map +1 -0
  28. package/dist/stockpile.d.ts +133 -0
  29. package/dist/stockpile.js +439 -0
  30. package/dist/stockpile.js.map +1 -0
  31. package/dist/storage-rack.d.ts +12 -0
  32. package/dist/storage-rack.js +20 -10
  33. package/dist/storage-rack.js.map +1 -1
  34. package/dist/templates/index.d.ts +80 -0
  35. package/dist/templates/index.js +7 -1
  36. package/dist/templates/index.js.map +1 -1
  37. package/dist/templates/picking-station.d.ts +20 -0
  38. package/dist/templates/picking-station.js +22 -0
  39. package/dist/templates/picking-station.js.map +1 -0
  40. package/dist/templates/stockpile-grid.d.ts +37 -0
  41. package/dist/templates/stockpile-grid.js +38 -0
  42. package/dist/templates/stockpile-grid.js.map +1 -0
  43. package/dist/templates/stockpile.d.ts +29 -0
  44. package/dist/templates/stockpile.js +31 -0
  45. package/dist/templates/stockpile.js.map +1 -0
  46. package/package.json +3 -3
  47. package/src/index.ts +14 -0
  48. package/src/picking-station-3d.ts +164 -0
  49. package/src/picking-station.ts +243 -0
  50. package/src/rack-capability.ts +26 -0
  51. package/src/rack-grid.ts +3 -8
  52. package/src/spot.ts +62 -0
  53. package/src/stockpile-3d.ts +412 -0
  54. package/src/stockpile-grid-3d.ts +327 -0
  55. package/src/stockpile-grid.ts +456 -0
  56. package/src/stockpile.ts +508 -0
  57. package/src/storage-rack.ts +21 -8
  58. package/src/templates/index.ts +7 -1
  59. package/src/templates/picking-station.ts +23 -0
  60. package/src/templates/stockpile-grid.ts +39 -0
  61. package/src/templates/stockpile.ts +32 -0
  62. package/test/test-rack-capability.ts +51 -0
  63. package/translations/en.json +18 -6
  64. package/translations/ja.json +18 -6
  65. package/translations/ko.json +17 -5
  66. package/translations/ms.json +18 -6
  67. package/translations/zh.json +17 -5
  68. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,429 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * StockpileGrid — cols × rows cell 들로 분할된 평치 영역. cell 들은 grid 공통 설정
5
+ * (carrierPreset/stackPattern/크기/capacity 등)을 *_모두 공유_* 하고, 각 cell 은 자기
6
+ * 위치(col, row)에서의 records 만 별도 가진다. cell 별 override 는 의미가 없어
7
+ * 제거됨 — cell 별 다른 설정이 진짜 필요한 경우엔 Stockpile 컴포넌트를 따로 두는
8
+ * 것이 자연스럽다.
9
+ *
10
+ * state.data = grid 의 cell 들 배열 (각 element 는 cell-단위 stockpile-like 데이터).
11
+ * data: [
12
+ * { col: 0, row: 0, data: [{ id, ... }, ...] },
13
+ * { col: 1, row: 0, data: [...] },
14
+ * ...
15
+ * ]
16
+ * cell.data 가 그 cell 의 records. 외부 데이터 매핑은 이 배열을 target.
17
+ */
18
+ import { __decorate } from "tslib";
19
+ import * as THREE from 'three';
20
+ import { Component, ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
21
+ import { CarrierHolder, Placeable, SlotTarget } from '@operato/scene-base';
22
+ import { StockpileGrid3D } from './stockpile-grid-3d.js';
23
+ const NATURE = {
24
+ mutable: false,
25
+ resizable: true,
26
+ rotatable: true,
27
+ properties: [
28
+ { type: 'number', label: 'cols', name: 'cols' },
29
+ { type: 'number', label: 'rows', name: 'rows' },
30
+ { type: 'number', label: 'cell-width', name: 'cellWidth' },
31
+ { type: 'number', label: 'cell-height', name: 'cellHeight' },
32
+ { type: 'select', label: 'stack-pattern', name: 'stackPattern',
33
+ property: { options: ['row', 'staggered', 'pyramid', 'column', 'pile'] } },
34
+ { type: 'select', label: 'carrier-preset', name: 'carrierPreset',
35
+ property: { options: ['box', 'pallet', 'drum', 'sack', 'crate', 'bale'] } },
36
+ { type: 'number', label: 'carrier-width', name: 'carrierWidth' },
37
+ { type: 'number', label: 'carrier-height', name: 'carrierHeight' },
38
+ { type: 'number', label: 'carrier-depth', name: 'carrierDepth' },
39
+ { type: 'number', label: 'carrier-gap', name: 'carrierGap' },
40
+ { type: 'number', label: 'capacity', name: 'capacity' },
41
+ { type: 'number', label: 'stack-height-limit', name: 'stackHeightLimit' },
42
+ { type: 'select', label: 'pick-policy', name: 'pickPolicy',
43
+ property: { options: ['lifo', 'fifo'] } },
44
+ { type: 'id-input', label: 'popup-ref', name: 'popupRef',
45
+ property: { component: 'popup' } },
46
+ { type: 'id-input', label: 'legend-target', name: 'legendTarget',
47
+ property: { component: 'legend' },
48
+ placeholder: '미명시 시 scene 의 legend 자동 발견' }
49
+ ],
50
+ help: 'scene/component/stockpile-grid'
51
+ };
52
+ let StockpileGrid = class StockpileGrid extends CarrierHolder(Placeable(ContainerAbstract)) {
53
+ static placement = 'floor';
54
+ static align = 'bottom';
55
+ static defaultDepth = (_h) => 5;
56
+ get nature() { return NATURE; }
57
+ get anchors() { return []; }
58
+ // ── grid 좌표 helpers ─────────────────────────────────────
59
+ get cols() { return Math.max(1, Math.floor(this.state.cols ?? 3)); }
60
+ get rows() { return Math.max(1, Math.floor(this.state.rows ?? 3)); }
61
+ get cellW() { return this.state.cellWidth ?? 80; }
62
+ get cellH() { return this.state.cellHeight ?? 80; }
63
+ cellIdOf(col, row) { return `${col}-${row}`; }
64
+ parseCellId(slotId) {
65
+ const m = slotId.match(/^(\d+)-(\d+)$/);
66
+ if (!m)
67
+ return null;
68
+ return { col: Number(m[1]), row: Number(m[2]) };
69
+ }
70
+ /** state.data 에서 (col,row) cell 찾기. */
71
+ _findCell(col, row) {
72
+ return (this.state.data ?? []).find(c => c.col === col && c.row === row);
73
+ }
74
+ /** cell 의 records (없으면 빈 배열). */
75
+ recordsOf(col, row) {
76
+ return this._findCell(col, row)?.data ?? [];
77
+ }
78
+ /** capacity 는 모든 cell 공통. */
79
+ capacityOf(_col, _row) {
80
+ return this.state.capacity;
81
+ }
82
+ // ── SlottedHolder — cell 별 slot ─────────────────────────────
83
+ slotIds() {
84
+ const out = [];
85
+ for (let r = 0; r < this.rows; r++) {
86
+ for (let c = 0; c < this.cols; c++)
87
+ out.push(this.cellIdOf(c, r));
88
+ }
89
+ return out;
90
+ }
91
+ hasCarrierAt(slotId) {
92
+ const p = this.parseCellId(slotId);
93
+ return !!p && this.recordsOf(p.col, p.row).length > 0;
94
+ }
95
+ canReceiveAt(slotId, _carrier) {
96
+ const p = this.parseCellId(slotId);
97
+ if (!p)
98
+ return false;
99
+ const cap = this.capacityOf(p.col, p.row);
100
+ if (typeof cap === 'number' && this.recordsOf(p.col, p.row).length >= cap)
101
+ return false;
102
+ return true;
103
+ }
104
+ occupiedSlotIds() {
105
+ const out = [];
106
+ for (let r = 0; r < this.rows; r++)
107
+ for (let c = 0; c < this.cols; c++) {
108
+ if (this.recordsOf(c, r).length > 0)
109
+ out.push(this.cellIdOf(c, r));
110
+ }
111
+ return out;
112
+ }
113
+ emptySlotIds() {
114
+ const out = [];
115
+ for (let r = 0; r < this.rows; r++)
116
+ for (let c = 0; c < this.cols; c++) {
117
+ if (this.canReceiveAt(this.cellIdOf(c, r)))
118
+ out.push(this.cellIdOf(c, r));
119
+ }
120
+ return out;
121
+ }
122
+ obtainCarrier(slotId) {
123
+ const p = this.parseCellId(slotId);
124
+ if (!p)
125
+ return null;
126
+ const recs = this.recordsOf(p.col, p.row);
127
+ if (recs.length === 0)
128
+ return null;
129
+ const records = [...recs];
130
+ const policy = (this.state.pickPolicy ?? 'lifo');
131
+ const record = policy === 'fifo' ? records.shift() : records.pop();
132
+ this._setCellRecords(p.col, p.row, records);
133
+ return this._materializeCarrier(record, p.col, p.row);
134
+ }
135
+ async receiveAt(slotId, carrier, _options) {
136
+ const p = this.parseCellId(slotId);
137
+ if (!p)
138
+ return;
139
+ const cstate = carrier?.state ?? {};
140
+ const cid = cstate.id ?? `stkg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
141
+ const record = {
142
+ id: String(cid),
143
+ ...(cstate.type ? { type: cstate.type } : {}),
144
+ ...(typeof cstate.width === 'number' ? { width: cstate.width } : {}),
145
+ ...(typeof cstate.height === 'number' ? { height: cstate.height } : {}),
146
+ ...(typeof cstate.depth === 'number' ? { depth: cstate.depth } : {})
147
+ };
148
+ const records = [...this.recordsOf(p.col, p.row), record];
149
+ this._setCellRecords(p.col, p.row, records);
150
+ carrier?.dispose?.();
151
+ }
152
+ async accept(carrier, options) {
153
+ const empty = this.emptySlotIds();
154
+ if (empty.length === 0)
155
+ return;
156
+ return this.receiveAt(empty[0], carrier, options);
157
+ }
158
+ async receive(carrier, options) {
159
+ return this.accept(carrier, options);
160
+ }
161
+ slotTargetAt(slotId) { return new SlotTarget(this, slotId); }
162
+ getSlotAttachObject3d(slotId) {
163
+ return this._realObject?.getCellPadMesh?.(slotId);
164
+ }
165
+ attachPointFor(carrier) {
166
+ const ro = this._realObject;
167
+ const frame = ro?.getAttachFrame?.();
168
+ if (!frame)
169
+ return null;
170
+ const carrierDepth = resolveDepth(carrier);
171
+ return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } };
172
+ }
173
+ _setCellRecords(col, row, records) {
174
+ const cells = [...(this.state.data ?? [])];
175
+ const idx = cells.findIndex(c => c.col === col && c.row === row);
176
+ if (idx === -1)
177
+ cells.push({ col, row, data: records });
178
+ else
179
+ cells[idx] = { ...cells[idx], data: records };
180
+ this.state.data = cells;
181
+ this._realObject?.update?.();
182
+ }
183
+ _materializeCarrier(record, col, row) {
184
+ const preset = (this.state.carrierPreset ?? 'box');
185
+ const PRESET_TO_TYPE = {
186
+ box: 'box', pallet: 'pallet', crate: 'box',
187
+ drum: 'parcel', sack: 'parcel', bale: 'parcel'
188
+ };
189
+ const carrierType = record.type ?? PRESET_TO_TYPE[preset] ?? 'parcel';
190
+ const CarrierClass = Component.register(carrierType);
191
+ if (!CarrierClass) {
192
+ console.warn(`[stockpile-grid] carrier type "${carrierType}" 미등록`);
193
+ return null;
194
+ }
195
+ const cw = this.state.carrierWidth ?? 30;
196
+ const ch = this.state.carrierHeight ?? 30;
197
+ const cd = this.state.carrierDepth ?? 22;
198
+ const { id: _id, refid: _refid, transform: _tf, ...recordCopy } = record;
199
+ const cellCenterX = col * this.cellW + this.cellW / 2;
200
+ const cellCenterZ = row * this.cellH + this.cellH / 2;
201
+ const carrierState = {
202
+ ...recordCopy,
203
+ type: carrierType,
204
+ width: cw,
205
+ height: ch,
206
+ depth: cd,
207
+ refid: _nextStockpileGridCarrierRefid(),
208
+ left: cellCenterX - cw / 2,
209
+ top: cellCenterZ - ch / 2
210
+ };
211
+ const carrier = new CarrierClass(carrierState, this._app);
212
+ this.addComponent(carrier, { silent: true });
213
+ void carrier.realObject;
214
+ carrier.applyHolderAttachPoint?.();
215
+ return carrier;
216
+ }
217
+ // ── Legend ─────────────────────────────────────────────────
218
+ _legendTarget;
219
+ get legendTarget() {
220
+ if (this._legendTarget)
221
+ return this._legendTarget;
222
+ const id = this.state.legendTarget;
223
+ if (id) {
224
+ const found = (this.root)?.findById?.(id);
225
+ if (found) {
226
+ this._legendTarget = found;
227
+ found.on?.('change', this._onLegendChanged, this);
228
+ return found;
229
+ }
230
+ }
231
+ const visit = (node) => {
232
+ if (!node)
233
+ return undefined;
234
+ if (node.state?.type === 'legend')
235
+ return node;
236
+ const children = node.components;
237
+ if (children)
238
+ for (const c of children) {
239
+ const r = visit(c);
240
+ if (r)
241
+ return r;
242
+ }
243
+ return undefined;
244
+ };
245
+ const found = visit(this.root);
246
+ if (found) {
247
+ this._legendTarget = found;
248
+ found.on?.('change', this._onLegendChanged, this);
249
+ }
250
+ return found;
251
+ }
252
+ _onLegendChanged = () => { ; this._realObject?.update?.(); };
253
+ resolveLegendColor(record) {
254
+ const legend = this.legendTarget;
255
+ if (!legend)
256
+ return undefined;
257
+ const status = legend.getState?.('status') ?? legend.state?.status;
258
+ if (!status)
259
+ return undefined;
260
+ const field = status.field;
261
+ const ranges = status.ranges;
262
+ if (!field || !Array.isArray(ranges))
263
+ return undefined;
264
+ const value = record?.[field];
265
+ if (value === undefined || value === null)
266
+ return status.defaultColor;
267
+ for (const range of ranges) {
268
+ if (!range)
269
+ continue;
270
+ if (range.value !== undefined) {
271
+ if (range.value === value)
272
+ return range.color;
273
+ continue;
274
+ }
275
+ const num = Number(value);
276
+ if (!Number.isFinite(num))
277
+ continue;
278
+ const min = range.min !== undefined && range.min !== '' ? Number(range.min) : undefined;
279
+ const max = range.max !== undefined && range.max !== '' ? Number(range.max) : undefined;
280
+ const minOk = min === undefined || num >= min;
281
+ const maxOk = max === undefined || num < max;
282
+ if (minOk && maxOk)
283
+ return range.color;
284
+ }
285
+ return status.defaultColor;
286
+ }
287
+ // ── Popup + click ─────────────────────────────────────────────
288
+ get eventMap() {
289
+ return { '(self)': { '(self)': { click: this._onGridClick } } };
290
+ }
291
+ _onGridClick = (mouseEvent) => {
292
+ if (!this.app?.isViewMode)
293
+ return;
294
+ const hit = this._raycastHit(mouseEvent);
295
+ if (!hit)
296
+ return;
297
+ const slotId = hit.object?.userData?.slotId;
298
+ this._invokePopup(slotId);
299
+ };
300
+ _invokePopup(slotId) {
301
+ const popupRefId = this.state.popupRef;
302
+ if (!popupRefId)
303
+ return;
304
+ const popupComp = this.root?.findById?.(popupRefId);
305
+ if (!popupComp || typeof popupComp.openPopup !== 'function')
306
+ return;
307
+ if (slotId) {
308
+ const p = this.parseCellId(slotId);
309
+ const anchor = this.slotTargetAt(slotId);
310
+ popupComp.openPopup({
311
+ cellId: slotId,
312
+ col: p?.col, row: p?.row,
313
+ records: p ? this.recordsOf(p.col, p.row) : [],
314
+ capacity: p ? this.capacityOf(p.col, p.row) : undefined
315
+ }, { anchor });
316
+ }
317
+ else {
318
+ const anchor = this.slotTargetAt(this.cellIdOf(0, 0));
319
+ popupComp.openPopup({
320
+ componentId: this.state.id,
321
+ cols: this.cols, rows: this.rows
322
+ }, { anchor });
323
+ }
324
+ }
325
+ _raycastHit(mouseEvent) {
326
+ const ro = this._realObject;
327
+ if (!ro?.object3d)
328
+ return undefined;
329
+ const tc = ro.threeContainer;
330
+ if (!tc)
331
+ return undefined;
332
+ const cap = tc._threeCapability ?? tc._capability;
333
+ let intersects;
334
+ if (cap?.getObjectsByRaycast)
335
+ intersects = cap.getObjectsByRaycast();
336
+ if (!intersects || intersects.length === 0) {
337
+ const scene = tc.scene3d;
338
+ const renderer = tc.renderer3d;
339
+ const camera = tc.activeCamera3d ??
340
+ cap?.activeCamera ??
341
+ cap?.camera;
342
+ const canvas = renderer?.domElement;
343
+ if (!scene || !canvas || !camera)
344
+ return undefined;
345
+ const rect = canvas.getBoundingClientRect();
346
+ if (rect.width === 0 || rect.height === 0)
347
+ return undefined;
348
+ const ndc = new THREE.Vector2(((mouseEvent.clientX - rect.left) / rect.width) * 2 - 1, -((mouseEvent.clientY - rect.top) / rect.height) * 2 + 1);
349
+ const raycaster = new THREE.Raycaster();
350
+ raycaster.setFromCamera(ndc, camera);
351
+ intersects = raycaster.intersectObjects(scene.children, true);
352
+ }
353
+ if (!intersects || intersects.length === 0)
354
+ return undefined;
355
+ const closest = intersects[0];
356
+ let obj = closest.object;
357
+ while (obj) {
358
+ if (obj.userData?.context === ro)
359
+ return closest;
360
+ obj = obj.parent;
361
+ }
362
+ return undefined;
363
+ }
364
+ // ── 2D render ─────────────────────────────────────────────
365
+ render(ctx) {
366
+ const { left = 0, top = 0 } = this.state;
367
+ const totalW = this.cellW * this.cols;
368
+ const totalH = this.cellH * this.rows;
369
+ const fillStyle = this.state.fillStyle || '#c89c5c';
370
+ const strokeStyle = this.state.strokeStyle || '#7a5a2e';
371
+ ctx.save();
372
+ ctx.fillStyle = fillStyle;
373
+ ctx.globalAlpha = 0.15;
374
+ ctx.fillRect(left, top, totalW, totalH);
375
+ ctx.restore();
376
+ ctx.save();
377
+ ctx.strokeStyle = strokeStyle;
378
+ ctx.lineWidth = 1.2;
379
+ ctx.strokeRect(left + 0.6, top + 0.6, totalW - 1.2, totalH - 1.2);
380
+ for (let c = 1; c < this.cols; c++) {
381
+ const x = left + c * this.cellW;
382
+ ctx.beginPath();
383
+ ctx.moveTo(x, top);
384
+ ctx.lineTo(x, top + totalH);
385
+ ctx.stroke();
386
+ }
387
+ for (let r = 1; r < this.rows; r++) {
388
+ const y = top + r * this.cellH;
389
+ ctx.beginPath();
390
+ ctx.moveTo(left, y);
391
+ ctx.lineTo(left + totalW, y);
392
+ ctx.stroke();
393
+ }
394
+ ctx.restore();
395
+ ctx.save();
396
+ const fontSize = Math.min(this.cellW, this.cellH) * 0.22;
397
+ ctx.fillStyle = '#333';
398
+ ctx.font = `bold ${fontSize}px sans-serif`;
399
+ ctx.textAlign = 'center';
400
+ ctx.textBaseline = 'middle';
401
+ for (let r = 0; r < this.rows; r++)
402
+ for (let c = 0; c < this.cols; c++) {
403
+ const count = this.recordsOf(c, r).length;
404
+ if (count === 0)
405
+ continue;
406
+ ctx.fillText(`${count}`, left + (c + 0.5) * this.cellW, top + (r + 0.5) * this.cellH);
407
+ }
408
+ ctx.restore();
409
+ }
410
+ buildRealObject() {
411
+ return new StockpileGrid3D(this);
412
+ }
413
+ };
414
+ StockpileGrid = __decorate([
415
+ sceneComponent('stockpile-grid')
416
+ ], StockpileGrid);
417
+ export default StockpileGrid;
418
+ let _stockpileGridCarrierSeq = 0;
419
+ function _nextStockpileGridCarrierRefid() {
420
+ return 850000 + (_stockpileGridCarrierSeq++);
421
+ }
422
+ function resolveDepth(c) {
423
+ const eff = c._realObject?.effectiveDepth;
424
+ if (typeof eff === 'number' && Number.isFinite(eff))
425
+ return eff;
426
+ const d = c?.state?.depth;
427
+ return typeof d === 'number' && Number.isFinite(d) ? d : 0;
428
+ }
429
+ //# sourceMappingURL=stockpile-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stockpile-grid.js","sourceRoot":"","sources":["../src/stockpile-grid.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * StockpileGrid — cols × rows cell 들로 분할된 평치 영역. cell 들은 grid 공통 설정\n * (carrierPreset/stackPattern/크기/capacity 등)을 *_모두 공유_* 하고, 각 cell 은 자기\n * 위치(col, row)에서의 records 만 별도 가진다. cell 별 override 는 의미가 없어\n * 제거됨 — cell 별 다른 설정이 진짜 필요한 경우엔 Stockpile 컴포넌트를 따로 두는\n * 것이 자연스럽다.\n *\n * state.data = grid 의 cell 들 배열 (각 element 는 cell-단위 stockpile-like 데이터).\n * data: [\n * { col: 0, row: 0, data: [{ id, ... }, ...] },\n * { col: 1, row: 0, data: [...] },\n * ...\n * ]\n * cell.data 가 그 cell 의 records. 외부 데이터 매핑은 이 배열을 target.\n */\n\nimport * as THREE from 'three'\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n SlotTarget,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { StockpileGrid3D } from './stockpile-grid-3d.js'\nimport type { StackPattern, CarrierPreset, StockpileRecord, PickPolicy } from './stockpile.js'\n\n/** grid 의 한 cell — 위치 + 그 cell 의 records. */\nexport interface StockpileGridCell {\n col: number\n row: number\n data?: StockpileRecord[]\n}\n\nexport interface StockpileGridState extends State {\n cols?: number\n rows?: number\n /** 한 cell 의 평면 크기 (전체 width/height = cellWidth*cols, cellHeight*rows). */\n cellWidth?: number\n cellHeight?: number\n\n /** grid 의 cell 들 — 각 cell 은 { col, row, data: Record[] }. */\n data?: StockpileGridCell[]\n\n // ── 공통 carrier 설정 (모든 cell 공유) ──\n stackPattern?: StackPattern\n carrierPreset?: CarrierPreset\n carrierWidth?: number\n carrierHeight?: number\n carrierDepth?: number\n carrierGap?: number\n capacity?: number // cell 별 capacity (모든 cell 공통)\n stackHeightLimit?: number\n pickPolicy?: PickPolicy\n\n popupRef?: string\n legendTarget?: string\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n { type: 'number', label: 'cols', name: 'cols' },\n { type: 'number', label: 'rows', name: 'rows' },\n { type: 'number', label: 'cell-width', name: 'cellWidth' },\n { type: 'number', label: 'cell-height', name: 'cellHeight' },\n { type: 'select', label: 'stack-pattern', name: 'stackPattern',\n property: { options: ['row', 'staggered', 'pyramid', 'column', 'pile'] } },\n { type: 'select', label: 'carrier-preset', name: 'carrierPreset',\n property: { options: ['box', 'pallet', 'drum', 'sack', 'crate', 'bale'] } },\n { type: 'number', label: 'carrier-width', name: 'carrierWidth' },\n { type: 'number', label: 'carrier-height', name: 'carrierHeight' },\n { type: 'number', label: 'carrier-depth', name: 'carrierDepth' },\n { type: 'number', label: 'carrier-gap', name: 'carrierGap' },\n { type: 'number', label: 'capacity', name: 'capacity' },\n { type: 'number', label: 'stack-height-limit', name: 'stackHeightLimit' },\n { type: 'select', label: 'pick-policy', name: 'pickPolicy',\n property: { options: ['lifo', 'fifo'] } },\n { type: 'id-input', label: 'popup-ref', name: 'popupRef',\n property: { component: 'popup' } },\n { type: 'id-input', label: 'legend-target', name: 'legendTarget',\n property: { component: 'legend' },\n placeholder: '미명시 시 scene 의 legend 자동 발견' }\n ],\n help: 'scene/component/stockpile-grid'\n}\n\n@sceneComponent('stockpile-grid')\nexport default class StockpileGrid extends CarrierHolder(Placeable(ContainerAbstract)) {\n declare state: StockpileGridState\n declare _realObject?: StockpileGrid3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 5\n\n get nature(): ComponentNature { return NATURE }\n get anchors() { return [] }\n\n // ── grid 좌표 helpers ─────────────────────────────────────\n get cols(): number { return Math.max(1, Math.floor(this.state.cols ?? 3)) }\n get rows(): number { return Math.max(1, Math.floor(this.state.rows ?? 3)) }\n get cellW(): number { return this.state.cellWidth ?? 80 }\n get cellH(): number { return this.state.cellHeight ?? 80 }\n\n cellIdOf(col: number, row: number): string { return `${col}-${row}` }\n parseCellId(slotId: string): { col: number; row: number } | null {\n const m = slotId.match(/^(\\d+)-(\\d+)$/)\n if (!m) return null\n return { col: Number(m[1]), row: Number(m[2]) }\n }\n\n /** state.data 에서 (col,row) cell 찾기. */\n private _findCell(col: number, row: number): StockpileGridCell | undefined {\n return (this.state.data ?? []).find(c => c.col === col && c.row === row)\n }\n\n /** cell 의 records (없으면 빈 배열). */\n recordsOf(col: number, row: number): ReadonlyArray<StockpileRecord> {\n return this._findCell(col, row)?.data ?? []\n }\n\n /** capacity 는 모든 cell 공통. */\n capacityOf(_col: number, _row: number): number | undefined {\n return this.state.capacity\n }\n\n // ── SlottedHolder — cell 별 slot ─────────────────────────────\n slotIds(): ReadonlyArray<string> {\n const out: string[] = []\n for (let r = 0; r < this.rows; r++) {\n for (let c = 0; c < this.cols; c++) out.push(this.cellIdOf(c, r))\n }\n return out\n }\n hasCarrierAt(slotId: string): boolean {\n const p = this.parseCellId(slotId)\n return !!p && this.recordsOf(p.col, p.row).length > 0\n }\n canReceiveAt(slotId: string, _carrier?: Component): boolean {\n const p = this.parseCellId(slotId)\n if (!p) return false\n const cap = this.capacityOf(p.col, p.row)\n if (typeof cap === 'number' && this.recordsOf(p.col, p.row).length >= cap) return false\n return true\n }\n occupiedSlotIds(): ReadonlyArray<string> {\n const out: string[] = []\n for (let r = 0; r < this.rows; r++) for (let c = 0; c < this.cols; c++) {\n if (this.recordsOf(c, r).length > 0) out.push(this.cellIdOf(c, r))\n }\n return out\n }\n emptySlotIds(): ReadonlyArray<string> {\n const out: string[] = []\n for (let r = 0; r < this.rows; r++) for (let c = 0; c < this.cols; c++) {\n if (this.canReceiveAt(this.cellIdOf(c, r))) out.push(this.cellIdOf(c, r))\n }\n return out\n }\n\n obtainCarrier(slotId: string): Component | null {\n const p = this.parseCellId(slotId)\n if (!p) return null\n const recs = this.recordsOf(p.col, p.row)\n if (recs.length === 0) return null\n const records = [...recs]\n const policy = (this.state.pickPolicy ?? 'lifo') as PickPolicy\n const record = policy === 'fifo' ? records.shift()! : records.pop()!\n this._setCellRecords(p.col, p.row, records)\n return this._materializeCarrier(record, p.col, p.row)\n }\n\n async receiveAt(slotId: string, carrier: Component, _options?: any): Promise<void> {\n const p = this.parseCellId(slotId)\n if (!p) return\n const cstate: any = (carrier as any)?.state ?? {}\n const cid = cstate.id ?? `stkg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`\n const record: StockpileRecord = {\n id: String(cid),\n ...(cstate.type ? { type: cstate.type } : {}),\n ...(typeof cstate.width === 'number' ? { width: cstate.width } : {}),\n ...(typeof cstate.height === 'number' ? { height: cstate.height } : {}),\n ...(typeof cstate.depth === 'number' ? { depth: cstate.depth } : {})\n }\n const records = [...this.recordsOf(p.col, p.row), record]\n this._setCellRecords(p.col, p.row, records)\n ;(carrier as any)?.dispose?.()\n }\n\n async accept(carrier: Component, options?: any): Promise<void> {\n const empty = this.emptySlotIds()\n if (empty.length === 0) return\n return this.receiveAt(empty[0], carrier, options)\n }\n async receive(carrier: Component, options?: any): Promise<void> {\n return this.accept(carrier, options)\n }\n\n slotTargetAt(slotId: string): SlotTarget { return new SlotTarget(this as any, slotId) }\n getSlotAttachObject3d(slotId: string): any {\n return (this as any)._realObject?.getCellPadMesh?.(slotId)\n }\n\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getAttachFrame?.()\n if (!frame) return null\n const carrierDepth = resolveDepth(carrier)\n return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }\n }\n\n private _setCellRecords(col: number, row: number, records: StockpileRecord[]): void {\n const cells = [...((this.state.data as StockpileGridCell[]) ?? [])]\n const idx = cells.findIndex(c => c.col === col && c.row === row)\n if (idx === -1) cells.push({ col, row, data: records })\n else cells[idx] = { ...cells[idx], data: records }\n ;(this.state as any).data = cells\n this._realObject?.update?.()\n }\n\n private _materializeCarrier(record: StockpileRecord, col: number, row: number): Component | null {\n const preset = (this.state.carrierPreset ?? 'box') as CarrierPreset\n const PRESET_TO_TYPE: Record<CarrierPreset, string> = {\n box: 'box', pallet: 'pallet', crate: 'box',\n drum: 'parcel', sack: 'parcel', bale: 'parcel'\n }\n const carrierType = (record as any).type ?? PRESET_TO_TYPE[preset] ?? 'parcel'\n const CarrierClass = (Component as any).register(carrierType) as\n | (new (...args: any[]) => Component) | undefined\n if (!CarrierClass) {\n console.warn(`[stockpile-grid] carrier type \"${carrierType}\" 미등록`)\n return null\n }\n\n const cw = this.state.carrierWidth ?? 30\n const ch = this.state.carrierHeight ?? 30\n const cd = this.state.carrierDepth ?? 22\n\n const { id: _id, refid: _refid, transform: _tf, ...recordCopy } = record as any\n const cellCenterX = col * this.cellW + this.cellW / 2\n const cellCenterZ = row * this.cellH + this.cellH / 2\n\n const carrierState: any = {\n ...recordCopy,\n type: carrierType,\n width: cw,\n height: ch,\n depth: cd,\n refid: _nextStockpileGridCarrierRefid(),\n left: cellCenterX - cw / 2,\n top: cellCenterZ - ch / 2\n }\n\n const carrier = new CarrierClass(carrierState, (this as any)._app)\n ;(this as any).addComponent(carrier, { silent: true })\n void (carrier as any).realObject\n ;(carrier as any).applyHolderAttachPoint?.()\n return carrier\n }\n\n // ── Legend ─────────────────────────────────────────────────\n private _legendTarget?: Component\n get legendTarget(): Component | undefined {\n if (this._legendTarget) return this._legendTarget\n const id = this.state.legendTarget\n if (id) {\n const found = ((this as any).root)?.findById?.(id) as Component | undefined\n if (found) {\n this._legendTarget = found\n ;(found as any).on?.('change', this._onLegendChanged, this)\n return found\n }\n }\n const visit = (node: any): Component | undefined => {\n if (!node) return undefined\n if (node.state?.type === 'legend') return node as Component\n const children = node.components as Component[] | undefined\n if (children) for (const c of children) { const r = visit(c); if (r) return r }\n return undefined\n }\n const found = visit((this as any).root)\n if (found) {\n this._legendTarget = found\n ;(found as any).on?.('change', this._onLegendChanged, this)\n }\n return found\n }\n private _onLegendChanged = (): void => { ;(this._realObject as any)?.update?.() }\n\n resolveLegendColor(record: any): string | undefined {\n const legend = this.legendTarget\n if (!legend) return undefined\n const status: any = (legend as any).getState?.('status') ?? (legend.state as any)?.status\n if (!status) return undefined\n const field = status.field as string | undefined\n const ranges = status.ranges as any[] | undefined\n if (!field || !Array.isArray(ranges)) return undefined\n const value = record?.[field]\n if (value === undefined || value === null) return status.defaultColor\n for (const range of ranges) {\n if (!range) continue\n if (range.value !== undefined) {\n if (range.value === value) return range.color\n continue\n }\n const num = Number(value)\n if (!Number.isFinite(num)) continue\n const min = range.min !== undefined && range.min !== '' ? Number(range.min) : undefined\n const max = range.max !== undefined && range.max !== '' ? Number(range.max) : undefined\n const minOk = min === undefined || num >= min\n const maxOk = max === undefined || num < max\n if (minOk && maxOk) return range.color\n }\n return status.defaultColor as string | undefined\n }\n\n // ── Popup + click ─────────────────────────────────────────────\n get eventMap() {\n return { '(self)': { '(self)': { click: this._onGridClick } } }\n }\n private _onGridClick = (mouseEvent: MouseEvent) => {\n if (!(this as any).app?.isViewMode) return\n const hit = this._raycastHit(mouseEvent)\n if (!hit) return\n const slotId = (hit.object?.userData?.slotId as string | undefined)\n this._invokePopup(slotId)\n }\n private _invokePopup(slotId?: string): void {\n const popupRefId = this.state.popupRef\n if (!popupRefId) return\n const popupComp: any = (this as any).root?.findById?.(popupRefId)\n if (!popupComp || typeof popupComp.openPopup !== 'function') return\n if (slotId) {\n const p = this.parseCellId(slotId)\n const anchor = this.slotTargetAt(slotId)\n popupComp.openPopup({\n cellId: slotId,\n col: p?.col, row: p?.row,\n records: p ? this.recordsOf(p.col, p.row) : [],\n capacity: p ? this.capacityOf(p.col, p.row) : undefined\n }, { anchor })\n } else {\n const anchor = this.slotTargetAt(this.cellIdOf(0, 0))\n popupComp.openPopup({\n componentId: (this.state as any).id,\n cols: this.cols, rows: this.rows\n }, { anchor })\n }\n }\n\n private _raycastHit(mouseEvent: MouseEvent): THREE.Intersection | undefined {\n const ro: any = (this as any)._realObject\n if (!ro?.object3d) return undefined\n const tc: any = ro.threeContainer\n if (!tc) return undefined\n const cap: any = tc._threeCapability ?? tc._capability\n let intersects: THREE.Intersection[] | undefined\n if (cap?.getObjectsByRaycast) intersects = cap.getObjectsByRaycast() as THREE.Intersection[] | undefined\n if (!intersects || intersects.length === 0) {\n const scene = tc.scene3d as THREE.Scene | undefined\n const renderer = tc.renderer3d as THREE.WebGLRenderer | undefined\n const camera =\n (tc.activeCamera3d as THREE.Camera | undefined) ??\n (cap?.activeCamera as THREE.Camera | undefined) ??\n (cap?.camera as THREE.Camera | undefined)\n const canvas = renderer?.domElement\n if (!scene || !canvas || !camera) return undefined\n const rect = canvas.getBoundingClientRect()\n if (rect.width === 0 || rect.height === 0) return undefined\n const ndc = new THREE.Vector2(\n ((mouseEvent.clientX - rect.left) / rect.width) * 2 - 1,\n -((mouseEvent.clientY - rect.top) / rect.height) * 2 + 1\n )\n const raycaster = new THREE.Raycaster()\n raycaster.setFromCamera(ndc, camera)\n intersects = raycaster.intersectObjects(scene.children, true)\n }\n if (!intersects || intersects.length === 0) return undefined\n const closest = intersects[0]\n let obj: THREE.Object3D | null = closest.object\n while (obj) {\n if (obj.userData?.context === ro) return closest\n obj = obj.parent\n }\n return undefined\n }\n\n // ── 2D render ─────────────────────────────────────────────\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0 } = this.state\n const totalW = this.cellW * this.cols\n const totalH = this.cellH * this.rows\n const fillStyle = (this.state.fillStyle as string) || '#c89c5c'\n const strokeStyle = (this.state.strokeStyle as string) || '#7a5a2e'\n\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, totalW, totalH)\n ctx.restore()\n\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = 1.2\n ctx.strokeRect(left + 0.6, top + 0.6, totalW - 1.2, totalH - 1.2)\n for (let c = 1; c < this.cols; c++) {\n const x = left + c * this.cellW\n ctx.beginPath(); ctx.moveTo(x, top); ctx.lineTo(x, top + totalH); ctx.stroke()\n }\n for (let r = 1; r < this.rows; r++) {\n const y = top + r * this.cellH\n ctx.beginPath(); ctx.moveTo(left, y); ctx.lineTo(left + totalW, y); ctx.stroke()\n }\n ctx.restore()\n\n ctx.save()\n const fontSize = Math.min(this.cellW, this.cellH) * 0.22\n ctx.fillStyle = '#333'\n ctx.font = `bold ${fontSize}px sans-serif`\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n for (let r = 0; r < this.rows; r++) for (let c = 0; c < this.cols; c++) {\n const count = this.recordsOf(c, r).length\n if (count === 0) continue\n ctx.fillText(`${count}`, left + (c + 0.5) * this.cellW, top + (r + 0.5) * this.cellH)\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new StockpileGrid3D(this)\n }\n}\n\nlet _stockpileGridCarrierSeq = 0\nfunction _nextStockpileGridCarrierRefid(): number {\n return 850000 + (_stockpileGridCarrierSeq++)\n}\n\nfunction resolveDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n const d = (c as any)?.state?.depth\n return typeof d === 'number' && Number.isFinite(d) ? d : 0\n}\n"]}
@@ -0,0 +1,133 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import type { State, Material3D } from '@hatiolab/things-scene';
3
+ import { SlotTarget, type AttachFrame, type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base';
4
+ import { Stockpile3D } from './stockpile-3d.js';
5
+ export type StackPattern = 'row' | 'staggered' | 'pyramid' | 'column' | 'pile';
6
+ export type CarrierPreset = 'box' | 'pallet' | 'drum' | 'sack' | 'crate' | 'bale';
7
+ export type PickPolicy = 'lifo' | 'fifo';
8
+ /** 적치된 carrier 의 record. 향후 sku / weight / 입고시각 등 확장. */
9
+ export interface StockpileRecord {
10
+ id: string;
11
+ [key: string]: any;
12
+ }
13
+ export interface StockpileState extends State {
14
+ /** 적치 carrier 의 record 목록 — storage-rack 의 state.data 와 동일 idiom. */
15
+ data?: StockpileRecord[];
16
+ /** 적치 형태 프리셋. default 'row'. */
17
+ stackPattern?: StackPattern;
18
+ /** 가상 carrier 종류 프리셋. default 'box'. */
19
+ carrierPreset?: CarrierPreset;
20
+ /** 가상 carrier 한 개의 크기 (없으면 preset 별 기본). */
21
+ carrierWidth?: number;
22
+ carrierHeight?: number;
23
+ carrierDepth?: number;
24
+ /** 적치된 carrier 사이의 평면(xz) 간격 — 0 이면 딱 붙음, default 3. y(단 적층)는 항상 딱 붙음. */
25
+ carrierGap?: number;
26
+ /** 최대 적치 수 (undefined = 무제한). */
27
+ capacity?: number;
28
+ /** 위로 몇 단까지 (undefined = stack 형태에 맡김). */
29
+ stackHeightLimit?: number;
30
+ /** 어느 끝에서 빼나. default 'lifo'. */
31
+ pickPolicy?: PickPolicy;
32
+ /** click 시 invoke 할 Popup 컴포넌트 id (StorageRack / RackGrid 와 동일 패턴). */
33
+ popupRef?: string;
34
+ /**
35
+ * Legend 컴포넌트 id. legend 의 `state.status = {field, ranges, defaultColor}` 를
36
+ * 참조해 각 record 의 field 값을 색상으로 매핑한다 (StorageRack 와 동일 패턴).
37
+ * 미명시 시 scene 안 `type='legend'` 첫 컴포넌트 자동 발견.
38
+ */
39
+ legendTarget?: string;
40
+ material3d?: Material3D;
41
+ }
42
+ declare const Stockpile_base: any;
43
+ export default class Stockpile extends Stockpile_base {
44
+ state: StockpileState;
45
+ _realObject?: Stockpile3D;
46
+ static placement: PlacementArchetype;
47
+ static align: Alignment;
48
+ static defaultDepth: (_h: Heights) => number;
49
+ get nature(): ComponentNature;
50
+ get anchors(): never[];
51
+ get records(): ReadonlyArray<StockpileRecord>;
52
+ get inventoryCount(): number;
53
+ slotIds(): ReadonlyArray<string>;
54
+ hasCarrierAt(slotId: string): boolean;
55
+ canReceiveAt(slotId: string, _carrier?: Component): boolean;
56
+ occupiedSlotIds(): ReadonlyArray<string>;
57
+ emptySlotIds(): ReadonlyArray<string>;
58
+ /**
59
+ * record 한 개를 빼서 carrier 컴포넌트로 transient materialize. mover 가 pickup
60
+ * 하면 이걸 reparent 해서 들고 다닌다. storage-rack._materializeCarrier 와 동일
61
+ * 패턴 — Component.register(type) 으로 클래스 lookup, addComponent({silent: true})
62
+ * 로 cascade 차단.
63
+ */
64
+ obtainCarrier(slotId: string): Component | null;
65
+ private _materializeCarrier;
66
+ /**
67
+ * carrier 를 받아 record 로 push, carrier 객체는 dispose (시각은 _realObject 가
68
+ * records 길이 기준 자동 갱신). capacity 초과 시도는 canReceiveAt 가 이미 차단.
69
+ */
70
+ receiveAt(_slotId: string, carrier: Component, _options?: any): Promise<void>;
71
+ /** dispatch → handoff 의 accept 분기 — receiveAt 으로 위임. */
72
+ accept(carrier: Component, options?: any): Promise<void>;
73
+ receive(carrier: Component, options?: any): Promise<void>;
74
+ /**
75
+ * mover 가 obtainCarrier 로 빼낸 transient carrier 를 pad 위에 안착 (잠깐 보이는
76
+ * 위치). mover 가 pick 하면 즉시 deck 으로 reparent 되므로 잔류는 짧다.
77
+ * Spot 과 동일 idiom — pad-top + carrier 의 halfDepth 만큼 들어올림.
78
+ */
79
+ attachPointFor(carrier: Component): AttachFrame | null;
80
+ slotTargetAt(slotId: string): SlotTarget;
81
+ getSlotAttachObject3d(slotId: string): any;
82
+ /** state.data 갱신 + 3D 즉시 재배치. */
83
+ private _setDataSilently;
84
+ /**
85
+ * 2D — outlined pad + 인벤토리 카운트 텍스트. 평치 의도가 한 눈에 — 사각 영역 위에
86
+ * 적재된 carrier 수가 가운데 큰 글씨로.
87
+ */
88
+ render(ctx: CanvasRenderingContext2D): void;
89
+ /**
90
+ * things-scene EventManager3D 가 raycast → object3d.userData.context.component 의
91
+ * `trigger("click", mouseEvent)` 을 호출 → eventMap 으로 receive. pad / carrier mesh
92
+ * 어느 쪽을 클릭하든 stockpile 전체 popup invoke (단일 slot 이라 cell 구분 없음).
93
+ */
94
+ get eventMap(): {
95
+ '(self)': {
96
+ '(self)': {
97
+ click: (mouseEvent: MouseEvent) => void;
98
+ };
99
+ };
100
+ };
101
+ private _onStockpileClick;
102
+ /**
103
+ * state.popupRef 가 가리키는 Popup 컴포넌트를 invoke.
104
+ * - recordId 명시 → 그 record 의 anchor = mesh. payload = 해당 record.
105
+ * - 미명시 (pad 클릭) → 'pile' anchor (pad). payload = 전체 inventory.
106
+ * anchor 는 SlotTarget — Popup 이 anchor.holder.getSlotAttachObject3d(anchor.slotId)
107
+ * 로 tether 위치 잡음 (storage-rack 와 동일 패턴).
108
+ */
109
+ private _invokePopup;
110
+ /**
111
+ * 클릭 시 framework 의 mouse NDC 를 재사용해 raycast → *우리 stockpile* 의 어떤 mesh 가
112
+ * closest hit 인지 반환 (다른 object 가 더 가까우면 undefined). storage-rack._raycastRackHit
113
+ * 와 동일 패턴 — capability.getObjectsByRaycast 우선, 없으면 scene/camera/canvas 재구성.
114
+ */
115
+ private _raycastStockpileHit;
116
+ private _legendTarget?;
117
+ /**
118
+ * Legend 컴포넌트 lookup. 우선:
119
+ * 1) state.legendTarget id 명시
120
+ * 2) scene 전체에서 type='legend' 첫 번째 (자동 발견)
121
+ */
122
+ get legendTarget(): Component | undefined;
123
+ private _onLegendChanged;
124
+ /**
125
+ * record 의 legend.field 값을 ranges 와 매칭해 색상 해석.
126
+ * - range.value === recordValue (카테고리)
127
+ * - range.min ≤ Number(v) < range.max (수치)
128
+ * - 매칭 없으면 defaultColor
129
+ */
130
+ resolveLegendColor(record: any): string | undefined;
131
+ buildRealObject(): RealObject | undefined;
132
+ }
133
+ export {};