@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.
- package/CHANGELOG.md +14 -0
- package/dist/crane-3d.d.ts +14 -0
- package/dist/crane-3d.js +238 -0
- package/dist/crane-3d.js.map +1 -0
- package/dist/crane.d.ts +157 -0
- package/dist/crane.js +440 -0
- package/dist/crane.js.map +1 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/mobile-storage-rack.d.ts +17 -0
- package/dist/mobile-storage-rack.js +55 -0
- package/dist/mobile-storage-rack.js.map +1 -0
- package/dist/rack-column.d.ts +35 -0
- package/dist/rack-column.js +258 -0
- package/dist/rack-column.js.map +1 -0
- package/dist/rack-grid-3d.d.ts +13 -0
- package/dist/rack-grid-3d.js +94 -0
- package/dist/rack-grid-3d.js.map +1 -0
- package/dist/rack-grid-cell.d.ts +341 -0
- package/dist/rack-grid-cell.js +321 -0
- package/dist/rack-grid-cell.js.map +1 -0
- package/dist/rack-grid-helpers.d.ts +28 -0
- package/dist/rack-grid-helpers.js +71 -0
- package/dist/rack-grid-helpers.js.map +1 -0
- package/dist/rack-grid-location.d.ts +37 -0
- package/dist/rack-grid-location.js +227 -0
- package/dist/rack-grid-location.js.map +1 -0
- package/dist/rack-grid.d.ts +80 -0
- package/dist/rack-grid.js +829 -0
- package/dist/rack-grid.js.map +1 -0
- package/dist/stock.d.ts +78 -0
- package/dist/stock.js +333 -0
- package/dist/stock.js.map +1 -0
- package/dist/{rack-cell-3d.d.ts → storage-cell-3d.d.ts} +1 -1
- package/dist/{rack-cell-3d.js → storage-cell-3d.js} +3 -3
- package/dist/storage-cell-3d.js.map +1 -0
- package/dist/{rack-cell.d.ts → storage-cell.d.ts} +12 -6
- package/dist/{rack-cell.js → storage-cell.js} +9 -9
- package/dist/storage-cell.js.map +1 -0
- package/dist/{asrs-rack-3d.d.ts → storage-rack-3d.d.ts} +1 -1
- package/dist/{asrs-rack-3d.js → storage-rack-3d.js} +4 -4
- package/dist/storage-rack-3d.js.map +1 -0
- package/dist/{asrs-rack.d.ts → storage-rack.d.ts} +22 -16
- package/dist/{asrs-rack.js → storage-rack.js} +32 -26
- package/dist/storage-rack.js.map +1 -0
- package/dist/templates/index.d.ts +60 -0
- package/dist/templates/index.js +59 -17
- package/dist/templates/index.js.map +1 -1
- package/package.json +2 -2
- package/src/crane-3d.ts +273 -0
- package/src/crane.ts +538 -0
- package/src/index.ts +13 -6
- package/src/mobile-storage-rack.ts +56 -0
- package/src/rack-column.ts +340 -0
- package/src/rack-grid-3d.ts +128 -0
- package/src/rack-grid-cell.ts +404 -0
- package/src/rack-grid-helpers.ts +77 -0
- package/src/rack-grid-location.ts +286 -0
- package/src/rack-grid.ts +994 -0
- package/src/stock.ts +426 -0
- package/src/{rack-cell-3d.ts → storage-cell-3d.ts} +2 -2
- package/src/{rack-cell.ts → storage-cell.ts} +19 -13
- package/src/{asrs-rack-3d.ts → storage-rack-3d.ts} +3 -3
- package/src/{asrs-rack.ts → storage-rack.ts} +31 -25
- package/src/templates/index.ts +59 -17
- package/test/test-rack-grid-crane.ts +212 -0
- package/test/test-rack-grid.ts +77 -0
- package/test/{test-asrs-crane.ts → test-storage-rack-crane.ts} +8 -8
- package/translations/en.json +55 -7
- package/translations/ja.json +52 -4
- package/translations/ko.json +52 -4
- package/translations/ms.json +55 -7
- package/translations/zh.json +52 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/asrs-crane-3d.d.ts +0 -17
- package/dist/asrs-crane-3d.js +0 -181
- package/dist/asrs-crane-3d.js.map +0 -1
- package/dist/asrs-crane.d.ts +0 -98
- package/dist/asrs-crane.js +0 -216
- package/dist/asrs-crane.js.map +0 -1
- package/dist/asrs-rack-3d.js.map +0 -1
- package/dist/asrs-rack.js.map +0 -1
- package/dist/rack-cell-3d.js.map +0 -1
- package/dist/rack-cell.js.map +0 -1
- package/src/asrs-crane-3d.ts +0 -211
- package/src/asrs-crane.ts +0 -275
- /package/icons/{asrs-crane.png → crane.png} +0 -0
- /package/icons/{asrs-rack.png → storage-rack.png} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.0.0-beta.34](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.33...v10.0.0-beta.34) (2026-05-13)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
* **storage:** RackGrid 명명 분리 + Crane 재설계 + simulate() + i18n 정리 ([9cc2177](https://github.com/things-scene/operato-scene/commit/9cc217715e42a56e5c7fef062e6af98e03aff9f0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### :house: Code Refactoring
|
|
15
|
+
|
|
16
|
+
* **storage:** ASRS 의존 제거 + RackTable 3 계층 추상 도입 ([77f55c5](https://github.com/things-scene/operato-scene/commit/77f55c58b77485044ef4015378f50dbcedd45b61))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
## [10.0.0-beta.33](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.32...v10.0.0-beta.33) (2026-05-11)
|
|
7
21
|
|
|
8
22
|
**Note:** Version bump only for package @operato/scene-storage
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
3
|
+
export declare class Crane3D extends RealObjectGroup {
|
|
4
|
+
private _forkGroup?;
|
|
5
|
+
private _forkTopY;
|
|
6
|
+
private _bladeMidZ;
|
|
7
|
+
build(): void;
|
|
8
|
+
getCarriageFrame(): THREE.Object3D | undefined;
|
|
9
|
+
get platformTopY(): number;
|
|
10
|
+
get bladeMidZ(): number;
|
|
11
|
+
updateDimension(): void;
|
|
12
|
+
onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
|
|
13
|
+
updateAlpha(): void;
|
|
14
|
+
}
|
package/dist/crane-3d.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Crane 3D — heavy-duty pallet AS/RS stacker crane (twin-mast).
|
|
5
|
+
*
|
|
6
|
+
* Axis convention (things-scene 3D):
|
|
7
|
+
* +X = aisle 진행 방향 (Crane 좌우 이동, *옆으로 길게* 축)
|
|
8
|
+
* +Y = 수직 (Crane 키, carriage 상하 이동)
|
|
9
|
+
* +Z = aisle 폭 방향 (Rack cell 쪽 / fork 신축 방향)
|
|
10
|
+
*
|
|
11
|
+
* **스케일 룰**:
|
|
12
|
+
* - 모든 가로(X,Z) dimension 과 Y 두께 → 2D footprint (state.width, state.height) 비례
|
|
13
|
+
* - Mast Y 길이만 → state.depth 사용 (수직 키)
|
|
14
|
+
* - Y 두께는 절대 depth (크레인 키) 에 비례 안 함 → 키워도 뭉치 안 두꺼워짐
|
|
15
|
+
* - mm/cm/scene-unit 무관 — 사용자의 footprint 스케일에 자동 맞춤
|
|
16
|
+
*
|
|
17
|
+
* 부품:
|
|
18
|
+
* [Ceiling rail]
|
|
19
|
+
* [Top guide trolley] — 천장 rail 위
|
|
20
|
+
* [Top frame] — 두 mast 상부 연결
|
|
21
|
+
* [Mast L][Mast R] — twin yellow column (X 축으로 떨어져 있음)
|
|
22
|
+
* [Carriage] — 두 mast 사이 가로 frame (carriageHeight 로 상하)
|
|
23
|
+
* └── [Two forks] — carriage 에서 ±Z 신축 (forkExtension)
|
|
24
|
+
* [Base trolley] — 바닥 motor housing (X 길게)
|
|
25
|
+
* └── [Cabinet] — base 한쪽 red 컨트롤박스
|
|
26
|
+
* └── [Status lamp]
|
|
27
|
+
* [Floor rail]
|
|
28
|
+
*
|
|
29
|
+
* Actuators (state):
|
|
30
|
+
* carriageHeight, forkExtension (±), forkLift
|
|
31
|
+
*/
|
|
32
|
+
import * as THREE from 'three';
|
|
33
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
34
|
+
const MAST_COLOR = 0xff7a00; // mast — orange
|
|
35
|
+
const TROLLEY_COLOR = 0x3a4048; // base / top — dark charcoal
|
|
36
|
+
const CARRIAGE_COLOR = 0xffcc00; // carriage (shuttle) — yellow
|
|
37
|
+
const CONTROLLER_COLOR = 0xc63333; // cabinet — red
|
|
38
|
+
const FORK_COLOR = 0xffcc00; // fork — yellow (same as carriage)
|
|
39
|
+
const RAIL_COLOR = 0x1a1f24; // rail — dark steel
|
|
40
|
+
const LAMP_OFF = 0x222222;
|
|
41
|
+
export class Crane3D extends RealObjectGroup {
|
|
42
|
+
_forkGroup;
|
|
43
|
+
_forkTopY = 0;
|
|
44
|
+
_bladeMidZ = 0;
|
|
45
|
+
build() {
|
|
46
|
+
super.build();
|
|
47
|
+
this._forkGroup = undefined;
|
|
48
|
+
this._forkTopY = 0;
|
|
49
|
+
this._bladeMidZ = 0;
|
|
50
|
+
const { width, height, depth } = this.component.state;
|
|
51
|
+
const emissiveColor = this.component.state.lampEmissive || '#222222';
|
|
52
|
+
const status = this.component.state.status;
|
|
53
|
+
const lampOn = status && status !== 'idle';
|
|
54
|
+
// Actuators
|
|
55
|
+
const D = numOr(depth, Math.max(width, height) * 4);
|
|
56
|
+
const carriageRaw = numOr(this.component.state.carriageHeight, D * 0.4);
|
|
57
|
+
const carriageHeight = Math.max(0, Math.min(carriageRaw, D * 0.85));
|
|
58
|
+
const forkLength = numOr(this.component.state.forkLength, height * 0.6);
|
|
59
|
+
const forkExtensionRaw = numOr(this.component.state.forkExtension, 0);
|
|
60
|
+
const forkExtension = Math.max(-forkLength, Math.min(forkLength, forkExtensionRaw));
|
|
61
|
+
const forkLift = numOr(this.component.state.forkLift, 0);
|
|
62
|
+
// ── Axis convention (FIXED): ─────────────────────────────────────
|
|
63
|
+
// Rail = X (state.left = 2D X = 3D X). Crane 좌우 이동.
|
|
64
|
+
// Fork = Z (2D Y = 3D Z). Fork 앞뒤 신축.
|
|
65
|
+
// 사용자: "포크는 앞뒤로, 크레인 본체는 좌우로."
|
|
66
|
+
// width = rail-direction 풋프린트 (좁음)
|
|
67
|
+
// height = cross-aisle (fork 방향) 풋프린트 (깊음, fork 가 cell 까지 닿는 거리)
|
|
68
|
+
const S = Math.min(width, height);
|
|
69
|
+
const railH = S * 0.04;
|
|
70
|
+
const baseH = S * 0.18;
|
|
71
|
+
const topFrameH = S * 0.1;
|
|
72
|
+
const topGuideH = S * 0.1;
|
|
73
|
+
const carriageH = S * 0.12;
|
|
74
|
+
const mastW = width * 0.1; // mast X 단면 (along rail)
|
|
75
|
+
const mastD = height * 0.25; // mast Z 단면 (cross-rail)
|
|
76
|
+
const mastSpacing = width * 0.7; // 두 mast X 간격
|
|
77
|
+
const bladeW = S * 0.1;
|
|
78
|
+
const bladeH = S * 0.05;
|
|
79
|
+
const bladeL = forkLength;
|
|
80
|
+
const bladeSpacing = mastSpacing * 0.45;
|
|
81
|
+
const carriageW = mastSpacing - mastW * 0.2;
|
|
82
|
+
const carriageZ = height * 0.55;
|
|
83
|
+
const cabW = S * 0.4;
|
|
84
|
+
const cabH = S * 0.4;
|
|
85
|
+
const cabD = S * 0.3;
|
|
86
|
+
const baseY = -D / 2;
|
|
87
|
+
const mastH = Math.max(D - railH * 2 - baseH - topFrameH - topGuideH, S * 0.5);
|
|
88
|
+
// ── Materials ─────────────────────────────────────────────────────
|
|
89
|
+
const mastMat = new THREE.MeshStandardMaterial({ color: MAST_COLOR, metalness: 0.3, roughness: 0.5 });
|
|
90
|
+
const trolleyMat = new THREE.MeshStandardMaterial({ color: TROLLEY_COLOR, metalness: 0.6, roughness: 0.5 });
|
|
91
|
+
const carriageMat = new THREE.MeshStandardMaterial({ color: CARRIAGE_COLOR, metalness: 0.85, roughness: 0.35 });
|
|
92
|
+
const cabinetMat = new THREE.MeshStandardMaterial({ color: CONTROLLER_COLOR, metalness: 0.2, roughness: 0.6 });
|
|
93
|
+
const forkMat = new THREE.MeshStandardMaterial({ color: FORK_COLOR, metalness: 0.85, roughness: 0.3 });
|
|
94
|
+
const railMat = new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 });
|
|
95
|
+
// ── Floor rail ────────────────────────────────────────────────────
|
|
96
|
+
{
|
|
97
|
+
const geo = new THREE.BoxGeometry(width * 1.1, railH, height * 0.35);
|
|
98
|
+
const mesh = new THREE.Mesh(geo, railMat);
|
|
99
|
+
mesh.position.set(0, baseY + railH / 2, 0);
|
|
100
|
+
mesh.receiveShadow = true;
|
|
101
|
+
this.object3d.add(mesh);
|
|
102
|
+
}
|
|
103
|
+
// ── Base trolley ──────────────────────────────────────────────────
|
|
104
|
+
const baseTrolleyY = baseY + railH + baseH / 2;
|
|
105
|
+
{
|
|
106
|
+
const geo = new THREE.BoxGeometry(width * 0.95, baseH, height * 0.7);
|
|
107
|
+
const mesh = new THREE.Mesh(geo, trolleyMat);
|
|
108
|
+
mesh.position.set(0, baseTrolleyY, 0);
|
|
109
|
+
mesh.castShadow = true;
|
|
110
|
+
mesh.receiveShadow = true;
|
|
111
|
+
this.object3d.add(mesh);
|
|
112
|
+
}
|
|
113
|
+
// ── Control cabinet on one side of base ───────────────────────────
|
|
114
|
+
{
|
|
115
|
+
const geo = new THREE.BoxGeometry(cabW, cabH, cabD);
|
|
116
|
+
const cab = new THREE.Mesh(geo, cabinetMat);
|
|
117
|
+
cab.position.set(-width * 0.4 + cabW / 2, baseTrolleyY + baseH / 2 + cabH / 2, -height * 0.25 + cabD / 2);
|
|
118
|
+
cab.castShadow = true;
|
|
119
|
+
this.object3d.add(cab);
|
|
120
|
+
}
|
|
121
|
+
// ── Status lamp ───────────────────────────────────────────────────
|
|
122
|
+
{
|
|
123
|
+
const lampR = S * 0.04;
|
|
124
|
+
const lampH = S * 0.1;
|
|
125
|
+
const lampMat = new THREE.MeshStandardMaterial({
|
|
126
|
+
color: lampOn ? emissiveColor : LAMP_OFF,
|
|
127
|
+
emissive: lampOn ? emissiveColor : LAMP_OFF,
|
|
128
|
+
emissiveIntensity: lampOn ? 1.5 : 0.2,
|
|
129
|
+
metalness: 0,
|
|
130
|
+
roughness: 0.3
|
|
131
|
+
});
|
|
132
|
+
const geo = new THREE.CylinderGeometry(lampR, lampR * 0.8, lampH, 12);
|
|
133
|
+
const lamp = new THREE.Mesh(geo, lampMat);
|
|
134
|
+
lamp.position.set(width * 0.4, baseTrolleyY + baseH / 2 + lampH / 2, 0);
|
|
135
|
+
this.object3d.add(lamp);
|
|
136
|
+
}
|
|
137
|
+
// ── Twin masts ────────────────────────────────────────────────────
|
|
138
|
+
const mastY = baseTrolleyY + baseH / 2 + mastH / 2;
|
|
139
|
+
for (const xOff of [-mastSpacing / 2, +mastSpacing / 2]) {
|
|
140
|
+
const geo = new THREE.BoxGeometry(mastW, mastH, mastD);
|
|
141
|
+
const mesh = new THREE.Mesh(geo, mastMat);
|
|
142
|
+
mesh.position.set(xOff, mastY, 0);
|
|
143
|
+
mesh.castShadow = true;
|
|
144
|
+
mesh.receiveShadow = true;
|
|
145
|
+
this.object3d.add(mesh);
|
|
146
|
+
}
|
|
147
|
+
// ── Carriage (between masts) ──────────────────────────────────────
|
|
148
|
+
const carriageY = baseTrolleyY + baseH / 2 + carriageHeight + carriageH / 2;
|
|
149
|
+
{
|
|
150
|
+
const geo = new THREE.BoxGeometry(carriageW, carriageH, carriageZ);
|
|
151
|
+
const mesh = new THREE.Mesh(geo, carriageMat);
|
|
152
|
+
mesh.position.set(0, carriageY, 0);
|
|
153
|
+
mesh.castShadow = true;
|
|
154
|
+
mesh.receiveShadow = true;
|
|
155
|
+
this.object3d.add(mesh);
|
|
156
|
+
}
|
|
157
|
+
// ── Two-prong forks (telescopic ±Z) ───────────────────────────────
|
|
158
|
+
// forkExtension 부호: + = +Z, - = -Z. forkExtension=0 일 때 blade 중심이 carriage 안쪽
|
|
159
|
+
// (= retracted, blade tip 이 carriage 앞면 근처).
|
|
160
|
+
const forkSign = forkExtension >= 0 ? 1 : -1;
|
|
161
|
+
const forkBaseZ = forkSign * (carriageZ / 2 - bladeL / 2 + Math.abs(forkExtension));
|
|
162
|
+
const forkY = carriageY + forkLift - bladeH / 2;
|
|
163
|
+
{
|
|
164
|
+
const group = new THREE.Group();
|
|
165
|
+
group.position.set(0, forkY, forkBaseZ);
|
|
166
|
+
// forkSign 으로 blade tip 방향 설정
|
|
167
|
+
group.rotation.y = forkSign < 0 ? Math.PI : 0;
|
|
168
|
+
for (const xOff of [-bladeSpacing / 2, +bladeSpacing / 2]) {
|
|
169
|
+
const geo = new THREE.BoxGeometry(bladeW, bladeH, bladeL);
|
|
170
|
+
const mesh = new THREE.Mesh(geo, forkMat);
|
|
171
|
+
mesh.position.set(xOff, 0, 0);
|
|
172
|
+
mesh.castShadow = true;
|
|
173
|
+
mesh.receiveShadow = true;
|
|
174
|
+
group.add(mesh);
|
|
175
|
+
}
|
|
176
|
+
this.object3d.add(group);
|
|
177
|
+
this._forkGroup = group;
|
|
178
|
+
this._forkTopY = bladeH / 2;
|
|
179
|
+
this._bladeMidZ = 0;
|
|
180
|
+
}
|
|
181
|
+
// ── Top frame (connects mast tops) ────────────────────────────────
|
|
182
|
+
const topFrameY = mastY + mastH / 2 + topFrameH / 2;
|
|
183
|
+
{
|
|
184
|
+
const geo = new THREE.BoxGeometry(mastSpacing + mastW, topFrameH, height * 0.35);
|
|
185
|
+
const mesh = new THREE.Mesh(geo, trolleyMat);
|
|
186
|
+
mesh.position.set(0, topFrameY, 0);
|
|
187
|
+
mesh.castShadow = true;
|
|
188
|
+
this.object3d.add(mesh);
|
|
189
|
+
}
|
|
190
|
+
// ── Top guide trolley ─────────────────────────────────────────────
|
|
191
|
+
const topGuideY = topFrameY + topFrameH / 2 + topGuideH / 2;
|
|
192
|
+
{
|
|
193
|
+
const geo = new THREE.BoxGeometry(mastSpacing + mastW * 2, topGuideH, height * 0.3);
|
|
194
|
+
const mesh = new THREE.Mesh(geo, trolleyMat);
|
|
195
|
+
mesh.position.set(0, topGuideY, 0);
|
|
196
|
+
mesh.castShadow = true;
|
|
197
|
+
this.object3d.add(mesh);
|
|
198
|
+
}
|
|
199
|
+
// ── Ceiling rail ──────────────────────────────────────────────────
|
|
200
|
+
{
|
|
201
|
+
const geo = new THREE.BoxGeometry(width * 1.1, railH, height * 0.3);
|
|
202
|
+
const mesh = new THREE.Mesh(geo, railMat);
|
|
203
|
+
mesh.position.set(0, topGuideY + topGuideH / 2 + railH / 2, 0);
|
|
204
|
+
this.object3d.add(mesh);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
getCarriageFrame() {
|
|
208
|
+
return this._forkGroup ?? this.object3d;
|
|
209
|
+
}
|
|
210
|
+
get platformTopY() {
|
|
211
|
+
return this._forkTopY;
|
|
212
|
+
}
|
|
213
|
+
get bladeMidZ() {
|
|
214
|
+
return this._bladeMidZ;
|
|
215
|
+
}
|
|
216
|
+
updateDimension() { }
|
|
217
|
+
onchange(after, before) {
|
|
218
|
+
if ('width' in after ||
|
|
219
|
+
'height' in after ||
|
|
220
|
+
'depth' in after ||
|
|
221
|
+
'carriageHeight' in after ||
|
|
222
|
+
'forkLength' in after ||
|
|
223
|
+
'forkExtension' in after ||
|
|
224
|
+
'forkLift' in after ||
|
|
225
|
+
'status' in after ||
|
|
226
|
+
'bodyColor' in after ||
|
|
227
|
+
'lampEmissive' in after) {
|
|
228
|
+
this.update();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
super.onchange(after, before);
|
|
232
|
+
}
|
|
233
|
+
updateAlpha() { }
|
|
234
|
+
}
|
|
235
|
+
function numOr(v, dflt) {
|
|
236
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : dflt;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=crane-3d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crane-3d.js","sourceRoot":"","sources":["../src/crane-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Crane 3D — heavy-duty pallet AS/RS stacker crane (twin-mast).\n *\n * Axis convention (things-scene 3D):\n * +X = aisle 진행 방향 (Crane 좌우 이동, *옆으로 길게* 축)\n * +Y = 수직 (Crane 키, carriage 상하 이동)\n * +Z = aisle 폭 방향 (Rack cell 쪽 / fork 신축 방향)\n *\n * **스케일 룰**:\n * - 모든 가로(X,Z) dimension 과 Y 두께 → 2D footprint (state.width, state.height) 비례\n * - Mast Y 길이만 → state.depth 사용 (수직 키)\n * - Y 두께는 절대 depth (크레인 키) 에 비례 안 함 → 키워도 뭉치 안 두꺼워짐\n * - mm/cm/scene-unit 무관 — 사용자의 footprint 스케일에 자동 맞춤\n *\n * 부품:\n * [Ceiling rail]\n * [Top guide trolley] — 천장 rail 위\n * [Top frame] — 두 mast 상부 연결\n * [Mast L][Mast R] — twin yellow column (X 축으로 떨어져 있음)\n * [Carriage] — 두 mast 사이 가로 frame (carriageHeight 로 상하)\n * └── [Two forks] — carriage 에서 ±Z 신축 (forkExtension)\n * [Base trolley] — 바닥 motor housing (X 길게)\n * └── [Cabinet] — base 한쪽 red 컨트롤박스\n * └── [Status lamp]\n * [Floor rail]\n *\n * Actuators (state):\n * carriageHeight, forkExtension (±), forkLift\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst MAST_COLOR = 0xff7a00 // mast — orange\nconst TROLLEY_COLOR = 0x3a4048 // base / top — dark charcoal\nconst CARRIAGE_COLOR = 0xffcc00 // carriage (shuttle) — yellow\nconst CONTROLLER_COLOR = 0xc63333 // cabinet — red\nconst FORK_COLOR = 0xffcc00 // fork — yellow (same as carriage)\nconst RAIL_COLOR = 0x1a1f24 // rail — dark steel\nconst LAMP_OFF = 0x222222\n\nexport class Crane3D extends RealObjectGroup {\n private _forkGroup?: THREE.Group\n private _forkTopY: number = 0\n private _bladeMidZ: number = 0\n\n build() {\n super.build()\n\n this._forkGroup = undefined\n this._forkTopY = 0\n this._bladeMidZ = 0\n\n const { width, height, depth } = this.component.state\n const emissiveColor = (this.component.state.lampEmissive as string) || '#222222'\n const status = this.component.state.status\n const lampOn = status && status !== 'idle'\n\n // Actuators\n const D = numOr(depth, Math.max(width, height) * 4)\n const carriageRaw = numOr((this.component.state as any).carriageHeight, D * 0.4)\n const carriageHeight = Math.max(0, Math.min(carriageRaw, D * 0.85))\n\n const forkLength = numOr((this.component.state as any).forkLength, height * 0.6)\n const forkExtensionRaw = numOr((this.component.state as any).forkExtension, 0)\n const forkExtension = Math.max(-forkLength, Math.min(forkLength, forkExtensionRaw))\n const forkLift = numOr((this.component.state as any).forkLift, 0)\n\n // ── Axis convention (FIXED): ─────────────────────────────────────\n // Rail = X (state.left = 2D X = 3D X). Crane 좌우 이동.\n // Fork = Z (2D Y = 3D Z). Fork 앞뒤 신축.\n // 사용자: \"포크는 앞뒤로, 크레인 본체는 좌우로.\"\n // width = rail-direction 풋프린트 (좁음)\n // height = cross-aisle (fork 방향) 풋프린트 (깊음, fork 가 cell 까지 닿는 거리)\n const S = Math.min(width, height)\n const railH = S * 0.04\n const baseH = S * 0.18\n const topFrameH = S * 0.1\n const topGuideH = S * 0.1\n const carriageH = S * 0.12\n const mastW = width * 0.1 // mast X 단면 (along rail)\n const mastD = height * 0.25 // mast Z 단면 (cross-rail)\n const mastSpacing = width * 0.7 // 두 mast X 간격\n const bladeW = S * 0.1\n const bladeH = S * 0.05\n const bladeL = forkLength\n const bladeSpacing = mastSpacing * 0.45\n const carriageW = mastSpacing - mastW * 0.2\n const carriageZ = height * 0.55\n const cabW = S * 0.4\n const cabH = S * 0.4\n const cabD = S * 0.3\n\n const baseY = -D / 2\n const mastH = Math.max(D - railH * 2 - baseH - topFrameH - topGuideH, S * 0.5)\n\n // ── Materials ─────────────────────────────────────────────────────\n const mastMat = new THREE.MeshStandardMaterial({ color: MAST_COLOR, metalness: 0.3, roughness: 0.5 })\n const trolleyMat = new THREE.MeshStandardMaterial({ color: TROLLEY_COLOR, metalness: 0.6, roughness: 0.5 })\n const carriageMat = new THREE.MeshStandardMaterial({ color: CARRIAGE_COLOR, metalness: 0.85, roughness: 0.35 })\n const cabinetMat = new THREE.MeshStandardMaterial({ color: CONTROLLER_COLOR, metalness: 0.2, roughness: 0.6 })\n const forkMat = new THREE.MeshStandardMaterial({ color: FORK_COLOR, metalness: 0.85, roughness: 0.3 })\n const railMat = new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 })\n\n // ── Floor rail ────────────────────────────────────────────────────\n {\n const geo = new THREE.BoxGeometry(width * 1.1, railH, height * 0.35)\n const mesh = new THREE.Mesh(geo, railMat)\n mesh.position.set(0, baseY + railH / 2, 0)\n mesh.receiveShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Base trolley ──────────────────────────────────────────────────\n const baseTrolleyY = baseY + railH + baseH / 2\n {\n const geo = new THREE.BoxGeometry(width * 0.95, baseH, height * 0.7)\n const mesh = new THREE.Mesh(geo, trolleyMat)\n mesh.position.set(0, baseTrolleyY, 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Control cabinet on one side of base ───────────────────────────\n {\n const geo = new THREE.BoxGeometry(cabW, cabH, cabD)\n const cab = new THREE.Mesh(geo, cabinetMat)\n cab.position.set(\n -width * 0.4 + cabW / 2,\n baseTrolleyY + baseH / 2 + cabH / 2,\n -height * 0.25 + cabD / 2\n )\n cab.castShadow = true\n this.object3d.add(cab)\n }\n\n // ── Status lamp ───────────────────────────────────────────────────\n {\n const lampR = S * 0.04\n const lampH = S * 0.1\n const lampMat = new THREE.MeshStandardMaterial({\n color: lampOn ? emissiveColor : LAMP_OFF,\n emissive: lampOn ? emissiveColor : LAMP_OFF,\n emissiveIntensity: lampOn ? 1.5 : 0.2,\n metalness: 0,\n roughness: 0.3\n })\n const geo = new THREE.CylinderGeometry(lampR, lampR * 0.8, lampH, 12)\n const lamp = new THREE.Mesh(geo, lampMat)\n lamp.position.set(width * 0.4, baseTrolleyY + baseH / 2 + lampH / 2, 0)\n this.object3d.add(lamp)\n }\n\n // ── Twin masts ────────────────────────────────────────────────────\n const mastY = baseTrolleyY + baseH / 2 + mastH / 2\n for (const xOff of [-mastSpacing / 2, +mastSpacing / 2]) {\n const geo = new THREE.BoxGeometry(mastW, mastH, mastD)\n const mesh = new THREE.Mesh(geo, mastMat)\n mesh.position.set(xOff, mastY, 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Carriage (between masts) ──────────────────────────────────────\n const carriageY = baseTrolleyY + baseH / 2 + carriageHeight + carriageH / 2\n {\n const geo = new THREE.BoxGeometry(carriageW, carriageH, carriageZ)\n const mesh = new THREE.Mesh(geo, carriageMat)\n mesh.position.set(0, carriageY, 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Two-prong forks (telescopic ±Z) ───────────────────────────────\n // forkExtension 부호: + = +Z, - = -Z. forkExtension=0 일 때 blade 중심이 carriage 안쪽\n // (= retracted, blade tip 이 carriage 앞면 근처).\n const forkSign = forkExtension >= 0 ? 1 : -1\n const forkBaseZ = forkSign * (carriageZ / 2 - bladeL / 2 + Math.abs(forkExtension))\n const forkY = carriageY + forkLift - bladeH / 2\n {\n const group = new THREE.Group()\n group.position.set(0, forkY, forkBaseZ)\n // forkSign 으로 blade tip 방향 설정\n group.rotation.y = forkSign < 0 ? Math.PI : 0\n\n for (const xOff of [-bladeSpacing / 2, +bladeSpacing / 2]) {\n const geo = new THREE.BoxGeometry(bladeW, bladeH, bladeL)\n const mesh = new THREE.Mesh(geo, forkMat)\n mesh.position.set(xOff, 0, 0)\n mesh.castShadow = true\n mesh.receiveShadow = true\n group.add(mesh)\n }\n\n this.object3d.add(group)\n this._forkGroup = group\n this._forkTopY = bladeH / 2\n this._bladeMidZ = 0\n }\n\n // ── Top frame (connects mast tops) ────────────────────────────────\n const topFrameY = mastY + mastH / 2 + topFrameH / 2\n {\n const geo = new THREE.BoxGeometry(mastSpacing + mastW, topFrameH, height * 0.35)\n const mesh = new THREE.Mesh(geo, trolleyMat)\n mesh.position.set(0, topFrameY, 0)\n mesh.castShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Top guide trolley ─────────────────────────────────────────────\n const topGuideY = topFrameY + topFrameH / 2 + topGuideH / 2\n {\n const geo = new THREE.BoxGeometry(mastSpacing + mastW * 2, topGuideH, height * 0.3)\n const mesh = new THREE.Mesh(geo, trolleyMat)\n mesh.position.set(0, topGuideY, 0)\n mesh.castShadow = true\n this.object3d.add(mesh)\n }\n\n // ── Ceiling rail ──────────────────────────────────────────────────\n {\n const geo = new THREE.BoxGeometry(width * 1.1, railH, height * 0.3)\n const mesh = new THREE.Mesh(geo, railMat)\n mesh.position.set(0, topGuideY + topGuideH / 2 + railH / 2, 0)\n this.object3d.add(mesh)\n }\n }\n\n getCarriageFrame(): THREE.Object3D | undefined {\n return this._forkGroup ?? this.object3d\n }\n\n get platformTopY(): number {\n return this._forkTopY\n }\n\n get bladeMidZ(): number {\n return this._bladeMidZ\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'width' in after ||\n 'height' in after ||\n 'depth' in after ||\n 'carriageHeight' in after ||\n 'forkLength' in after ||\n 'forkExtension' in after ||\n 'forkLift' in after ||\n 'status' in after ||\n 'bodyColor' in after ||\n 'lampEmissive' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
|
package/dist/crane.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
+
import type { SlotDef, State, Material3D } from '@hatiolab/things-scene';
|
|
3
|
+
import { type AttachFrame, type Alignment, type Heights, type LegendBinding, type MoveOptions, type PlacementArchetype } from '@operato/scene-base';
|
|
4
|
+
import { Crane3D } from './crane-3d.js';
|
|
5
|
+
/**
|
|
6
|
+
* Crane status — the operating state of a stacker crane.
|
|
7
|
+
*
|
|
8
|
+
* - `idle` — parked at home position
|
|
9
|
+
* - `moving` — translating (horizontal) or along the mast (vertical);
|
|
10
|
+
* typically combined
|
|
11
|
+
* - `loading` — extracting a carrier from a rack cell
|
|
12
|
+
* - `unloading` — placing a carrier into a rack cell
|
|
13
|
+
* - `error` — fault / e-stop / collision warning
|
|
14
|
+
*/
|
|
15
|
+
export type CraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error';
|
|
16
|
+
/** Crane 컴포넌트 state */
|
|
17
|
+
export interface CraneState extends State {
|
|
18
|
+
status?: CraneStatus;
|
|
19
|
+
/**
|
|
20
|
+
* Crane 이 서비스 할 타깃 컴포넌트의 refid (또는 id).
|
|
21
|
+
* 명시되면 그 컴포넌트 (또는 그 children) 의 위치를 stop point 로 사용 — heuristic
|
|
22
|
+
* 으로 sibling 추정 안 함. 미명시면 sibling 안에서 추측.
|
|
23
|
+
* 예: RackGrid 의 refid 를 지정하면 그 안의 cell 들 사이에서만 이동.
|
|
24
|
+
*/
|
|
25
|
+
target?: string;
|
|
26
|
+
/** 마스트 따라 carriage 의 수직 위치 (mm). */
|
|
27
|
+
carriageHeight?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Fork prong 의 최대 신축 가능 길이 (mm). cell 깊이 + 여유.
|
|
30
|
+
* 일반적으로 pallet 깊이 (1200mm) + 여유 ≈ 1300mm 또는 그 이하 (default 600).
|
|
31
|
+
*/
|
|
32
|
+
forkLength?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Fork 의 *현재 신축 거리* (mm). 0 = collapsed (shuttle 안 들어가있음),
|
|
35
|
+
* forkLength = fully extended (cell 안 carrier 의 fork pocket 도달).
|
|
36
|
+
*
|
|
37
|
+
* 시뮬레이션 흐름:
|
|
38
|
+
* idle: 0
|
|
39
|
+
* approach → 0
|
|
40
|
+
* pick / place: 0 → forkLength (extending) → 0 (retracting with carrier)
|
|
41
|
+
*/
|
|
42
|
+
forkExtension?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Fork 의 *carrier 들어올림 높이* (mm). 0 = neutral (cell shelf 와 같은 면),
|
|
45
|
+
* +값 = carrier 를 *위로* 들어올림 (fork pocket 안으로 진입 후 위로 lifting).
|
|
46
|
+
*/
|
|
47
|
+
forkLift?: number;
|
|
48
|
+
material3d?: Material3D;
|
|
49
|
+
}
|
|
50
|
+
declare const Crane_base: any;
|
|
51
|
+
/**
|
|
52
|
+
* Crane — a stacker / retrieval picker. Picker contract instance.
|
|
53
|
+
*
|
|
54
|
+
* Moves carriers between rack cells (or any Placeable target). The crane
|
|
55
|
+
* is *picker-side* of the Phase G/H Pickable contract — it knows how to
|
|
56
|
+
* `pick(carrier)` / `place(carrier, target)` and `engage(target, kind)`
|
|
57
|
+
* to align its actuators. It is *storage-agnostic* — any Rack / Cell /
|
|
58
|
+
* Spot / Conveyor that implements the receive/dispatch protocol is a
|
|
59
|
+
* valid target.
|
|
60
|
+
*
|
|
61
|
+
* Structure: a tall vertical mast that translates horizontally on a rail
|
|
62
|
+
* or free floor path, with a carriage that slides up/down the mast carrying
|
|
63
|
+
* a shuttle / forks.
|
|
64
|
+
*
|
|
65
|
+
* **Monitoring mode**: crane status is driven by external data binding
|
|
66
|
+
* (`state.status`, `state.carriageHeight`). The carrier is referenced
|
|
67
|
+
* via data binding — it is NOT a child of the crane in monitoring mode.
|
|
68
|
+
*
|
|
69
|
+
* **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, target)`
|
|
70
|
+
* (or `crane.pickAndPlace(carrier, target)`). Mover handles navigation +
|
|
71
|
+
* engage + reparent. During transit the carrier IS a child of the crane.
|
|
72
|
+
*/
|
|
73
|
+
export default class Crane extends Crane_base {
|
|
74
|
+
state: CraneState;
|
|
75
|
+
_realObject?: Crane3D;
|
|
76
|
+
static legends: Record<string, LegendBinding>;
|
|
77
|
+
static placement: PlacementArchetype;
|
|
78
|
+
static align: Alignment;
|
|
79
|
+
static defaultDepth: (h: Heights) => number;
|
|
80
|
+
/** Yaw offset: crane model is drawn with the rail axis along X (right = forward). */
|
|
81
|
+
static yawOffset: number;
|
|
82
|
+
/**
|
|
83
|
+
* Phase H — the crane uses telescoping forks to pick up a carrier in a rack
|
|
84
|
+
* cell. Same mechanism as a forklift fork — engages the pallet's fork pocket.
|
|
85
|
+
* (Mover mixin chain is `: any` so override keyword is not usable, only the
|
|
86
|
+
* getter is defined.)
|
|
87
|
+
*/
|
|
88
|
+
get toolType(): string;
|
|
89
|
+
get nature(): ComponentNature;
|
|
90
|
+
get anchors(): never[];
|
|
91
|
+
/** Stacker crane carries at most one load at a time on its forks. */
|
|
92
|
+
get slots(): SlotDef[];
|
|
93
|
+
/**
|
|
94
|
+
* Return the 3D attach frame on the crane's carriage (fork tip).
|
|
95
|
+
* Carriers are attached here while the crane is in transit (pick phase).
|
|
96
|
+
*
|
|
97
|
+
* The Crane3D exposes `getCarriageFrame()` — a sub-Object3D that
|
|
98
|
+
* tracks the carriage height and sits at the fork TCP. If the 3D object
|
|
99
|
+
* isn't built yet (e.g. before scene initialization), fall back to the
|
|
100
|
+
* crane's own object3d centre.
|
|
101
|
+
*/
|
|
102
|
+
attachPointFor(carrier: Component): AttachFrame | null;
|
|
103
|
+
/**
|
|
104
|
+
* Domain-specific actuation between arrival and reparent.
|
|
105
|
+
*
|
|
106
|
+
* Simulation sequence for PICK:
|
|
107
|
+
* 1. Mover.pick() navigates crane to carrier position (moveTo).
|
|
108
|
+
* 2. engage('pick') → snap carriage height + status 'loading'.
|
|
109
|
+
* 3. Carrier is reparented to crane (becomes child).
|
|
110
|
+
*
|
|
111
|
+
* For now: set status and snap carriage height. A full simulation
|
|
112
|
+
* would tween the carriageHeight here (animate Crane3D).
|
|
113
|
+
*
|
|
114
|
+
* Status lifecycle:
|
|
115
|
+
* idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle
|
|
116
|
+
* The 'moving' state is not set from Mover.moveTo() because TypeScript
|
|
117
|
+
* can't call super.moveTo() on an `: any`-typed mixin. External data
|
|
118
|
+
* binding sets 'moving' in monitoring mode; override pick()/place() to
|
|
119
|
+
* set it in full simulation environments.
|
|
120
|
+
*/
|
|
121
|
+
engage(target: Component, kind: 'pick' | 'place', _options?: MoveOptions): Promise<void>;
|
|
122
|
+
/** Fetch a carrier from a rack cell (semantically = pick). */
|
|
123
|
+
fetch(carrier: Component, options?: MoveOptions): Promise<void>;
|
|
124
|
+
/** Deposit a carrier into a rack cell (semantically = place). */
|
|
125
|
+
deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* 2D — top-down rectangle showing the crane's footprint along the rail.
|
|
128
|
+
* The crane is much taller than wide, so the 2D mark is small.
|
|
129
|
+
*/
|
|
130
|
+
render(ctx: CanvasRenderingContext2D): void;
|
|
131
|
+
buildRealObject(): RealObject | undefined;
|
|
132
|
+
private _simStarted;
|
|
133
|
+
private _simRunning;
|
|
134
|
+
private _simAbort;
|
|
135
|
+
private _railOriginX;
|
|
136
|
+
private _railOriginY;
|
|
137
|
+
private _railCos;
|
|
138
|
+
private _railSin;
|
|
139
|
+
private _railMin;
|
|
140
|
+
private _railMax;
|
|
141
|
+
private _targetSide;
|
|
142
|
+
/** Continuous random pick → transport → place cycles. Visual smoke test. */
|
|
143
|
+
simulate(): Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* state.target bbox 를 crane 의 *로컬 X 축* 으로 projection 해서 rail range 1회 계산.
|
|
146
|
+
* Rotation 적용된 crane 의 local X (= rail) 방향으로 움직이도록 cos/sin 캐시.
|
|
147
|
+
*/
|
|
148
|
+
private _initRailRange;
|
|
149
|
+
stopSimulate(): void;
|
|
150
|
+
private _oneCycle;
|
|
151
|
+
/**
|
|
152
|
+
* things-scene 의 `component.animate()` 로 한 구간 tween. step 콜백에서 setState 호출.
|
|
153
|
+
* frameClock.simTime 기반이라 scene pause / speed change / suppressAnimations 모두 자동 대응.
|
|
154
|
+
*/
|
|
155
|
+
private _tween;
|
|
156
|
+
}
|
|
157
|
+
export {};
|