@operato/scene-storage 10.0.0-beta.50 → 10.0.0-beta.53
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 +20 -0
- package/dist/box.js +2 -2
- package/dist/box.js.map +1 -1
- package/dist/pallet.js +2 -2
- package/dist/pallet.js.map +1 -1
- package/dist/parcel.js +2 -2
- package/dist/parcel.js.map +1 -1
- package/dist/picking-station.d.ts +10 -16
- package/dist/picking-station.js +20 -46
- package/dist/picking-station.js.map +1 -1
- package/dist/rack-grid.d.ts +4 -22
- package/dist/rack-grid.js +21 -106
- package/dist/rack-grid.js.map +1 -1
- package/dist/spot.d.ts +2 -19
- package/dist/spot.js +4 -62
- package/dist/spot.js.map +1 -1
- package/dist/stockpile-grid.d.ts +2 -5
- package/dist/stockpile-grid.js +18 -86
- package/dist/stockpile-grid.js.map +1 -1
- package/dist/stockpile.d.ts +5 -22
- package/dist/stockpile.js +21 -115
- package/dist/stockpile.js.map +1 -1
- package/dist/storage-rack.d.ts +27 -44
- package/dist/storage-rack.js +52 -137
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/box.ts +2 -1
- package/src/pallet.ts +2 -1
- package/src/parcel.ts +2 -1
- package/src/picking-station.ts +26 -49
- package/src/rack-grid.ts +21 -100
- package/src/spot.ts +11 -59
- package/src/stockpile-grid.ts +21 -69
- package/src/stockpile.ts +23 -104
- package/src/storage-rack.ts +61 -129
- package/translations/en.json +6 -1
- package/translations/ja.json +6 -1
- package/translations/ko.json +6 -1
- package/translations/ms.json +6 -1
- package/translations/zh.json +6 -1
- package/tsconfig.tsbuildinfo +1 -1
package/dist/stockpile.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { __decorate } from "tslib";
|
|
16
16
|
import * as THREE from 'three';
|
|
17
17
|
import { Component, ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
|
|
18
|
-
import { CarrierHolder, Placeable, SlotTarget } from '@operato/scene-base';
|
|
18
|
+
import { CarrierHolder, Placeable, RecordStorage, SlotTarget } from '@operato/scene-base';
|
|
19
19
|
import { Stockpile3D } from './stockpile-3d.js';
|
|
20
20
|
const SLOT_ID = 'pile';
|
|
21
21
|
const NATURE = {
|
|
@@ -43,20 +43,16 @@ const NATURE = {
|
|
|
43
43
|
],
|
|
44
44
|
help: 'scene/component/stockpile'
|
|
45
45
|
};
|
|
46
|
-
let Stockpile = class Stockpile extends CarrierHolder(Placeable(ContainerAbstract)) {
|
|
46
|
+
let Stockpile = class Stockpile extends RecordStorage()(CarrierHolder(Placeable(ContainerAbstract))) {
|
|
47
47
|
static placement = 'floor';
|
|
48
48
|
static align = 'bottom';
|
|
49
49
|
static defaultDepth = (_h) => 5; // pad 두께
|
|
50
50
|
get nature() { return NATURE; }
|
|
51
51
|
get anchors() { return []; }
|
|
52
|
-
// ── records
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
get inventoryCount() {
|
|
57
|
-
return this.records.length;
|
|
58
|
-
}
|
|
59
|
-
// ── SlottedHolder duck-type — 단일 slot ('pile') ────────────────────────
|
|
52
|
+
// ── records / inventoryCount: RecordStorage mixin 제공.
|
|
53
|
+
// ── SlottedHolder duck-type override — 단일 slot ('pile') ───────────────
|
|
54
|
+
// mixin default 는 record 마다 slotId. Stockpile 은 *_단일 슬롯_* 시맨틱이라
|
|
55
|
+
// 자기 구현 유지.
|
|
60
56
|
slotIds() { return [SLOT_ID]; }
|
|
61
57
|
hasCarrierAt(slotId) {
|
|
62
58
|
return slotId === SLOT_ID && this.inventoryCount > 0;
|
|
@@ -184,10 +180,9 @@ let Stockpile = class Stockpile extends CarrierHolder(Placeable(ContainerAbstrac
|
|
|
184
180
|
// stock 에 정확히 연결되도록.
|
|
185
181
|
return this._realObject?.getAttachFrame?.(slotId);
|
|
186
182
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.state.data = records;
|
|
183
|
+
// _setDataSilently 는 RecordStorage mixin 제공 — 다만 mixin 은 _rebuildVisual
|
|
184
|
+
// 호출. Stockpile 의 3D 갱신은 update() 라서 hook override.
|
|
185
|
+
_rebuildVisual() {
|
|
191
186
|
this._realObject?.update?.();
|
|
192
187
|
}
|
|
193
188
|
/**
|
|
@@ -250,39 +245,33 @@ let Stockpile = class Stockpile extends CarrierHolder(Placeable(ContainerAbstrac
|
|
|
250
245
|
// hit.object 가 carrier mesh 면 userData.recordId 보유 → 그 stock 의 popup.
|
|
251
246
|
// pad / 기타면 stockpile 전체 popup.
|
|
252
247
|
const recordId = hit.object?.userData?.recordId;
|
|
253
|
-
this.
|
|
248
|
+
this._dispatchStockpilePopup(typeof recordId === 'string' ? recordId : undefined);
|
|
254
249
|
};
|
|
255
250
|
/**
|
|
256
251
|
* state.popupRef 가 가리키는 Popup 컴포넌트를 invoke.
|
|
257
252
|
* - recordId 명시 → 그 record 의 anchor = mesh. payload = 해당 record.
|
|
258
253
|
* - 미명시 (pad 클릭) → 'pile' anchor (pad). payload = 전체 inventory.
|
|
259
|
-
*
|
|
260
|
-
*
|
|
254
|
+
*
|
|
255
|
+
* RecordStorage mixin 의 `_invokePopup(slotId, payload)` 를 활용. 단일 slot
|
|
256
|
+
* 시맨틱이 record/pile 두 모드라 mixin 위 wrapper 로 dispatch.
|
|
261
257
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (!popupRefId)
|
|
265
|
-
return;
|
|
266
|
-
const popupComp = this.root?.findById?.(popupRefId);
|
|
267
|
-
if (!popupComp || typeof popupComp.openPopup !== 'function') {
|
|
268
|
-
console.warn(`[stockpile] popupRef="${popupRefId}" 가 가리키는 컴포넌트 없거나 openPopup 미지원`);
|
|
258
|
+
_dispatchStockpilePopup(recordId) {
|
|
259
|
+
if (!this.state.popupRef)
|
|
269
260
|
return;
|
|
270
|
-
}
|
|
271
261
|
if (recordId) {
|
|
272
|
-
const record = this.records
|
|
273
|
-
|
|
274
|
-
|
|
262
|
+
const record = this.records
|
|
263
|
+
.find(r => r.id === recordId) ?? { id: recordId };
|
|
264
|
+
this._invokePopup(recordId, record);
|
|
275
265
|
}
|
|
276
266
|
else {
|
|
277
|
-
|
|
278
|
-
popupComp.openPopup({
|
|
267
|
+
this._invokePopup(SLOT_ID, {
|
|
279
268
|
componentId: this.state.id,
|
|
280
269
|
records: this.records,
|
|
281
270
|
inventoryCount: this.inventoryCount,
|
|
282
271
|
capacity: this.state.capacity,
|
|
283
272
|
carrierPreset: this.state.carrierPreset,
|
|
284
273
|
stackPattern: this.state.stackPattern
|
|
285
|
-
}
|
|
274
|
+
});
|
|
286
275
|
}
|
|
287
276
|
}
|
|
288
277
|
/**
|
|
@@ -331,90 +320,7 @@ let Stockpile = class Stockpile extends CarrierHolder(Placeable(ContainerAbstrac
|
|
|
331
320
|
}
|
|
332
321
|
return undefined;
|
|
333
322
|
}
|
|
334
|
-
//
|
|
335
|
-
_legendTarget;
|
|
336
|
-
/**
|
|
337
|
-
* Legend 컴포넌트 lookup. 우선:
|
|
338
|
-
* 1) state.legendTarget id 명시
|
|
339
|
-
* 2) scene 전체에서 type='legend' 첫 번째 (자동 발견)
|
|
340
|
-
*/
|
|
341
|
-
get legendTarget() {
|
|
342
|
-
if (this._legendTarget)
|
|
343
|
-
return this._legendTarget;
|
|
344
|
-
const id = this.state.legendTarget;
|
|
345
|
-
if (id) {
|
|
346
|
-
const found = (this.root)?.findById?.(id);
|
|
347
|
-
if (found) {
|
|
348
|
-
this._legendTarget = found;
|
|
349
|
-
found.on?.('change', this._onLegendChanged, this);
|
|
350
|
-
return found;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const visit = (node) => {
|
|
354
|
-
if (!node)
|
|
355
|
-
return undefined;
|
|
356
|
-
if (node.state?.type === 'legend')
|
|
357
|
-
return node;
|
|
358
|
-
const children = node.components;
|
|
359
|
-
if (children)
|
|
360
|
-
for (const c of children) {
|
|
361
|
-
const r = visit(c);
|
|
362
|
-
if (r)
|
|
363
|
-
return r;
|
|
364
|
-
}
|
|
365
|
-
return undefined;
|
|
366
|
-
};
|
|
367
|
-
const found = visit(this.root);
|
|
368
|
-
if (found) {
|
|
369
|
-
this._legendTarget = found;
|
|
370
|
-
found.on?.('change', this._onLegendChanged, this);
|
|
371
|
-
}
|
|
372
|
-
return found;
|
|
373
|
-
}
|
|
374
|
-
_onLegendChanged = () => {
|
|
375
|
-
;
|
|
376
|
-
this._realObject?.update?.();
|
|
377
|
-
};
|
|
378
|
-
/**
|
|
379
|
-
* record 의 legend.field 값을 ranges 와 매칭해 색상 해석.
|
|
380
|
-
* - range.value === recordValue (카테고리)
|
|
381
|
-
* - range.min ≤ Number(v) < range.max (수치)
|
|
382
|
-
* - 매칭 없으면 defaultColor
|
|
383
|
-
*/
|
|
384
|
-
resolveLegendColor(record) {
|
|
385
|
-
const legend = this.legendTarget;
|
|
386
|
-
if (!legend)
|
|
387
|
-
return undefined;
|
|
388
|
-
const status = legend.getState?.('status') ?? legend.state?.status;
|
|
389
|
-
if (!status)
|
|
390
|
-
return undefined;
|
|
391
|
-
const field = status.field;
|
|
392
|
-
const ranges = status.ranges;
|
|
393
|
-
if (!field || !Array.isArray(ranges))
|
|
394
|
-
return undefined;
|
|
395
|
-
const value = record?.[field];
|
|
396
|
-
if (value === undefined || value === null)
|
|
397
|
-
return status.defaultColor;
|
|
398
|
-
for (const range of ranges) {
|
|
399
|
-
if (!range)
|
|
400
|
-
continue;
|
|
401
|
-
if (range.value !== undefined) {
|
|
402
|
-
if (range.value === value)
|
|
403
|
-
return range.color;
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
const num = Number(value);
|
|
407
|
-
if (!Number.isFinite(num))
|
|
408
|
-
continue;
|
|
409
|
-
const min = range.min !== undefined && range.min !== '' ? Number(range.min) : undefined;
|
|
410
|
-
const max = range.max !== undefined && range.max !== '' ? Number(range.max) : undefined;
|
|
411
|
-
const minOk = min === undefined || num >= min;
|
|
412
|
-
const maxOk = max === undefined || num < max;
|
|
413
|
-
if (minOk && maxOk)
|
|
414
|
-
return range.color;
|
|
415
|
-
}
|
|
416
|
-
return status.defaultColor;
|
|
417
|
-
}
|
|
323
|
+
// legendTarget / _onLegendChanged / resolveLegendColor — RecordStorage mixin 제공.
|
|
418
324
|
buildRealObject() {
|
|
419
325
|
return new Stockpile3D(this);
|
|
420
326
|
}
|
package/dist/stockpile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stockpile.js","sourceRoot":"","sources":["../src/stockpile.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Stockpile — 평치(block/floor) 보관 영역. 사각형 footprint + `state.data` 의 record[].\n * 실제 carrier component 를 자식으로 두지 않고, 인벤토리(records)의 *_개수와 종류_* 만으로\n * 가상 carrier mesh 를 자동 적치(stackPattern × carrierPreset)해 시각화한다.\n *\n * 데이터 idiom — StorageRack 과 동일 (`state.data: Record[]`). mover 가 pick/place 하면\n * record push/pop, _realObject 가 records.length 기준 mesh 를 재배치.\n *\n * 1단계 — 시각화: 모델에 `data: [{id:'a'},{id:'b'},...]` 가 들어오면 mesh 가 자동 적치.\n * 2단계(추후) — mover 통합: obtainCarrier 가 transient carrier 컴포넌트 materialize,\n * receiveAt 가 record push + carrier dispose.\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 { Stockpile3D } from './stockpile-3d.js'\n\nexport type StackPattern = 'row' | 'staggered' | 'pyramid' | 'column' | 'pile'\nexport type CarrierPreset = 'box' | 'pallet' | 'drum' | 'sack' | 'crate' | 'bale'\nexport type PickPolicy = 'lifo' | 'fifo'\n\n/** 적치된 carrier 의 record. 향후 sku / weight / 입고시각 등 확장. */\nexport interface StockpileRecord {\n id: string\n [key: string]: any\n}\n\nexport interface StockpileState extends State {\n /** 적치 carrier 의 record 목록 — storage-rack 의 state.data 와 동일 idiom. */\n data?: StockpileRecord[]\n\n /** 적치 형태 프리셋. default 'row'. */\n stackPattern?: StackPattern\n /** 가상 carrier 종류 프리셋. default 'box'. */\n carrierPreset?: CarrierPreset\n\n /** 가상 carrier 한 개의 크기 (없으면 preset 별 기본). */\n carrierWidth?: number\n carrierHeight?: number\n carrierDepth?: number\n /** 적치된 carrier 사이의 평면(xz) 간격 — 0 이면 딱 붙음, default 3. y(단 적층)는 항상 딱 붙음. */\n carrierGap?: number\n\n /** 최대 적치 수 (undefined = 무제한). */\n capacity?: number\n /** 위로 몇 단까지 (undefined = stack 형태에 맡김). */\n stackHeightLimit?: number\n /** 어느 끝에서 빼나. default 'lifo'. */\n pickPolicy?: PickPolicy\n\n /** click 시 invoke 할 Popup 컴포넌트 id (StorageRack / RackGrid 와 동일 패턴). */\n popupRef?: string\n\n /**\n * Legend 컴포넌트 id. legend 의 `state.status = {field, ranges, defaultColor}` 를\n * 참조해 각 record 의 field 값을 색상으로 매핑한다 (StorageRack 와 동일 패턴).\n * 미명시 시 scene 안 `type='legend'` 첫 컴포넌트 자동 발견.\n */\n legendTarget?: string\n\n material3d?: Material3D\n}\n\nconst SLOT_ID = 'pile'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\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'\n}\n\n@sceneComponent('stockpile')\nexport default class Stockpile extends CarrierHolder(Placeable(ContainerAbstract)) {\n declare state: StockpileState\n declare _realObject?: Stockpile3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 5 // pad 두께\n\n get nature(): ComponentNature { return NATURE }\n get anchors() { return [] }\n\n // ── records (state.data 의 읽기 전용 뷰, storage-rack 패턴) ─────────────\n get records(): ReadonlyArray<StockpileRecord> {\n return (this.state.data as StockpileRecord[]) ?? []\n }\n get inventoryCount(): number {\n return this.records.length\n }\n\n // ── SlottedHolder duck-type — 단일 slot ('pile') ────────────────────────\n slotIds(): ReadonlyArray<string> { return [SLOT_ID] }\n\n hasCarrierAt(slotId: string): boolean {\n return slotId === SLOT_ID && this.inventoryCount > 0\n }\n\n canReceiveAt(slotId: string, _carrier?: Component): boolean {\n if (slotId !== SLOT_ID) return false\n const cap = this.state.capacity\n if (typeof cap === 'number' && this.inventoryCount >= cap) return false\n return true\n }\n\n occupiedSlotIds(): ReadonlyArray<string> {\n return this.inventoryCount > 0 ? [SLOT_ID] : []\n }\n emptySlotIds(): ReadonlyArray<string> {\n return this.canReceiveAt(SLOT_ID) ? [SLOT_ID] : []\n }\n\n /**\n * record 한 개를 빼서 carrier 컴포넌트로 transient materialize. mover 가 pickup\n * 하면 이걸 reparent 해서 들고 다닌다. storage-rack._materializeCarrier 와 동일\n * 패턴 — Component.register(type) 으로 클래스 lookup, addComponent({silent: true})\n * 로 cascade 차단.\n */\n obtainCarrier(slotId: string): Component | null {\n if (slotId !== SLOT_ID) return null\n if (this.records.length === 0) return null\n const records = [...this.records]\n const policy = (this.state.pickPolicy ?? 'lifo') as PickPolicy\n const record = policy === 'fifo' ? records.shift()! : records.pop()!\n this._setDataSilently(records)\n return this._materializeCarrier(record)\n }\n\n private _materializeCarrier(record: StockpileRecord): Component | null {\n // carrierPreset → 등록된 컴포넌트 type. 미등록(drum/sack/bale 등) 은 시각 mesh\n // 전용이라 carrier 컴포넌트로는 fallback ('parcel'/'box').\n const preset = (this.state.carrierPreset ?? 'box') as CarrierPreset\n const PRESET_TO_TYPE: Record<CarrierPreset, string> = {\n box: 'box',\n pallet: 'pallet',\n crate: 'box',\n drum: 'parcel',\n sack: 'parcel',\n 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] carrier type \"${carrierType}\" 미등록 — obtainCarrier 실패`)\n return null\n }\n\n // 크기 — state.carrier* 또는 preset default\n const stockpileW = (this.state as any).width ?? 100\n const stockpileH = (this.state as any).height ?? 100\n const cw = this.state.carrierWidth ?? 30\n const ch = this.state.carrierHeight ?? 30\n const cd = this.state.carrierDepth ?? 22\n\n // id/refid/transform 류 제외 — scene 안 기존 component 충돌 회피 (storage-rack 패턴)\n const { id: _id, refid: _refid, transform: _tf, ...recordCopy } = record as any\n // stockpile-inner 좌표 — center 에 놓기 (Mover 가 곧 pick 해 들고 감)\n const carrierState: any = {\n ...recordCopy,\n type: carrierType,\n width: cw,\n height: ch,\n depth: cd,\n refid: _nextStockpileCarrierRefid(),\n left: stockpileW / 2 - cw / 2,\n top: stockpileH / 2 - ch / 2\n }\n\n const carrier = new CarrierClass(carrierState, (this as any)._app)\n // silent: refreshMappings cascade 차단 (transient 라 매핑 재계산 불필요)\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 /**\n * carrier 를 받아 record 로 push, carrier 객체는 dispose (시각은 _realObject 가\n * records 길이 기준 자동 갱신). capacity 초과 시도는 canReceiveAt 가 이미 차단.\n */\n async receiveAt(_slotId: string, carrier: Component, _options?: any): Promise<void> {\n const cstate: any = (carrier as any)?.state ?? {}\n const cid = cstate.id ?? `stk-${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.records, record]\n this._setDataSilently(records)\n ;(carrier as any)?.dispose?.()\n }\n\n /** dispatch → handoff 의 accept 분기 — receiveAt 으로 위임. */\n async accept(carrier: Component, options?: any): Promise<void> {\n return this.receiveAt(SLOT_ID, carrier, options)\n }\n async receive(carrier: Component, options?: any): Promise<void> {\n return this.receiveAt(SLOT_ID, carrier, options)\n }\n\n /**\n * mover 가 obtainCarrier 로 빼낸 transient carrier 를 pad 위에 안착 (잠깐 보이는\n * 위치). mover 가 pick 하면 즉시 deck 으로 reparent 되므로 잔류는 짧다.\n * Spot 과 동일 idiom — pad-top + carrier 의 halfDepth 만큼 들어올림.\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 {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n slotTargetAt(slotId: string): SlotTarget {\n return new SlotTarget(this as any, slotId)\n }\n getSlotAttachObject3d(slotId: string): any {\n // slotId 가 'pile' 이면 pad. record.id 면 그 carrier mesh — popup tether 가 그\n // stock 에 정확히 연결되도록.\n return (this as any)._realObject?.getAttachFrame?.(slotId)\n }\n\n /** state.data 갱신 + 3D 즉시 재배치. */\n private _setDataSilently(records: StockpileRecord[]): void {\n ;(this.state as any).data = records\n this._realObject?.update?.()\n }\n\n /**\n * 2D — outlined pad + 인벤토리 카운트 텍스트. 평치 의도가 한 눈에 — 사각 영역 위에\n * 적재된 carrier 수가 가운데 큰 글씨로.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#c89c5c'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n\n // pad (반투명)\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.18\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // outline (dashed)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = 1.5\n ctx.setLineDash([6, 3])\n ctx.strokeRect(left + 0.75, top + 0.75, width - 1.5, height - 1.5)\n ctx.setLineDash([])\n ctx.restore()\n\n // 인벤토리 수 + capacity (있으면)\n ctx.save()\n const fontSize = Math.min(width, height) * 0.22\n ctx.fillStyle = '#333'\n ctx.font = `bold ${fontSize}px sans-serif`\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n const label = typeof this.state.capacity === 'number'\n ? `${this.inventoryCount}/${this.state.capacity}`\n : `${this.inventoryCount}`\n ctx.fillText(label, left + width / 2, top + height / 2)\n ctx.restore()\n }\n\n // ── Popup 연동 — storage-rack 동일 패턴 ───────────────────────────────\n /**\n * things-scene EventManager3D 가 raycast → object3d.userData.context.component 의\n * `trigger(\"click\", mouseEvent)` 을 호출 → eventMap 으로 receive. pad / carrier mesh\n * 어느 쪽을 클릭하든 stockpile 전체 popup invoke (단일 slot 이라 cell 구분 없음).\n */\n get eventMap() {\n return {\n '(self)': {\n '(self)': {\n click: this._onStockpileClick\n }\n }\n }\n }\n\n private _onStockpileClick = (mouseEvent: MouseEvent) => {\n // view mode 에서만 동작 (modeling 중 click 은 framework 선택 로직 우선).\n if (!(this as any).app?.isViewMode) return\n const hit = this._raycastStockpileHit(mouseEvent)\n if (!hit) return\n // hit.object 가 carrier mesh 면 userData.recordId 보유 → 그 stock 의 popup.\n // pad / 기타면 stockpile 전체 popup.\n const recordId = hit.object?.userData?.recordId as string | undefined\n this._invokePopup(typeof recordId === 'string' ? recordId : undefined)\n }\n\n /**\n * state.popupRef 가 가리키는 Popup 컴포넌트를 invoke.\n * - recordId 명시 → 그 record 의 anchor = mesh. payload = 해당 record.\n * - 미명시 (pad 클릭) → 'pile' anchor (pad). payload = 전체 inventory.\n * anchor 는 SlotTarget — Popup 이 anchor.holder.getSlotAttachObject3d(anchor.slotId)\n * 로 tether 위치 잡음 (storage-rack 와 동일 패턴).\n */\n private _invokePopup(recordId?: 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') {\n console.warn(`[stockpile] popupRef=\"${popupRefId}\" 가 가리키는 컴포넌트 없거나 openPopup 미지원`)\n return\n }\n if (recordId) {\n const record = this.records.find(r => r.id === recordId) ?? { id: recordId }\n const anchor = this.slotTargetAt(recordId)\n popupComp.openPopup(record, { anchor })\n } else {\n const anchor = this.slotTargetAt(SLOT_ID)\n popupComp.openPopup({\n componentId: (this.state as any).id,\n records: this.records,\n inventoryCount: this.inventoryCount,\n capacity: this.state.capacity,\n carrierPreset: this.state.carrierPreset,\n stackPattern: this.state.stackPattern\n }, { anchor })\n }\n }\n\n /**\n * 클릭 시 framework 의 mouse NDC 를 재사용해 raycast → *우리 stockpile* 의 어떤 mesh 가\n * closest hit 인지 반환 (다른 object 가 더 가까우면 undefined). storage-rack._raycastRackHit\n * 와 동일 패턴 — capability.getObjectsByRaycast 우선, 없으면 scene/camera/canvas 재구성.\n */\n private _raycastStockpileHit(mouseEvent: MouseEvent): THREE.Intersection | undefined {\n const ro: any = (this as any)._realObject\n if (!ro?.object3d) return undefined\n\n const tc: any = ro.threeContainer\n if (!tc) return undefined\n\n const cap: any = tc._threeCapability ?? tc._capability\n let intersects: THREE.Intersection[] | undefined\n if (cap?.getObjectsByRaycast) {\n intersects = cap.getObjectsByRaycast() as THREE.Intersection[] | undefined\n }\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\n // 가장 가까운 hit 이 *이 stockpile* 의 descendant 여야 한다 (다른 mesh 가 사이에 있으면 skip).\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 // ── Legend — record 의 field 값 → 색상 매핑 (StorageRack 동일 패턴) ───────\n private _legendTarget?: Component\n\n /**\n * Legend 컴포넌트 lookup. 우선:\n * 1) state.legendTarget id 명시\n * 2) scene 전체에서 type='legend' 첫 번째 (자동 발견)\n */\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) {\n const r = visit(c)\n if (r) return r\n }\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\n private _onLegendChanged = (): void => {\n ;(this._realObject as any)?.update?.()\n }\n\n /**\n * record 의 legend.field 값을 ranges 와 매칭해 색상 해석.\n * - range.value === recordValue (카테고리)\n * - range.min ≤ Number(v) < range.max (수치)\n * - 매칭 없으면 defaultColor\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\n const value = record?.[field]\n if (value === undefined || value === null) return status.defaultColor\n\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 buildRealObject(): RealObject | undefined {\n return new Stockpile3D(this)\n }\n}\n\n// transient carrier refid — scene 내 기존 컴포넌트와 충돌 회피용 큰 시작값\n// (storage-rack 의 _nextCarrierRefid 와 같은 idiom, 별도 범위로 분리).\nlet _stockpileCarrierSeq = 0\nfunction _nextStockpileCarrierRefid(): number {\n return 800000 + (_stockpileCarrierSeq++)\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"]}
|
|
1
|
+
{"version":3,"file":"stockpile.js","sourceRoot":"","sources":["../src/stockpile.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Stockpile — 평치(block/floor) 보관 영역. 사각형 footprint + `state.data` 의 record[].\n * 실제 carrier component 를 자식으로 두지 않고, 인벤토리(records)의 *_개수와 종류_* 만으로\n * 가상 carrier mesh 를 자동 적치(stackPattern × carrierPreset)해 시각화한다.\n *\n * 데이터 idiom — StorageRack 과 동일 (`state.data: Record[]`). mover 가 pick/place 하면\n * record push/pop, _realObject 가 records.length 기준 mesh 를 재배치.\n *\n * 1단계 — 시각화: 모델에 `data: [{id:'a'},{id:'b'},...]` 가 들어오면 mesh 가 자동 적치.\n * 2단계(추후) — mover 통합: obtainCarrier 가 transient carrier 컴포넌트 materialize,\n * receiveAt 가 record push + carrier dispose.\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 RecordStorage,\n SlotTarget,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Stockpile3D } from './stockpile-3d.js'\n\nexport type StackPattern = 'row' | 'staggered' | 'pyramid' | 'column' | 'pile'\nexport type CarrierPreset = 'box' | 'pallet' | 'drum' | 'sack' | 'crate' | 'bale'\nexport type PickPolicy = 'lifo' | 'fifo'\n\n/** 적치된 carrier 의 record. 향후 sku / weight / 입고시각 등 확장. */\nexport interface StockpileRecord {\n id: string\n [key: string]: any\n}\n\nexport interface StockpileState extends State {\n /** 적치 carrier 의 record 목록 — storage-rack 의 state.data 와 동일 idiom. */\n data?: StockpileRecord[]\n\n /** 적치 형태 프리셋. default 'row'. */\n stackPattern?: StackPattern\n /** 가상 carrier 종류 프리셋. default 'box'. */\n carrierPreset?: CarrierPreset\n\n /** 가상 carrier 한 개의 크기 (없으면 preset 별 기본). */\n carrierWidth?: number\n carrierHeight?: number\n carrierDepth?: number\n /** 적치된 carrier 사이의 평면(xz) 간격 — 0 이면 딱 붙음, default 3. y(단 적층)는 항상 딱 붙음. */\n carrierGap?: number\n\n /** 최대 적치 수 (undefined = 무제한). */\n capacity?: number\n /** 위로 몇 단까지 (undefined = stack 형태에 맡김). */\n stackHeightLimit?: number\n /** 어느 끝에서 빼나. default 'lifo'. */\n pickPolicy?: PickPolicy\n\n /** click 시 invoke 할 Popup 컴포넌트 id (StorageRack / RackGrid 와 동일 패턴). */\n popupRef?: string\n\n /**\n * Legend 컴포넌트 id. legend 의 `state.status = {field, ranges, defaultColor}` 를\n * 참조해 각 record 의 field 값을 색상으로 매핑한다 (StorageRack 와 동일 패턴).\n * 미명시 시 scene 안 `type='legend'` 첫 컴포넌트 자동 발견.\n */\n legendTarget?: string\n\n material3d?: Material3D\n}\n\nconst SLOT_ID = 'pile'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\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'\n}\n\n@sceneComponent('stockpile')\nexport default class Stockpile extends RecordStorage<StockpileRecord>()(\n CarrierHolder(Placeable(ContainerAbstract))\n) {\n declare state: StockpileState\n declare _realObject?: Stockpile3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 5 // pad 두께\n\n get nature(): ComponentNature { return NATURE }\n get anchors() { return [] }\n\n // ── records / inventoryCount: RecordStorage mixin 제공.\n\n // ── SlottedHolder duck-type override — 단일 slot ('pile') ───────────────\n // mixin default 는 record 마다 slotId. Stockpile 은 *_단일 슬롯_* 시맨틱이라\n // 자기 구현 유지.\n slotIds(): ReadonlyArray<string> { return [SLOT_ID] }\n\n hasCarrierAt(slotId: string): boolean {\n return slotId === SLOT_ID && this.inventoryCount > 0\n }\n\n canReceiveAt(slotId: string, _carrier?: Component): boolean {\n if (slotId !== SLOT_ID) return false\n const cap = this.state.capacity\n if (typeof cap === 'number' && this.inventoryCount >= cap) return false\n return true\n }\n\n occupiedSlotIds(): ReadonlyArray<string> {\n return this.inventoryCount > 0 ? [SLOT_ID] : []\n }\n emptySlotIds(): ReadonlyArray<string> {\n return this.canReceiveAt(SLOT_ID) ? [SLOT_ID] : []\n }\n\n /**\n * record 한 개를 빼서 carrier 컴포넌트로 transient materialize. mover 가 pickup\n * 하면 이걸 reparent 해서 들고 다닌다. storage-rack._materializeCarrier 와 동일\n * 패턴 — Component.register(type) 으로 클래스 lookup, addComponent({silent: true})\n * 로 cascade 차단.\n */\n obtainCarrier(slotId: string): Component | null {\n if (slotId !== SLOT_ID) return null\n if (this.records.length === 0) return null\n const records = [...this.records]\n const policy = (this.state.pickPolicy ?? 'lifo') as PickPolicy\n const record = policy === 'fifo' ? records.shift()! : records.pop()!\n this._setDataSilently(records)\n return this._materializeCarrier(record)\n }\n\n private _materializeCarrier(record: StockpileRecord): Component | null {\n // carrierPreset → 등록된 컴포넌트 type. 미등록(drum/sack/bale 등) 은 시각 mesh\n // 전용이라 carrier 컴포넌트로는 fallback ('parcel'/'box').\n const preset = (this.state.carrierPreset ?? 'box') as CarrierPreset\n const PRESET_TO_TYPE: Record<CarrierPreset, string> = {\n box: 'box',\n pallet: 'pallet',\n crate: 'box',\n drum: 'parcel',\n sack: 'parcel',\n 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] carrier type \"${carrierType}\" 미등록 — obtainCarrier 실패`)\n return null\n }\n\n // 크기 — state.carrier* 또는 preset default\n const stockpileW = (this.state as any).width ?? 100\n const stockpileH = (this.state as any).height ?? 100\n const cw = this.state.carrierWidth ?? 30\n const ch = this.state.carrierHeight ?? 30\n const cd = this.state.carrierDepth ?? 22\n\n // id/refid/transform 류 제외 — scene 안 기존 component 충돌 회피 (storage-rack 패턴)\n const { id: _id, refid: _refid, transform: _tf, ...recordCopy } = record as any\n // stockpile-inner 좌표 — center 에 놓기 (Mover 가 곧 pick 해 들고 감)\n const carrierState: any = {\n ...recordCopy,\n type: carrierType,\n width: cw,\n height: ch,\n depth: cd,\n refid: _nextStockpileCarrierRefid(),\n left: stockpileW / 2 - cw / 2,\n top: stockpileH / 2 - ch / 2\n }\n\n const carrier = new CarrierClass(carrierState, (this as any)._app)\n // silent: refreshMappings cascade 차단 (transient 라 매핑 재계산 불필요)\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 /**\n * carrier 를 받아 record 로 push, carrier 객체는 dispose (시각은 _realObject 가\n * records 길이 기준 자동 갱신). capacity 초과 시도는 canReceiveAt 가 이미 차단.\n */\n async receiveAt(_slotId: string, carrier: Component, _options?: any): Promise<void> {\n const cstate: any = (carrier as any)?.state ?? {}\n const cid = cstate.id ?? `stk-${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.records, record]\n this._setDataSilently(records)\n ;(carrier as any)?.dispose?.()\n }\n\n /** dispatch → handoff 의 accept 분기 — receiveAt 으로 위임. */\n async accept(carrier: Component, options?: any): Promise<void> {\n return this.receiveAt(SLOT_ID, carrier, options)\n }\n async receive(carrier: Component, options?: any): Promise<void> {\n return this.receiveAt(SLOT_ID, carrier, options)\n }\n\n /**\n * mover 가 obtainCarrier 로 빼낸 transient carrier 를 pad 위에 안착 (잠깐 보이는\n * 위치). mover 가 pick 하면 즉시 deck 으로 reparent 되므로 잔류는 짧다.\n * Spot 과 동일 idiom — pad-top + carrier 의 halfDepth 만큼 들어올림.\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 {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n slotTargetAt(slotId: string): SlotTarget {\n return new SlotTarget(this as any, slotId)\n }\n getSlotAttachObject3d(slotId: string): any {\n // slotId 가 'pile' 이면 pad. record.id 면 그 carrier mesh — popup tether 가 그\n // stock 에 정확히 연결되도록.\n return (this as any)._realObject?.getAttachFrame?.(slotId)\n }\n\n // _setDataSilently 는 RecordStorage mixin 제공 — 다만 mixin 은 _rebuildVisual\n // 호출. Stockpile 의 3D 갱신은 update() 라서 hook override.\n _rebuildVisual(): void {\n this._realObject?.update?.()\n }\n\n /**\n * 2D — outlined pad + 인벤토리 카운트 텍스트. 평치 의도가 한 눈에 — 사각 영역 위에\n * 적재된 carrier 수가 가운데 큰 글씨로.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#c89c5c'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n\n // pad (반투명)\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.18\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // outline (dashed)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = 1.5\n ctx.setLineDash([6, 3])\n ctx.strokeRect(left + 0.75, top + 0.75, width - 1.5, height - 1.5)\n ctx.setLineDash([])\n ctx.restore()\n\n // 인벤토리 수 + capacity (있으면)\n ctx.save()\n const fontSize = Math.min(width, height) * 0.22\n ctx.fillStyle = '#333'\n ctx.font = `bold ${fontSize}px sans-serif`\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n const label = typeof this.state.capacity === 'number'\n ? `${this.inventoryCount}/${this.state.capacity}`\n : `${this.inventoryCount}`\n ctx.fillText(label, left + width / 2, top + height / 2)\n ctx.restore()\n }\n\n // ── Popup 연동 — storage-rack 동일 패턴 ───────────────────────────────\n /**\n * things-scene EventManager3D 가 raycast → object3d.userData.context.component 의\n * `trigger(\"click\", mouseEvent)` 을 호출 → eventMap 으로 receive. pad / carrier mesh\n * 어느 쪽을 클릭하든 stockpile 전체 popup invoke (단일 slot 이라 cell 구분 없음).\n */\n get eventMap() {\n return {\n '(self)': {\n '(self)': {\n click: this._onStockpileClick\n }\n }\n }\n }\n\n private _onStockpileClick = (mouseEvent: MouseEvent) => {\n // view mode 에서만 동작 (modeling 중 click 은 framework 선택 로직 우선).\n if (!(this as any).app?.isViewMode) return\n const hit = this._raycastStockpileHit(mouseEvent)\n if (!hit) return\n // hit.object 가 carrier mesh 면 userData.recordId 보유 → 그 stock 의 popup.\n // pad / 기타면 stockpile 전체 popup.\n const recordId = hit.object?.userData?.recordId as string | undefined\n this._dispatchStockpilePopup(typeof recordId === 'string' ? recordId : undefined)\n }\n\n /**\n * state.popupRef 가 가리키는 Popup 컴포넌트를 invoke.\n * - recordId 명시 → 그 record 의 anchor = mesh. payload = 해당 record.\n * - 미명시 (pad 클릭) → 'pile' anchor (pad). payload = 전체 inventory.\n *\n * RecordStorage mixin 의 `_invokePopup(slotId, payload)` 를 활용. 단일 slot\n * 시맨틱이 record/pile 두 모드라 mixin 위 wrapper 로 dispatch.\n */\n private _dispatchStockpilePopup(recordId?: string): void {\n if (!this.state.popupRef) return\n if (recordId) {\n const record = (this.records as ReadonlyArray<StockpileRecord>)\n .find(r => r.id === recordId) ?? { id: recordId }\n this._invokePopup(recordId, record)\n } else {\n this._invokePopup(SLOT_ID, {\n componentId: (this.state as any).id,\n records: this.records,\n inventoryCount: this.inventoryCount,\n capacity: this.state.capacity,\n carrierPreset: this.state.carrierPreset,\n stackPattern: this.state.stackPattern\n })\n }\n }\n\n /**\n * 클릭 시 framework 의 mouse NDC 를 재사용해 raycast → *우리 stockpile* 의 어떤 mesh 가\n * closest hit 인지 반환 (다른 object 가 더 가까우면 undefined). storage-rack._raycastRackHit\n * 와 동일 패턴 — capability.getObjectsByRaycast 우선, 없으면 scene/camera/canvas 재구성.\n */\n private _raycastStockpileHit(mouseEvent: MouseEvent): THREE.Intersection | undefined {\n const ro: any = (this as any)._realObject\n if (!ro?.object3d) return undefined\n\n const tc: any = ro.threeContainer\n if (!tc) return undefined\n\n const cap: any = tc._threeCapability ?? tc._capability\n let intersects: THREE.Intersection[] | undefined\n if (cap?.getObjectsByRaycast) {\n intersects = cap.getObjectsByRaycast() as THREE.Intersection[] | undefined\n }\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\n // 가장 가까운 hit 이 *이 stockpile* 의 descendant 여야 한다 (다른 mesh 가 사이에 있으면 skip).\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 // legendTarget / _onLegendChanged / resolveLegendColor — RecordStorage mixin 제공.\n\n buildRealObject(): RealObject | undefined {\n return new Stockpile3D(this)\n }\n}\n\n// transient carrier refid — scene 내 기존 컴포넌트와 충돌 회피용 큰 시작값\n// (storage-rack 의 _nextCarrierRefid 와 같은 idiom, 별도 범위로 분리).\nlet _stockpileCarrierSeq = 0\nfunction _nextStockpileCarrierRefid(): number {\n return 800000 + (_stockpileCarrierSeq++)\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"]}
|
package/dist/storage-rack.d.ts
CHANGED
|
@@ -76,6 +76,10 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
76
76
|
static placement: PlacementArchetype;
|
|
77
77
|
static align: Alignment;
|
|
78
78
|
static defaultDepth: (h: Heights) => number;
|
|
79
|
+
_recordToSlotId(record: {
|
|
80
|
+
cellId: string;
|
|
81
|
+
}): string;
|
|
82
|
+
_rebuildVisual(): void;
|
|
79
83
|
get isObstacle(): boolean;
|
|
80
84
|
obstacleBoundingBox(): {
|
|
81
85
|
left: number;
|
|
@@ -129,11 +133,6 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
129
133
|
* anchor object3d* 를 반환 → carrier 의 object3d 가 자동으로 셀 위치에 정렬됨.
|
|
130
134
|
*/
|
|
131
135
|
attachPointFor(carrier: Component): AttachFrame | null;
|
|
132
|
-
/** state.data 의 record 목록 (읽기 전용 뷰). */
|
|
133
|
-
get records(): ReadonlyArray<{
|
|
134
|
-
cellId: string;
|
|
135
|
-
[key: string]: any;
|
|
136
|
-
}>;
|
|
137
136
|
/**
|
|
138
137
|
* 1-based (bay, row, level) → 0-based cellId 문자열.
|
|
139
138
|
*
|
|
@@ -181,19 +180,6 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
181
180
|
* obtainCarrier(1) ≡ obtainCarrier(1,1,1)
|
|
182
181
|
*/
|
|
183
182
|
obtainCarrier(idOrBay: string | number, row?: number, level?: number): Component | null;
|
|
184
|
-
/**
|
|
185
|
-
* State.data 의 *internal* 갱신 — Plan A 의 obtainCarrier / receiveAt 가 사용.
|
|
186
|
-
* `setState` 와 달리 *'change' 이벤트 / onchangeData / mapping cascade 를 우회*.
|
|
187
|
-
*
|
|
188
|
-
* 이유: mapping 시스템이 state.data 변경 시 *자동으로 script fire*. Plan A 의
|
|
189
|
-
* setState 가 그 cascade 를 트리거하면 사용자 script 가 *재귀적으로 자기 자신을 호출*
|
|
190
|
-
* 하는 회귀 (board 의 의도된 binding 일 수도, 우연일 수도) 발생.
|
|
191
|
-
*
|
|
192
|
-
* 대신 *직접 _state 갱신 + rebuildStockMesh 직접 호출* — 시각화는 갱신되지만 외부
|
|
193
|
-
* mapping 은 fire 안 됨. External (WMS / application setState) 호출은 그대로 setState
|
|
194
|
-
* 거치므로 그쪽 mapping 은 정상 동작.
|
|
195
|
-
*/
|
|
196
|
-
private _setDataSilently;
|
|
197
183
|
/**
|
|
198
184
|
* cell 이 carrier 를 받을 수 있는가.
|
|
199
185
|
*
|
|
@@ -290,21 +276,6 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
290
276
|
render(ctx: CanvasRenderingContext2D): void;
|
|
291
277
|
get fillStyle(): string;
|
|
292
278
|
onchangeData(): void;
|
|
293
|
-
private _legendTarget?;
|
|
294
|
-
/**
|
|
295
|
-
* Legend 컴포넌트 lookup. 우선순위:
|
|
296
|
-
* 1) state.legendTarget id 명시
|
|
297
|
-
* 2) scene 전체에서 `type='legend'` 첫 번째 컴포넌트 (자동 발견)
|
|
298
|
-
*/
|
|
299
|
-
get legendTarget(): Component | undefined;
|
|
300
|
-
private _onLegendChanged;
|
|
301
|
-
/**
|
|
302
|
-
* record 의 legend.field 값을 ranges 와 매칭해 색상 해석.
|
|
303
|
-
* - `range.value === recordValue` (카테고리 일치)
|
|
304
|
-
* - `range.min ≤ Number(v) < range.max` (수치 범위)
|
|
305
|
-
* - 매칭 없으면 `defaultColor` 또는 undefined
|
|
306
|
-
*/
|
|
307
|
-
resolveLegendColor(record: any): string | undefined;
|
|
308
279
|
/**
|
|
309
280
|
* things-scene EventManager3D 가 raycast → object3d.userData.context.component 의
|
|
310
281
|
* `trigger("click", mouseEvent)` 을 호출 → eventMap 으로 receive.
|
|
@@ -318,17 +289,6 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
318
289
|
};
|
|
319
290
|
};
|
|
320
291
|
private _onRackClick;
|
|
321
|
-
/**
|
|
322
|
-
* state.popupRef 가 가리키는 Popup 컴포넌트를 invoke. anchor 를 SlotTarget 으로
|
|
323
|
-
* 지정 — SlotTarget._realObject.object3d 가 cellId 위치의 anchor object3d 를
|
|
324
|
-
* 가리켜 tether / projectToScreen 정확.
|
|
325
|
-
*
|
|
326
|
-
* - popupRef 미설정 → no-op (event 만 발사된 상태로 남음)
|
|
327
|
-
* - 다른 cell 클릭 시 popup 이 새 anchor 로 "이동" (Popup 의 board 등 설정 유지)
|
|
328
|
-
* - frame/empty 영역 클릭 시 호출 안 됨 → popup 그대로 유지
|
|
329
|
-
* - 명시적 close 버튼은 popup 자체의 closable 옵션이 처리
|
|
330
|
-
*/
|
|
331
|
-
private _invokePopup;
|
|
332
292
|
/**
|
|
333
293
|
* 클릭 시 framework 의 mouse NDC (이미 InteractionManager 가 set 한 상태) 를 재사용해
|
|
334
294
|
* raycast → *우리 rack* 의 어떤 mesh 가 closest hit 인지 반환. 다른 object 가 더 가까우면
|
|
@@ -352,6 +312,29 @@ export default class Rack extends Rack_base implements SlottedHolder {
|
|
|
352
312
|
* 범위 밖이면 `"out-of-bounds(...)"` 문자열 반환 (caller 가 무시).
|
|
353
313
|
*/
|
|
354
314
|
private _cellIdFromWorldPoint;
|
|
315
|
+
/**
|
|
316
|
+
* Cell-precise approach point — mover 가 *_cell 정면 외부 stand-off_* 까지만.
|
|
317
|
+
*
|
|
318
|
+
* cellId format: `${bay - 1}-${row - 1}-${level - 1}` (0-based).
|
|
319
|
+
*
|
|
320
|
+
* **사용자 의도**: rack 은 *_전면_* 만 포크질 가능. front face = state.top 쪽
|
|
321
|
+
* (작은 z, 3D 의 -Z forward). back face 사용 금지 — forklift 가 rack 후면에
|
|
322
|
+
* 정차하는 동작은 잘못. navigation 의 obstacle avoidance 가 rack 우회 path 계산.
|
|
323
|
+
*
|
|
324
|
+
* forkLen — fork blade tip 부터 mover center 까지 거리. mover 가 그 거리만큼
|
|
325
|
+
* cell face 앞에서 정차 → 정확히 fork blade 가 cell face 정렬.
|
|
326
|
+
*
|
|
327
|
+
* (rotation 지원 V11 — 현재 axis-aligned rack 만, facing default = top.)
|
|
328
|
+
*/
|
|
329
|
+
slotApproachWorldPosition(slotId: string, _fromPos?: {
|
|
330
|
+
x: number;
|
|
331
|
+
y: number;
|
|
332
|
+
z: number;
|
|
333
|
+
}): {
|
|
334
|
+
x: number;
|
|
335
|
+
y: number;
|
|
336
|
+
z: number;
|
|
337
|
+
};
|
|
355
338
|
buildRealObject(): RealObject | undefined;
|
|
356
339
|
}
|
|
357
340
|
export {};
|