@operato/scene-storage 10.0.0-beta.33 → 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.
Files changed (89) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/crane-3d.d.ts +14 -0
  3. package/dist/crane-3d.js +238 -0
  4. package/dist/crane-3d.js.map +1 -0
  5. package/dist/crane.d.ts +157 -0
  6. package/dist/crane.js +440 -0
  7. package/dist/crane.js.map +1 -0
  8. package/dist/index.d.ts +13 -6
  9. package/dist/index.js +9 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/mobile-storage-rack.d.ts +17 -0
  12. package/dist/mobile-storage-rack.js +55 -0
  13. package/dist/mobile-storage-rack.js.map +1 -0
  14. package/dist/rack-column.d.ts +35 -0
  15. package/dist/rack-column.js +258 -0
  16. package/dist/rack-column.js.map +1 -0
  17. package/dist/rack-grid-3d.d.ts +13 -0
  18. package/dist/rack-grid-3d.js +94 -0
  19. package/dist/rack-grid-3d.js.map +1 -0
  20. package/dist/rack-grid-cell.d.ts +341 -0
  21. package/dist/rack-grid-cell.js +321 -0
  22. package/dist/rack-grid-cell.js.map +1 -0
  23. package/dist/rack-grid-helpers.d.ts +28 -0
  24. package/dist/rack-grid-helpers.js +71 -0
  25. package/dist/rack-grid-helpers.js.map +1 -0
  26. package/dist/rack-grid-location.d.ts +37 -0
  27. package/dist/rack-grid-location.js +227 -0
  28. package/dist/rack-grid-location.js.map +1 -0
  29. package/dist/rack-grid.d.ts +80 -0
  30. package/dist/rack-grid.js +829 -0
  31. package/dist/rack-grid.js.map +1 -0
  32. package/dist/stock.d.ts +78 -0
  33. package/dist/stock.js +333 -0
  34. package/dist/stock.js.map +1 -0
  35. package/dist/{rack-cell-3d.d.ts → storage-cell-3d.d.ts} +1 -1
  36. package/dist/{rack-cell-3d.js → storage-cell-3d.js} +3 -3
  37. package/dist/storage-cell-3d.js.map +1 -0
  38. package/dist/{rack-cell.d.ts → storage-cell.d.ts} +12 -6
  39. package/dist/{rack-cell.js → storage-cell.js} +9 -9
  40. package/dist/storage-cell.js.map +1 -0
  41. package/dist/{asrs-rack-3d.d.ts → storage-rack-3d.d.ts} +1 -1
  42. package/dist/{asrs-rack-3d.js → storage-rack-3d.js} +4 -4
  43. package/dist/storage-rack-3d.js.map +1 -0
  44. package/dist/{asrs-rack.d.ts → storage-rack.d.ts} +22 -16
  45. package/dist/{asrs-rack.js → storage-rack.js} +32 -26
  46. package/dist/storage-rack.js.map +1 -0
  47. package/dist/templates/index.d.ts +60 -0
  48. package/dist/templates/index.js +59 -17
  49. package/dist/templates/index.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/crane-3d.ts +273 -0
  52. package/src/crane.ts +538 -0
  53. package/src/index.ts +13 -6
  54. package/src/mobile-storage-rack.ts +56 -0
  55. package/src/rack-column.ts +340 -0
  56. package/src/rack-grid-3d.ts +128 -0
  57. package/src/rack-grid-cell.ts +404 -0
  58. package/src/rack-grid-helpers.ts +77 -0
  59. package/src/rack-grid-location.ts +286 -0
  60. package/src/rack-grid.ts +994 -0
  61. package/src/stock.ts +426 -0
  62. package/src/{rack-cell-3d.ts → storage-cell-3d.ts} +2 -2
  63. package/src/{rack-cell.ts → storage-cell.ts} +19 -13
  64. package/src/{asrs-rack-3d.ts → storage-rack-3d.ts} +3 -3
  65. package/src/{asrs-rack.ts → storage-rack.ts} +31 -25
  66. package/src/templates/index.ts +59 -17
  67. package/test/test-rack-grid-crane.ts +212 -0
  68. package/test/test-rack-grid.ts +77 -0
  69. package/test/{test-asrs-crane.ts → test-storage-rack-crane.ts} +8 -8
  70. package/translations/en.json +55 -7
  71. package/translations/ja.json +52 -4
  72. package/translations/ko.json +52 -4
  73. package/translations/ms.json +55 -7
  74. package/translations/zh.json +52 -4
  75. package/tsconfig.tsbuildinfo +1 -1
  76. package/dist/asrs-crane-3d.d.ts +0 -17
  77. package/dist/asrs-crane-3d.js +0 -181
  78. package/dist/asrs-crane-3d.js.map +0 -1
  79. package/dist/asrs-crane.d.ts +0 -98
  80. package/dist/asrs-crane.js +0 -216
  81. package/dist/asrs-crane.js.map +0 -1
  82. package/dist/asrs-rack-3d.js.map +0 -1
  83. package/dist/asrs-rack.js.map +0 -1
  84. package/dist/rack-cell-3d.js.map +0 -1
  85. package/dist/rack-cell.js.map +0 -1
  86. package/src/asrs-crane-3d.ts +0 -211
  87. package/src/asrs-crane.ts +0 -275
  88. /package/icons/{asrs-crane.png → crane.png} +0 -0
  89. /package/icons/{asrs-rack.png → storage-rack.png} +0 -0
package/dist/crane.js ADDED
@@ -0,0 +1,440 @@
1
+ import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ */
5
+ import { ContainerAbstract, ContainerCapacity, sceneComponent } from '@hatiolab/things-scene';
6
+ import { CarrierHolder, Legendable, Mover, Placeable } from '@operato/scene-base';
7
+ import { Crane3D } from './crane-3d.js';
8
+ const BODY_LEGEND = {
9
+ idle: '#888',
10
+ moving: '#aabbcc',
11
+ loading: '#ffaa00',
12
+ unloading: '#ffaa00',
13
+ error: '#c66',
14
+ default: '#888'
15
+ };
16
+ const LAMP_EMISSIVE_LEGEND = {
17
+ idle: '#222222',
18
+ moving: '#44ff44',
19
+ loading: '#ffaa00',
20
+ unloading: '#ffaa00',
21
+ error: '#ff3333',
22
+ default: '#222222'
23
+ };
24
+ const NATURE = {
25
+ mutable: false,
26
+ resizable: true,
27
+ rotatable: true,
28
+ properties: [
29
+ {
30
+ type: 'select',
31
+ label: 'status',
32
+ name: 'status',
33
+ property: {
34
+ options: [
35
+ { display: 'Idle', value: 'idle' },
36
+ { display: 'Moving', value: 'moving' },
37
+ { display: 'Loading', value: 'loading' },
38
+ { display: 'Unloading', value: 'unloading' },
39
+ { display: 'Error', value: 'error' }
40
+ ]
41
+ }
42
+ },
43
+ {
44
+ type: 'string',
45
+ label: 'target',
46
+ name: 'target',
47
+ placeholder: 'refid of target (e.g., a RackGrid) — crane serves only its cells'
48
+ },
49
+ {
50
+ type: 'number',
51
+ label: 'carriage-height',
52
+ name: 'carriageHeight',
53
+ placeholder: 'mm — height of carriage on mast'
54
+ },
55
+ {
56
+ type: 'number',
57
+ label: 'fork-length',
58
+ name: 'forkLength',
59
+ placeholder: 'mm — fork prong 최대 신축 길이 (default 600)'
60
+ },
61
+ {
62
+ type: 'number',
63
+ label: 'fork-extension',
64
+ name: 'forkExtension',
65
+ placeholder: 'mm — 현재 신축 거리 (0 = collapsed)'
66
+ },
67
+ {
68
+ type: 'number',
69
+ label: 'fork-lift',
70
+ name: 'forkLift',
71
+ placeholder: 'mm — carrier 들어올림 높이 (+ 위로)'
72
+ }
73
+ ],
74
+ help: 'scene/component/crane'
75
+ };
76
+ // Mixin chain: Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract
77
+ //
78
+ // Mover: pick / place / pickAndPlace / moveTo / engage primitives
79
+ // CarrierHolder: attachPointFor() — where the carrier sits on the crane (carriage fork)
80
+ // ContainerCapacity: receive() / dispatch() / canReceive() / slots — slot tracking +
81
+ // TRANSFER_SLOT_KEY bookkeeping during transit
82
+ // Legendable: status → bodyColor / lampEmissive colour mapping
83
+ // Placeable: floor-archetype 3D positioning
84
+ // ContainerAbstract: child management — carrier becomes a child while in transit
85
+ //
86
+ // Note: ContainerAbstract replaces Shape. The 2D outline is drawn manually in
87
+ // render() below (a simple top-down rectangle), matching the old Shape output
88
+ // without the Shape base-class overhead.
89
+ /**
90
+ * Crane — a stacker / retrieval picker. Picker contract instance.
91
+ *
92
+ * Moves carriers between rack cells (or any Placeable target). The crane
93
+ * is *picker-side* of the Phase G/H Pickable contract — it knows how to
94
+ * `pick(carrier)` / `place(carrier, target)` and `engage(target, kind)`
95
+ * to align its actuators. It is *storage-agnostic* — any Rack / Cell /
96
+ * Spot / Conveyor that implements the receive/dispatch protocol is a
97
+ * valid target.
98
+ *
99
+ * Structure: a tall vertical mast that translates horizontally on a rail
100
+ * or free floor path, with a carriage that slides up/down the mast carrying
101
+ * a shuttle / forks.
102
+ *
103
+ * **Monitoring mode**: crane status is driven by external data binding
104
+ * (`state.status`, `state.carriageHeight`). The carrier is referenced
105
+ * via data binding — it is NOT a child of the crane in monitoring mode.
106
+ *
107
+ * **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, target)`
108
+ * (or `crane.pickAndPlace(carrier, target)`). Mover handles navigation +
109
+ * engage + reparent. During transit the carrier IS a child of the crane.
110
+ */
111
+ let Crane = class Crane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {
112
+ static legends = {
113
+ bodyColor: { from: 'status', legend: BODY_LEGEND },
114
+ lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
115
+ };
116
+ static placement = 'floor';
117
+ static align = 'bottom';
118
+ static defaultDepth = (h) => h.ceiling - h.floor;
119
+ /** Yaw offset: crane model is drawn with the rail axis along X (right = forward). */
120
+ static yawOffset = 0;
121
+ /**
122
+ * Phase H — the crane uses telescoping forks to pick up a carrier in a rack
123
+ * cell. Same mechanism as a forklift fork — engages the pallet's fork pocket.
124
+ * (Mover mixin chain is `: any` so override keyword is not usable, only the
125
+ * getter is defined.)
126
+ */
127
+ get toolType() {
128
+ return 'forklift-fork';
129
+ }
130
+ get nature() {
131
+ return NATURE;
132
+ }
133
+ get anchors() {
134
+ return [];
135
+ }
136
+ // ── ContainerCapacity ─────────────────────────────────────────────────────
137
+ /** Stacker crane carries at most one load at a time on its forks. */
138
+ get slots() {
139
+ return [{ id: 'forks', maxCount: 1 }];
140
+ }
141
+ // ── CarrierHolder — attach frame (carriage fork position) ─────────────────
142
+ /**
143
+ * Return the 3D attach frame on the crane's carriage (fork tip).
144
+ * Carriers are attached here while the crane is in transit (pick phase).
145
+ *
146
+ * The Crane3D exposes `getCarriageFrame()` — a sub-Object3D that
147
+ * tracks the carriage height and sits at the fork TCP. If the 3D object
148
+ * isn't built yet (e.g. before scene initialization), fall back to the
149
+ * crane's own object3d centre.
150
+ */
151
+ attachPointFor(carrier) {
152
+ const ro = this._realObject;
153
+ const frame = ro?.getCarriageFrame?.();
154
+ if (frame) {
155
+ const carrierDepth = resolveCarrierDepth(carrier);
156
+ // fork blade 위 (top surface) 에 pallet pocket 끼우듯 얹힘. y = blade top + carrier/2.
157
+ // z = blade 중심 (forkLength 의 반쪽 위치 — pallet center 가 blade 중간에 위치).
158
+ const platformTopY = ro?.platformTopY ?? 0;
159
+ const bladeMidZ = ro?.bladeMidZ ?? 0;
160
+ return {
161
+ attach: frame,
162
+ localPosition: { x: 0, y: platformTopY + carrierDepth / 2, z: bladeMidZ }
163
+ };
164
+ }
165
+ const root = this._realObject?.object3d;
166
+ if (!root)
167
+ return null;
168
+ return { attach: root };
169
+ }
170
+ // ── Mover overrides ───────────────────────────────────────────────────────
171
+ /**
172
+ * Domain-specific actuation between arrival and reparent.
173
+ *
174
+ * Simulation sequence for PICK:
175
+ * 1. Mover.pick() navigates crane to carrier position (moveTo).
176
+ * 2. engage('pick') → snap carriage height + status 'loading'.
177
+ * 3. Carrier is reparented to crane (becomes child).
178
+ *
179
+ * For now: set status and snap carriage height. A full simulation
180
+ * would tween the carriageHeight here (animate Crane3D).
181
+ *
182
+ * Status lifecycle:
183
+ * idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle
184
+ * The 'moving' state is not set from Mover.moveTo() because TypeScript
185
+ * can't call super.moveTo() on an `: any`-typed mixin. External data
186
+ * binding sets 'moving' in monitoring mode; override pick()/place() to
187
+ * set it in full simulation environments.
188
+ */
189
+ async engage(target, kind, _options = {}) {
190
+ if (kind === 'pick') {
191
+ this.setState({ status: 'loading' });
192
+ const carrierY = resolveCarrierCenterY(target);
193
+ if (carrierY !== null) {
194
+ this.setState({ carriageHeight: carrierY });
195
+ }
196
+ }
197
+ else {
198
+ this.setState({ status: 'unloading' });
199
+ }
200
+ // In a full simulation: await carriage-motion tween here.
201
+ }
202
+ // ── Domain aliases ────────────────────────────────────────────────────────
203
+ /** Fetch a carrier from a rack cell (semantically = pick). */
204
+ fetch(carrier, options) {
205
+ return this.pick(carrier, options);
206
+ }
207
+ /** Deposit a carrier into a rack cell (semantically = place). */
208
+ deposit(carrier, cell, options) {
209
+ return this.place(carrier, cell, options);
210
+ }
211
+ // ── 2D rendering ─────────────────────────────────────────────────────────
212
+ /**
213
+ * 2D — top-down rectangle showing the crane's footprint along the rail.
214
+ * The crane is much taller than wide, so the 2D mark is small.
215
+ */
216
+ render(ctx) {
217
+ const { width, height, left, top } = this.state;
218
+ const fillColor = this.state.bodyColor || '#888';
219
+ ctx.save();
220
+ ctx.fillStyle = fillColor;
221
+ ctx.beginPath();
222
+ ctx.rect(left, top, width, height);
223
+ ctx.fill();
224
+ ctx.restore();
225
+ }
226
+ // ── 3D ───────────────────────────────────────────────────────────────────
227
+ buildRealObject() {
228
+ if (!this._simStarted) {
229
+ this._simStarted = true;
230
+ // 초기 지연 800~2800ms 사이 random — 여러 crane 이 동기적으로 시작하지 않도록.
231
+ const initialDelay = 800 + Math.random() * 2000;
232
+ setTimeout(() => {
233
+ this.simulate().catch(e => console.error('[Crane] simulate', e));
234
+ }, initialDelay);
235
+ }
236
+ return new Crane3D(this);
237
+ }
238
+ // ── Random animation (visual smoke test) ─────────────────────────────────
239
+ // things-scene 의 component.animate() (frameClock 기반, pause/speed 대응) 를 사용.
240
+ _simStarted = false;
241
+ _simRunning = false;
242
+ _simAbort = false;
243
+ // Rail = crane 의 *로컬 X 축* (state.rotation 적용). simulate 진입 시 1회 계산해서 캐시.
244
+ _railOriginX = 0;
245
+ _railOriginY = 0;
246
+ _railCos = 1;
247
+ _railSin = 0;
248
+ _railMin = NaN; // local-X 축 rail offset min
249
+ _railMax = NaN; // local-X 축 rail offset max
250
+ _targetSide = 1; // +Z (fork +Z 로 뻗어야 하나) / -Z
251
+ /** Continuous random pick → transport → place cycles. Visual smoke test. */
252
+ async simulate() {
253
+ if (this._simRunning)
254
+ return;
255
+ this._simRunning = true;
256
+ this._simAbort = false;
257
+ try {
258
+ this._initRailRange();
259
+ while (!this._simAbort) {
260
+ await this._oneCycle();
261
+ }
262
+ }
263
+ finally {
264
+ this._simRunning = false;
265
+ }
266
+ }
267
+ /**
268
+ * state.target bbox 를 crane 의 *로컬 X 축* 으로 projection 해서 rail range 1회 계산.
269
+ * Rotation 적용된 crane 의 local X (= rail) 방향으로 움직이도록 cos/sin 캐시.
270
+ */
271
+ _initRailRange() {
272
+ this._railMin = this._railMax = NaN;
273
+ // 1순위: state.target refid 로 명시. 2순위: crane 의 parent 컴포넌트 (자연스러운
274
+ // "이 컨테이너 안에서만 움직여" 기본값).
275
+ const targetRefId = this.state.target;
276
+ const root = this.root;
277
+ let target = targetRefId ? root?.findById?.(targetRefId) : null;
278
+ if (!target) {
279
+ const parent = this.parent;
280
+ // parent 가 root/model-layer 같은 top-level 이면 bbox 가 의미없음 → skip
281
+ if (parent && parent !== root && parent.state?.width > 0) {
282
+ target = parent;
283
+ }
284
+ }
285
+ if (!target)
286
+ return;
287
+ const tState = target.state ?? {};
288
+ const tl = numOr(tState.left, 0);
289
+ const tt = numOr(tState.top, 0);
290
+ const tw = numOr(tState.width, 0);
291
+ const th = numOr(tState.height, 0);
292
+ if (tw <= 0 || th <= 0)
293
+ return;
294
+ const W = numOr(this.state.width, 100);
295
+ const H = numOr(this.state.height, 100);
296
+ const rotation = numOr(this.state.rotation, 0);
297
+ const cos = Math.cos(rotation);
298
+ const sin = Math.sin(rotation);
299
+ // Rail origin = crane 의 현재 canvas center. 이 점에서 local X 방향으로 ±rail offset.
300
+ this._railOriginX = numOr(this.state.left, 0) + W / 2;
301
+ this._railOriginY = numOr(this.state.top, 0) + H / 2;
302
+ this._railCos = cos;
303
+ this._railSin = sin;
304
+ // Target bbox 4 corner 를 crane local frame 으로 projection.
305
+ // local X (rail) = dx*cos + dy*sin
306
+ // local Z (aisle) = -dx*sin + dy*cos
307
+ const corners = [
308
+ { x: tl, y: tt },
309
+ { x: tl + tw, y: tt },
310
+ { x: tl, y: tt + th },
311
+ { x: tl + tw, y: tt + th }
312
+ ];
313
+ let minRail = Infinity;
314
+ let maxRail = -Infinity;
315
+ let aisleSum = 0;
316
+ for (const c of corners) {
317
+ const dx = c.x - this._railOriginX;
318
+ const dy = c.y - this._railOriginY;
319
+ const rail = dx * cos + dy * sin;
320
+ const aisle = -dx * sin + dy * cos;
321
+ if (rail < minRail)
322
+ minRail = rail;
323
+ if (rail > maxRail)
324
+ maxRail = rail;
325
+ aisleSum += aisle;
326
+ }
327
+ // Crane 자체 W 만큼 안쪽으로 좁혀 — crane center 가 [minRail+W/2, maxRail-W/2] 안에
328
+ // 있으면 crane 전체가 target bbox 안 (local X 방향).
329
+ this._railMin = minRail + W / 2;
330
+ this._railMax = Math.max(this._railMin, maxRail - W / 2);
331
+ this._targetSide = aisleSum >= 0 ? +1 : -1;
332
+ }
333
+ stopSimulate() {
334
+ this._simAbort = true;
335
+ }
336
+ async _oneCycle() {
337
+ const W = numOr(this.state.width, 100);
338
+ const H = numOr(this.state.height, 100);
339
+ const D = numOr(this.state.depth, W * 4);
340
+ const forkLen = numOr(this.state.forkLength, H * 0.6);
341
+ if (!Number.isFinite(this._railMin) || !Number.isFinite(this._railMax)) {
342
+ await new Promise(r => setTimeout(r, 800));
343
+ return;
344
+ }
345
+ const pickRail = () => this._railMin + Math.random() * Math.max(0, this._railMax - this._railMin);
346
+ const toLeftTop = (rail) => ({
347
+ left: this._railOriginX + rail * this._railCos - W / 2,
348
+ top: this._railOriginY + rail * this._railSin - H / 2
349
+ });
350
+ const source = toLeftTop(pickRail());
351
+ const dest = toLeftTop(pickRail());
352
+ const sourceCH = Math.random() * D * 0.75;
353
+ const destCH = Math.random() * D * 0.75;
354
+ const liftH = Math.max(20, D * 0.02);
355
+ const side = this._targetSide;
356
+ // Tween duration 은 base 의 70~130% 사이 random — 여러 crane 이 같은 타이밍으로 안 보이도록.
357
+ const jitter = (base) => base * (0.7 + Math.random() * 0.6);
358
+ // 이동: crane local X 방향 → canvas (left, top) 둘 다 동시 변경 (rotation 적용 시 diagonal)
359
+ await this._tween({ status: 'moving', left: source.left, top: source.top, carriageHeight: sourceCH }, jitter(1500));
360
+ await this._tween({ status: 'loading', forkExtension: side * forkLen }, jitter(700));
361
+ await this._tween({ forkLift: liftH }, jitter(400));
362
+ await this._tween({ forkExtension: 0 }, jitter(700));
363
+ await this._tween({ status: 'moving', left: dest.left, top: dest.top, carriageHeight: destCH }, jitter(1500));
364
+ await this._tween({ status: 'unloading', forkExtension: side * forkLen }, jitter(700));
365
+ await this._tween({ forkLift: 0 }, jitter(400));
366
+ await this._tween({ status: 'idle', forkExtension: 0 }, jitter(700));
367
+ // 사이클 사이 짧은 idle (200~1000ms) — 자연스러운 phase 분산
368
+ await new Promise(r => setTimeout(r, 200 + Math.random() * 800));
369
+ }
370
+ /**
371
+ * things-scene 의 `component.animate()` 로 한 구간 tween. step 콜백에서 setState 호출.
372
+ * frameClock.simTime 기반이라 scene pause / speed change / suppressAnimations 모두 자동 대응.
373
+ */
374
+ _tween(targets, duration) {
375
+ const start = {};
376
+ for (const k of Object.keys(targets)) {
377
+ if (k === 'status')
378
+ continue;
379
+ const v = this.state[k];
380
+ start[k] = typeof v === 'number' && Number.isFinite(v) ? v : 0;
381
+ }
382
+ if (typeof targets.status === 'string') {
383
+ this.setState({ status: targets.status });
384
+ }
385
+ return new Promise(resolve => {
386
+ let resolved = false;
387
+ const finish = () => {
388
+ if (resolved)
389
+ return;
390
+ resolved = true;
391
+ resolve();
392
+ };
393
+ const controller = this.animate({
394
+ duration,
395
+ ease: 'inout',
396
+ delta: 'quad',
397
+ step: (dx) => {
398
+ if (this._simAbort) {
399
+ controller.stop?.();
400
+ return finish();
401
+ }
402
+ const update = {};
403
+ for (const k of Object.keys(start)) {
404
+ const tgt = targets[k];
405
+ if (typeof tgt !== 'number')
406
+ continue;
407
+ update[k] = start[k] + (tgt - start[k]) * dx;
408
+ }
409
+ this.setState(update);
410
+ },
411
+ onComplete: finish
412
+ });
413
+ controller.start();
414
+ });
415
+ }
416
+ };
417
+ Crane = __decorate([
418
+ sceneComponent('crane')
419
+ ], Crane);
420
+ export default Crane;
421
+ function resolveCarrierDepth(c) {
422
+ const eff = c._realObject?.effectiveDepth;
423
+ if (typeof eff === 'number' && Number.isFinite(eff))
424
+ return eff;
425
+ return numOr(c?.state?.depth, 0);
426
+ }
427
+ function resolveCarrierCenterY(c) {
428
+ const pos = c.state;
429
+ if (!pos)
430
+ return null;
431
+ // zPos is the 3D Y center of a Placeable component in things-scene
432
+ const zPos = numOr(pos.zPos, NaN);
433
+ if (!Number.isNaN(zPos))
434
+ return zPos;
435
+ return null;
436
+ }
437
+ function numOr(v, dflt) {
438
+ return typeof v === 'number' && Number.isFinite(v) ? v : dflt;
439
+ }
440
+ //# sourceMappingURL=crane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crane.js","sourceRoot":"","sources":["../src/crane.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA8B,iBAAiB,EAAE,iBAAiB,EAAc,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAErI,OAAO,EACL,aAAa,EACb,UAAU,EACV,KAAK,EACL,SAAS,EAOV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAuDvC,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,MAAM;CAChB,CAAA;AAED,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,SAAS;IAClB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACtC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;oBAC5C,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,WAAW,EAAE,kEAAkE;SAChF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,iBAAiB;YACxB,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,iCAAiC;SAC/C;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,aAAa;YACpB,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,wCAAwC;SACtD;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE,eAAe;YACrB,WAAW,EAAE,+BAA+B;SAC7C;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,UAAU;YAChB,WAAW,EAAE,6BAA6B;SAC3C;KACF;IACD,IAAI,EAAE,uBAAuB;CAC9B,CAAA;AAED,sGAAsG;AACtG,EAAE;AACF,gFAAgF;AAChF,8FAA8F;AAC9F,uFAAuF;AACvF,oEAAoE;AACpE,wEAAwE;AACxE,sDAAsD;AACtD,mFAAmF;AACnF,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAC9E,yCAAyC;AACzC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEY,IAAM,KAAK,GAAX,MAAM,KAAM,SAAQ,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,UAAU,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAIlH,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;QAClD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE;KAC/D,CAAA;IAED,MAAM,CAAC,SAAS,GAAuB,OAAO,CAAA;IAC9C,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,KAAK,CAAA;IAEzD,qFAAqF;IACrF,MAAM,CAAC,SAAS,GAAG,CAAC,CAAA;IAEpB;;;;;OAKG;IACH,IAAI,QAAQ;QACV,OAAO,eAAe,CAAA;IACxB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED,6EAA6E;IAE7E,qEAAqE;IACrE,IAAI,KAAK;QACP,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;IACvC,CAAC;IAED,6EAA6E;IAE7E;;;;;;;;OAQG;IACH,cAAc,CAAC,OAAkB;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAA;QAC3B,MAAM,KAAK,GAAG,EAAE,EAAE,gBAAgB,EAAE,EAAE,CAAA;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,YAAY,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAA;YACjD,gFAAgF;YAChF,oEAAoE;YACpE,MAAM,YAAY,GAAG,EAAE,EAAE,YAAY,IAAI,CAAC,CAAA;YAC1C,MAAM,SAAS,GAAG,EAAE,EAAE,SAAS,IAAI,CAAC,CAAA;YACpC,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE;aAC1E,CAAA;QACH,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAA;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAA;QACtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzB,CAAC;IAED,6EAA6E;IAE7E;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,MAAM,CACV,MAAiB,EACjB,IAAsB,EACtB,WAAwB,EAAE;QAE1B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAwB,EAAE,CAAC,CAAA;YACnD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAA;YAC9C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC,QAAQ,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC7C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,WAA0B,EAAE,CAAC,CAAA;QACvD,CAAC;QACD,0DAA0D;IAC5D,CAAC;IAED,6EAA6E;IAE7E,8DAA8D;IAC9D,KAAK,CAAC,OAAkB,EAAE,OAAqB;QAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IACpC,CAAC;IAED,iEAAiE;IACjE,OAAO,CAAC,OAAkB,EAAE,IAAe,EAAE,OAAqB;QAChE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAED,4EAA4E;IAE5E;;;OAGG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,SAAS,GAAI,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,MAAM,CAAA;QAC5D,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;QAClC,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,4EAA4E;IAE5E,eAAe;QACb,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;YACvB,0DAA0D;YAC1D,MAAM,YAAY,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAA;YAC/C,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAA;YAClE,CAAC,EAAE,YAAY,CAAC,CAAA;QAClB,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,4EAA4E;IAC5E,2EAA2E;IAEnE,WAAW,GAAG,KAAK,CAAA;IACnB,WAAW,GAAG,KAAK,CAAA;IACnB,SAAS,GAAG,KAAK,CAAA;IACzB,yEAAyE;IACjE,YAAY,GAAG,CAAC,CAAA;IAChB,YAAY,GAAG,CAAC,CAAA;IAChB,QAAQ,GAAG,CAAC,CAAA;IACZ,QAAQ,GAAG,CAAC,CAAA;IACZ,QAAQ,GAAG,GAAG,CAAA,CAAM,4BAA4B;IAChD,QAAQ,GAAG,GAAG,CAAA,CAAM,4BAA4B;IAChD,WAAW,GAAG,CAAC,CAAA,CAAK,6BAA6B;IAEzD,4EAA4E;IAC5E,KAAK,CAAC,QAAQ;QACZ,IAAI,IAAI,CAAC,WAAW;YAAE,OAAM;QAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;QACtB,IAAI,CAAC;YACH,IAAI,CAAC,cAAc,EAAE,CAAA;YACrB,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACvB,MAAM,IAAI,CAAC,SAAS,EAAE,CAAA;YACxB,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,GAAG,KAAK,CAAA;QAC1B,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,cAAc;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QAEnC,gEAAgE;QAChE,0BAA0B;QAC1B,MAAM,WAAW,GAAI,IAAI,CAAC,KAAa,CAAC,MAA4B,CAAA;QACpE,MAAM,IAAI,GAAI,IAAY,CAAC,IAAI,CAAA;QAC/B,IAAI,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC/D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,MAAM,GAAI,IAAY,CAAC,MAAM,CAAA;YACnC,+DAA+D;YAC/D,IAAI,MAAM,IAAI,MAAM,KAAK,IAAI,IAAK,MAAM,CAAC,KAAa,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC;gBAClE,MAAM,GAAG,MAAM,CAAA;YACjB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAM;QAEnB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAA;QACjC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAChC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QAC/B,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;QACjC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAClC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,OAAM;QAE9B,MAAM,CAAC,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC/C,MAAM,CAAC,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAChD,MAAM,QAAQ,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAE9B,2EAA2E;QAC3E,IAAI,CAAC,YAAY,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9D,IAAI,CAAC,YAAY,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7D,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QACnB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAA;QAEnB,0DAA0D;QAC1D,mCAAmC;QACnC,qCAAqC;QACrC,MAAM,OAAO,GAAG;YACd,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;YAChB,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;YACrB,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;YACrB,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;SAC3B,CAAA;QACD,IAAI,OAAO,GAAG,QAAQ,CAAA;QACtB,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAA;QACvB,IAAI,QAAQ,GAAG,CAAC,CAAA;QAChB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAA;YAClC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAA;YAClC,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,CAAA;YAChC,MAAM,KAAK,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,CAAA;YAClC,IAAI,IAAI,GAAG,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAA;YAClC,IAAI,IAAI,GAAG,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAA;YAClC,QAAQ,IAAI,KAAK,CAAA;QACnB,CAAC;QACD,uEAAuE;QACvE,4CAA4C;QAC5C,IAAI,CAAC,QAAQ,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACxD,IAAI,CAAC,WAAW,GAAG,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC;IAED,YAAY;QACV,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACvB,CAAC;IAEO,KAAK,CAAC,SAAS;QACrB,MAAM,CAAC,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QAC/C,MAAM,CAAC,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAChD,MAAM,CAAC,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACjD,MAAM,OAAO,GAAG,KAAK,CAAE,IAAI,CAAC,KAAa,CAAC,UAAU,EAAE,CAAC,GAAG,GAAG,CAAC,CAAA;QAE9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;YAC1C,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAA;QACjG,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,EAAE,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC;YACtD,GAAG,EAAE,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC;SACtD,CAAC,CAAA;QAEF,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAA;QAElC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAA;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAA;QAE7B,0EAA0E;QAC1E,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAA;QAE3E,+EAA+E;QAC/E,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QACnH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,IAAI,GAAG,OAAO,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QACpF,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QACnD,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QACpD,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;QAC7G,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,GAAG,OAAO,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QACtF,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAC/C,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QAEpE,+CAA+C;QAC/C,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAA;IAClE,CAAC;IAED;;;OAGG;IACK,MAAM,CAAC,OAA4B,EAAE,QAAgB;QAC3D,MAAM,KAAK,GAA2B,EAAE,CAAA;QACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,QAAQ;gBAAE,SAAQ;YAC5B,MAAM,CAAC,GAAI,IAAI,CAAC,KAAa,CAAC,CAAC,CAAC,CAAA;YAChC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,CAAC;QACD,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACvC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,IAAI,OAAO,CAAO,OAAO,CAAC,EAAE;YACjC,IAAI,QAAQ,GAAG,KAAK,CAAA;YACpB,MAAM,MAAM,GAAG,GAAG,EAAE;gBAClB,IAAI,QAAQ;oBAAE,OAAM;gBACpB,QAAQ,GAAG,IAAI,CAAA;gBACf,OAAO,EAAE,CAAA;YACX,CAAC,CAAA;YACD,MAAM,UAAU,GAAI,IAAY,CAAC,OAAO,CAAC;gBACvC,QAAQ;gBACR,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,MAAM;gBACb,IAAI,EAAE,CAAC,EAAU,EAAE,EAAE;oBACnB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;wBACnB,UAAU,CAAC,IAAI,EAAE,EAAE,CAAA;wBACnB,OAAO,MAAM,EAAE,CAAA;oBACjB,CAAC;oBACD,MAAM,MAAM,GAA2B,EAAE,CAAA;oBACzC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;wBACnC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;wBACtB,IAAI,OAAO,GAAG,KAAK,QAAQ;4BAAE,SAAQ;wBACrC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAA;oBAC9C,CAAC;oBACD,IAAI,CAAC,QAAQ,CAAC,MAAa,CAAC,CAAA;gBAC9B,CAAC;gBACD,UAAU,EAAE,MAAM;aACnB,CAAC,CAAA;YACF,UAAU,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC,CAAC,CAAA;IACJ,CAAC;;AAjVkB,KAAK;IADzB,cAAc,CAAC,OAAO,CAAC;GACH,KAAK,CAkVzB;eAlVoB,KAAK;AAoV1B,SAAS,mBAAmB,CAAC,CAAY;IACvC,MAAM,GAAG,GAAI,CAAS,CAAC,WAAW,EAAE,cAAc,CAAA;IAClD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/D,OAAO,KAAK,CAAE,CAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AAC3C,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAY;IACzC,MAAM,GAAG,GAAI,CAAS,CAAC,KAAK,CAAA;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,mEAAmE;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,KAAK,CAAC,CAAU,EAAE,IAAY;IACrC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC/D,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Legendable,\n Mover,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Crane3D } from './crane-3d.js'\n\n/**\n * Crane status — the operating state of a stacker crane.\n *\n * - `idle` — parked at home position\n * - `moving` — translating (horizontal) or along the mast (vertical);\n * typically combined\n * - `loading` — extracting a carrier from a rack cell\n * - `unloading` — placing a carrier into a rack cell\n * - `error` — fault / e-stop / collision warning\n */\nexport type CraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'\n\n/** Crane 컴포넌트 state */\nexport interface CraneState extends State {\n // ── 운영 상태 ──\n status?: CraneStatus\n\n /**\n * Crane 이 서비스 할 타깃 컴포넌트의 refid (또는 id).\n * 명시되면 그 컴포넌트 (또는 그 children) 의 위치를 stop point 로 사용 — heuristic\n * 으로 sibling 추정 안 함. 미명시면 sibling 안에서 추측.\n * 예: RackGrid 의 refid 를 지정하면 그 안의 cell 들 사이에서만 이동.\n */\n target?: string\n\n // ── 액추에이터 ──\n /** 마스트 따라 carriage 의 수직 위치 (mm). */\n carriageHeight?: number\n /**\n * Fork prong 의 최대 신축 가능 길이 (mm). cell 깊이 + 여유.\n * 일반적으로 pallet 깊이 (1200mm) + 여유 ≈ 1300mm 또는 그 이하 (default 600).\n */\n forkLength?: number\n /**\n * Fork 의 *현재 신축 거리* (mm). 0 = collapsed (shuttle 안 들어가있음),\n * forkLength = fully extended (cell 안 carrier 의 fork pocket 도달).\n *\n * 시뮬레이션 흐름:\n * idle: 0\n * approach → 0\n * pick / place: 0 → forkLength (extending) → 0 (retracting with carrier)\n */\n forkExtension?: number\n /**\n * Fork 의 *carrier 들어올림 높이* (mm). 0 = neutral (cell shelf 와 같은 면),\n * +값 = carrier 를 *위로* 들어올림 (fork pocket 안으로 진입 후 위로 lifting).\n */\n forkLift?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst BODY_LEGEND = {\n idle: '#888',\n moving: '#aabbcc',\n loading: '#ffaa00',\n unloading: '#ffaa00',\n error: '#c66',\n default: '#888'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n loading: '#ffaa00',\n unloading: '#ffaa00',\n error: '#ff3333',\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Loading', value: 'loading' },\n { display: 'Unloading', value: 'unloading' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'string',\n label: 'target',\n name: 'target',\n placeholder: 'refid of target (e.g., a RackGrid) — crane serves only its cells'\n },\n {\n type: 'number',\n label: 'carriage-height',\n name: 'carriageHeight',\n placeholder: 'mm — height of carriage on mast'\n },\n {\n type: 'number',\n label: 'fork-length',\n name: 'forkLength',\n placeholder: 'mm — fork prong 최대 신축 길이 (default 600)'\n },\n {\n type: 'number',\n label: 'fork-extension',\n name: 'forkExtension',\n placeholder: 'mm — 현재 신축 거리 (0 = collapsed)'\n },\n {\n type: 'number',\n label: 'fork-lift',\n name: 'forkLift',\n placeholder: 'mm — carrier 들어올림 높이 (+ 위로)'\n }\n ],\n help: 'scene/component/crane'\n}\n\n// Mixin chain: Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// Mover: pick / place / pickAndPlace / moveTo / engage primitives\n// CarrierHolder: attachPointFor() — where the carrier sits on the crane (carriage fork)\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — slot tracking +\n// TRANSFER_SLOT_KEY bookkeeping during transit\n// Legendable: status → bodyColor / lampEmissive colour mapping\n// Placeable: floor-archetype 3D positioning\n// ContainerAbstract: child management — carrier becomes a child while in transit\n//\n// Note: ContainerAbstract replaces Shape. The 2D outline is drawn manually in\n// render() below (a simple top-down rectangle), matching the old Shape output\n// without the Shape base-class overhead.\n/**\n * Crane — a stacker / retrieval picker. Picker contract instance.\n *\n * Moves carriers between rack cells (or any Placeable target). The crane\n * is *picker-side* of the Phase G/H Pickable contract — it knows how to\n * `pick(carrier)` / `place(carrier, target)` and `engage(target, kind)`\n * to align its actuators. It is *storage-agnostic* — any Rack / Cell /\n * Spot / Conveyor that implements the receive/dispatch protocol is a\n * valid target.\n *\n * Structure: a tall vertical mast that translates horizontally on a rail\n * or free floor path, with a carriage that slides up/down the mast carrying\n * a shuttle / forks.\n *\n * **Monitoring mode**: crane status is driven by external data binding\n * (`state.status`, `state.carriageHeight`). The carrier is referenced\n * via data binding — it is NOT a child of the crane in monitoring mode.\n *\n * **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, target)`\n * (or `crane.pickAndPlace(carrier, target)`). Mover handles navigation +\n * engage + reparent. During transit the carrier IS a child of the crane.\n */\n@sceneComponent('crane')\nexport default class Crane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {\n declare state: CraneState\n declare _realObject?: Crane3D\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.ceiling - h.floor\n\n /** Yaw offset: crane model is drawn with the rail axis along X (right = forward). */\n static yawOffset = 0\n\n /**\n * Phase H — the crane uses telescoping forks to pick up a carrier in a rack\n * cell. Same mechanism as a forklift fork — engages the pallet's fork pocket.\n * (Mover mixin chain is `: any` so override keyword is not usable, only the\n * getter is defined.)\n */\n get toolType(): string {\n return 'forklift-fork'\n }\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /** Stacker crane carries at most one load at a time on its forks. */\n get slots(): SlotDef[] {\n return [{ id: 'forks', maxCount: 1 }]\n }\n\n // ── CarrierHolder — attach frame (carriage fork position) ─────────────────\n\n /**\n * Return the 3D attach frame on the crane's carriage (fork tip).\n * Carriers are attached here while the crane is in transit (pick phase).\n *\n * The Crane3D exposes `getCarriageFrame()` — a sub-Object3D that\n * tracks the carriage height and sits at the fork TCP. If the 3D object\n * isn't built yet (e.g. before scene initialization), fall back to the\n * crane's own object3d centre.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getCarriageFrame?.()\n if (frame) {\n const carrierDepth = resolveCarrierDepth(carrier)\n // fork blade 위 (top surface) 에 pallet pocket 끼우듯 얹힘. y = blade top + carrier/2.\n // z = blade 중심 (forkLength 의 반쪽 위치 — pallet center 가 blade 중간에 위치).\n const platformTopY = ro?.platformTopY ?? 0\n const bladeMidZ = ro?.bladeMidZ ?? 0\n return {\n attach: frame,\n localPosition: { x: 0, y: platformTopY + carrierDepth / 2, z: bladeMidZ }\n }\n }\n const root = this._realObject?.object3d\n if (!root) return null\n return { attach: root }\n }\n\n // ── Mover overrides ───────────────────────────────────────────────────────\n\n /**\n * Domain-specific actuation between arrival and reparent.\n *\n * Simulation sequence for PICK:\n * 1. Mover.pick() navigates crane to carrier position (moveTo).\n * 2. engage('pick') → snap carriage height + status 'loading'.\n * 3. Carrier is reparented to crane (becomes child).\n *\n * For now: set status and snap carriage height. A full simulation\n * would tween the carriageHeight here (animate Crane3D).\n *\n * Status lifecycle:\n * idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle\n * The 'moving' state is not set from Mover.moveTo() because TypeScript\n * can't call super.moveTo() on an `: any`-typed mixin. External data\n * binding sets 'moving' in monitoring mode; override pick()/place() to\n * set it in full simulation environments.\n */\n async engage(\n target: Component,\n kind: 'pick' | 'place',\n _options: MoveOptions = {}\n ): Promise<void> {\n if (kind === 'pick') {\n this.setState({ status: 'loading' as CraneStatus })\n const carrierY = resolveCarrierCenterY(target)\n if (carrierY !== null) {\n this.setState({ carriageHeight: carrierY })\n }\n } else {\n this.setState({ status: 'unloading' as CraneStatus })\n }\n // In a full simulation: await carriage-motion tween here.\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Fetch a carrier from a rack cell (semantically = pick). */\n fetch(carrier: Component, options?: MoveOptions): Promise<void> {\n return this.pick(carrier, options)\n }\n\n /** Deposit a carrier into a rack cell (semantically = place). */\n deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {\n return this.place(carrier, cell, options)\n }\n\n // ── 2D rendering ─────────────────────────────────────────────────────────\n\n /**\n * 2D — top-down rectangle showing the crane's footprint along the rail.\n * The crane is much taller than wide, so the 2D mark is small.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const fillColor = (this.state.bodyColor as string) || '#888'\n ctx.save()\n ctx.fillStyle = fillColor\n ctx.beginPath()\n ctx.rect(left, top, width, height)\n ctx.fill()\n ctx.restore()\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n if (!this._simStarted) {\n this._simStarted = true\n // 초기 지연 800~2800ms 사이 random — 여러 crane 이 동기적으로 시작하지 않도록.\n const initialDelay = 800 + Math.random() * 2000\n setTimeout(() => {\n this.simulate().catch(e => console.error('[Crane] simulate', e))\n }, initialDelay)\n }\n return new Crane3D(this)\n }\n\n // ── Random animation (visual smoke test) ─────────────────────────────────\n // things-scene 의 component.animate() (frameClock 기반, pause/speed 대응) 를 사용.\n\n private _simStarted = false\n private _simRunning = false\n private _simAbort = false\n // Rail = crane 의 *로컬 X 축* (state.rotation 적용). simulate 진입 시 1회 계산해서 캐시.\n private _railOriginX = 0\n private _railOriginY = 0\n private _railCos = 1\n private _railSin = 0\n private _railMin = NaN // local-X 축 rail offset min\n private _railMax = NaN // local-X 축 rail offset max\n private _targetSide = 1 // +Z (fork +Z 로 뻗어야 하나) / -Z\n\n /** Continuous random pick → transport → place cycles. Visual smoke test. */\n async simulate(): Promise<void> {\n if (this._simRunning) return\n this._simRunning = true\n this._simAbort = false\n try {\n this._initRailRange()\n while (!this._simAbort) {\n await this._oneCycle()\n }\n } finally {\n this._simRunning = false\n }\n }\n\n /**\n * state.target bbox 를 crane 의 *로컬 X 축* 으로 projection 해서 rail range 1회 계산.\n * Rotation 적용된 crane 의 local X (= rail) 방향으로 움직이도록 cos/sin 캐시.\n */\n private _initRailRange(): void {\n this._railMin = this._railMax = NaN\n\n // 1순위: state.target refid 로 명시. 2순위: crane 의 parent 컴포넌트 (자연스러운\n // \"이 컨테이너 안에서만 움직여\" 기본값).\n const targetRefId = (this.state as any).target as string | undefined\n const root = (this as any).root\n let target = targetRefId ? root?.findById?.(targetRefId) : null\n if (!target) {\n const parent = (this as any).parent\n // parent 가 root/model-layer 같은 top-level 이면 bbox 가 의미없음 → skip\n if (parent && parent !== root && (parent.state as any)?.width > 0) {\n target = parent\n }\n }\n if (!target) return\n\n const tState = target.state ?? {}\n const tl = numOr(tState.left, 0)\n const tt = numOr(tState.top, 0)\n const tw = numOr(tState.width, 0)\n const th = numOr(tState.height, 0)\n if (tw <= 0 || th <= 0) return\n\n const W = numOr((this.state as any).width, 100)\n const H = numOr((this.state as any).height, 100)\n const rotation = numOr((this.state as any).rotation, 0)\n const cos = Math.cos(rotation)\n const sin = Math.sin(rotation)\n\n // Rail origin = crane 의 현재 canvas center. 이 점에서 local X 방향으로 ±rail offset.\n this._railOriginX = numOr((this.state as any).left, 0) + W / 2\n this._railOriginY = numOr((this.state as any).top, 0) + H / 2\n this._railCos = cos\n this._railSin = sin\n\n // Target bbox 4 corner 를 crane local frame 으로 projection.\n // local X (rail) = dx*cos + dy*sin\n // local Z (aisle) = -dx*sin + dy*cos\n const corners = [\n { x: tl, y: tt },\n { x: tl + tw, y: tt },\n { x: tl, y: tt + th },\n { x: tl + tw, y: tt + th }\n ]\n let minRail = Infinity\n let maxRail = -Infinity\n let aisleSum = 0\n for (const c of corners) {\n const dx = c.x - this._railOriginX\n const dy = c.y - this._railOriginY\n const rail = dx * cos + dy * sin\n const aisle = -dx * sin + dy * cos\n if (rail < minRail) minRail = rail\n if (rail > maxRail) maxRail = rail\n aisleSum += aisle\n }\n // Crane 자체 W 만큼 안쪽으로 좁혀 — crane center 가 [minRail+W/2, maxRail-W/2] 안에\n // 있으면 crane 전체가 target bbox 안 (local X 방향).\n this._railMin = minRail + W / 2\n this._railMax = Math.max(this._railMin, maxRail - W / 2)\n this._targetSide = aisleSum >= 0 ? +1 : -1\n }\n\n stopSimulate(): void {\n this._simAbort = true\n }\n\n private async _oneCycle(): Promise<void> {\n const W = numOr((this.state as any).width, 100)\n const H = numOr((this.state as any).height, 100)\n const D = numOr((this.state as any).depth, W * 4)\n const forkLen = numOr((this.state as any).forkLength, H * 0.6)\n\n if (!Number.isFinite(this._railMin) || !Number.isFinite(this._railMax)) {\n await new Promise(r => setTimeout(r, 800))\n return\n }\n\n const pickRail = () => this._railMin + Math.random() * Math.max(0, this._railMax - this._railMin)\n const toLeftTop = (rail: number) => ({\n left: this._railOriginX + rail * this._railCos - W / 2,\n top: this._railOriginY + rail * this._railSin - H / 2\n })\n\n const source = toLeftTop(pickRail())\n const dest = toLeftTop(pickRail())\n\n const sourceCH = Math.random() * D * 0.75\n const destCH = Math.random() * D * 0.75\n const liftH = Math.max(20, D * 0.02)\n const side = this._targetSide\n\n // Tween duration 은 base 의 70~130% 사이 random — 여러 crane 이 같은 타이밍으로 안 보이도록.\n const jitter = (base: number): number => base * (0.7 + Math.random() * 0.6)\n\n // 이동: crane local X 방향 → canvas (left, top) 둘 다 동시 변경 (rotation 적용 시 diagonal)\n await this._tween({ status: 'moving', left: source.left, top: source.top, carriageHeight: sourceCH }, jitter(1500))\n await this._tween({ status: 'loading', forkExtension: side * forkLen }, jitter(700))\n await this._tween({ forkLift: liftH }, jitter(400))\n await this._tween({ forkExtension: 0 }, jitter(700))\n await this._tween({ status: 'moving', left: dest.left, top: dest.top, carriageHeight: destCH }, jitter(1500))\n await this._tween({ status: 'unloading', forkExtension: side * forkLen }, jitter(700))\n await this._tween({ forkLift: 0 }, jitter(400))\n await this._tween({ status: 'idle', forkExtension: 0 }, jitter(700))\n\n // 사이클 사이 짧은 idle (200~1000ms) — 자연스러운 phase 분산\n await new Promise(r => setTimeout(r, 200 + Math.random() * 800))\n }\n\n /**\n * things-scene 의 `component.animate()` 로 한 구간 tween. step 콜백에서 setState 호출.\n * frameClock.simTime 기반이라 scene pause / speed change / suppressAnimations 모두 자동 대응.\n */\n private _tween(targets: Record<string, any>, duration: number): Promise<void> {\n const start: Record<string, number> = {}\n for (const k of Object.keys(targets)) {\n if (k === 'status') continue\n const v = (this.state as any)[k]\n start[k] = typeof v === 'number' && Number.isFinite(v) ? v : 0\n }\n if (typeof targets.status === 'string') {\n this.setState({ status: targets.status })\n }\n return new Promise<void>(resolve => {\n let resolved = false\n const finish = () => {\n if (resolved) return\n resolved = true\n resolve()\n }\n const controller = (this as any).animate({\n duration,\n ease: 'inout',\n delta: 'quad',\n step: (dx: number) => {\n if (this._simAbort) {\n controller.stop?.()\n return finish()\n }\n const update: Record<string, number> = {}\n for (const k of Object.keys(start)) {\n const tgt = targets[k]\n if (typeof tgt !== 'number') continue\n update[k] = start[k] + (tgt - start[k]) * dx\n }\n this.setState(update as any)\n },\n onComplete: finish\n })\n controller.start()\n })\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction resolveCarrierCenterY(c: Component): number | null {\n const pos = (c as any).state\n if (!pos) return null\n // zPos is the 3D Y center of a Placeable component in things-scene\n const zPos = numOr(pos.zPos, NaN)\n if (!Number.isNaN(zPos)) return zPos\n return null\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,12 +3,19 @@ export type { PalletMaterial } from './pallet.js';
3
3
  export { default as Box } from './box.js';
4
4
  export type { BoxMaterial } from './box.js';
5
5
  export { default as Parcel } from './parcel.js';
6
- export { default as AsrsRack } from './asrs-rack.js';
7
- export { default as RackCell } from './rack-cell.js';
8
- export type { RackCellType } from './rack-cell.js';
9
- export { RackCell3D } from './rack-cell-3d.js';
10
- export { default as AsrsCrane } from './asrs-crane.js';
11
- export type { AsrsCraneStatus } from './asrs-crane.js';
6
+ export { default as StorageRack } from './storage-rack.js';
7
+ export type { StorageRackState } from './storage-rack.js';
8
+ export { StorageRack3D } from './storage-rack-3d.js';
9
+ export { default as RackGrid } from './rack-grid.js';
10
+ export type { RackGridState } from './rack-grid.js';
11
+ export { RackGrid3D } from './rack-grid-3d.js';
12
+ export { default as MobileStorageRack } from './mobile-storage-rack.js';
13
+ export { default as StorageCell } from './storage-cell.js';
14
+ export type { StorageCellType } from './storage-cell.js';
15
+ export { StorageCell3D } from './storage-cell-3d.js';
16
+ export { default as Crane } from './crane.js';
17
+ export type { CraneState, CraneStatus } from './crane.js';
18
+ export { Crane3D } from './crane-3d.js';
12
19
  export { default as Spot } from './spot.js';
13
20
  export { Spot3D } from './spot-3d.js';
14
21
  export { default as GenericContainer } from './generic-container.js';
package/dist/index.js CHANGED
@@ -4,10 +4,15 @@
4
4
  export { default as Pallet } from './pallet.js';
5
5
  export { default as Box } from './box.js';
6
6
  export { default as Parcel } from './parcel.js';
7
- export { default as AsrsRack } from './asrs-rack.js';
8
- export { default as RackCell } from './rack-cell.js';
9
- export { RackCell3D } from './rack-cell-3d.js';
10
- export { default as AsrsCrane } from './asrs-crane.js';
7
+ export { default as StorageRack } from './storage-rack.js';
8
+ export { StorageRack3D } from './storage-rack-3d.js';
9
+ export { default as RackGrid } from './rack-grid.js';
10
+ export { RackGrid3D } from './rack-grid-3d.js';
11
+ export { default as MobileStorageRack } from './mobile-storage-rack.js';
12
+ export { default as StorageCell } from './storage-cell.js';
13
+ export { StorageCell3D } from './storage-cell-3d.js';
14
+ export { default as Crane } from './crane.js';
15
+ export { Crane3D } from './crane-3d.js';
11
16
  export { default as Spot } from './spot.js';
12
17
  export { Spot3D } from './spot-3d.js';
13
18
  export { default as GenericContainer } from './generic-container.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nexport { default as Pallet } from './pallet.js'\nexport type { PalletMaterial } from './pallet.js'\n\nexport { default as Box } from './box.js'\nexport type { BoxMaterial } from './box.js'\n\nexport { default as Parcel } from './parcel.js'\n\nexport { default as AsrsRack } from './asrs-rack.js'\nexport { default as RackCell } from './rack-cell.js'\nexport type { RackCellType } from './rack-cell.js'\nexport { RackCell3D } from './rack-cell-3d.js'\nexport { default as AsrsCrane } from './asrs-crane.js'\nexport type { AsrsCraneStatus } from './asrs-crane.js'\n\nexport { default as Spot } from './spot.js'\nexport { Spot3D } from './spot-3d.js'\n\nexport { default as GenericContainer } from './generic-container.js'\nexport type { ContainerStatus } from './generic-container.js'\nexport { GenericContainer3D } from './generic-container-3d.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,EAAE,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,CAAA;AAGzC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nexport { default as Pallet } from './pallet.js'\nexport type { PalletMaterial } from './pallet.js'\n\nexport { default as Box } from './box.js'\nexport type { BoxMaterial } from './box.js'\n\nexport { default as Parcel } from './parcel.js'\n\nexport { default as StorageRack } from './storage-rack.js'\nexport type { StorageRackState } from './storage-rack.js'\nexport { StorageRack3D } from './storage-rack-3d.js'\nexport { default as RackGrid } from './rack-grid.js'\nexport type { RackGridState } from './rack-grid.js'\nexport { RackGrid3D } from './rack-grid-3d.js'\nexport { default as MobileStorageRack } from './mobile-storage-rack.js'\nexport { default as StorageCell } from './storage-cell.js'\nexport type { StorageCellType } from './storage-cell.js'\nexport { StorageCell3D } from './storage-cell-3d.js'\nexport { default as Crane } from './crane.js'\nexport type { CraneState, CraneStatus } from './crane.js'\nexport { Crane3D } from './crane-3d.js'\n\nexport { default as Spot } from './spot.js'\nexport { Spot3D } from './spot-3d.js'\n\nexport { default as GenericContainer } from './generic-container.js'\nexport type { ContainerStatus } from './generic-container.js'\nexport { GenericContainer3D } from './generic-container-3d.js'\n"]}
@@ -0,0 +1,17 @@
1
+ import { Component } from '@hatiolab/things-scene';
2
+ import type { SlotDef } from '@hatiolab/things-scene';
3
+ declare const MobileRack_base: any;
4
+ export default class MobileRack extends MobileRack_base {
5
+ /**
6
+ * Mobile racks carry their cells as cargo. The crane that *moves* a
7
+ * MobileRack treats the rack itself as the carrier — one slot, one rack.
8
+ */
9
+ get slots(): SlotDef[];
10
+ /**
11
+ * The toolType for a fork/clamp that lifts an entire mobile rack.
12
+ * Different from the per-pallet tooling used to extract a carrier from
13
+ * a cell inside the rack.
14
+ */
15
+ get toolType(): string;
16
+ }
17
+ export { Component };
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * MobileRack — a Rack that can also move.
5
+ *
6
+ * The whole point of the storage abstraction: `Mover(Rack)` mixin gives a
7
+ * *moving storage* without changing Rack's role. Use cases — an AGV-mounted
8
+ * rack, a cart, a tote-on-conveyor, a mobile shelving unit, etc.
9
+ *
10
+ * The cells, the cell-map topology, the Pickable contract, the carrier
11
+ * attach-point — all unchanged. Only thing added: the rack can `moveTo(...)`,
12
+ * `pick(...)`, `place(...)`, `pickAndPlace(...)`.
13
+ *
14
+ * Component type: `mobile-rack`. Use `<mobile-rack levels=...>` on the
15
+ * board same as a Rack; pickers (Crane / Forklift / robot arm) interact
16
+ * with its cells via the same RackCell-side contract.
17
+ *
18
+ * Note: a moving rack is itself a *picker target* from another picker's
19
+ * point of view — the same RackCell.acceptanceRotation contract applies
20
+ * because cells, not racks, are the picker's interlocutor.
21
+ */
22
+ import { __decorate } from "tslib";
23
+ import { Component, ContainerCapacity, sceneComponent } from '@hatiolab/things-scene';
24
+ import { Mover } from '@operato/scene-base';
25
+ import StorageRack from './storage-rack.js';
26
+ // `Mover(Rack)` adds moveTo / pick / place / engage. `ContainerCapacity` is
27
+ // also added so the rack itself participates in slot bookkeeping when it
28
+ // behaves as a (mobile) carrier holder during transit. Rack already extends
29
+ // `CarrierHolder + Placeable`, so the full chain becomes:
30
+ // Mover → ContainerCapacity → Rack(=CellContainer → CarrierHolder → Placeable → ContainerAbstract)
31
+ let MobileRack = class MobileRack extends Mover(ContainerCapacity(StorageRack)) {
32
+ /**
33
+ * Mobile racks carry their cells as cargo. The crane that *moves* a
34
+ * MobileRack treats the rack itself as the carrier — one slot, one rack.
35
+ */
36
+ get slots() {
37
+ return [{ id: 'self', maxCount: 1 }];
38
+ }
39
+ /**
40
+ * The toolType for a fork/clamp that lifts an entire mobile rack.
41
+ * Different from the per-pallet tooling used to extract a carrier from
42
+ * a cell inside the rack.
43
+ */
44
+ get toolType() {
45
+ return 'rack-clamp';
46
+ }
47
+ };
48
+ MobileRack = __decorate([
49
+ sceneComponent('mobile-storage-rack')
50
+ ], MobileRack);
51
+ export default MobileRack;
52
+ // Re-export `Component` import to keep the file self-contained for IDEs that
53
+ // flatten dependency graphs. (Used implicitly by the parent mixin chain.)
54
+ export { Component };
55
+ //# sourceMappingURL=mobile-storage-rack.js.map