@operato/scene-transport 10.0.0-beta.22

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 (55) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +55 -0
  3. package/dist/agv-3d.d.ts +7 -0
  4. package/dist/agv-3d.js +233 -0
  5. package/dist/agv-3d.js.map +1 -0
  6. package/dist/agv.d.ts +57 -0
  7. package/dist/agv.js +171 -0
  8. package/dist/agv.js.map +1 -0
  9. package/dist/forklift-3d.d.ts +15 -0
  10. package/dist/forklift-3d.js +518 -0
  11. package/dist/forklift-3d.js.map +1 -0
  12. package/dist/forklift.d.ts +58 -0
  13. package/dist/forklift.js +163 -0
  14. package/dist/forklift.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.js +8 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/templates/index.d.ts +47 -0
  19. package/dist/templates/index.js +73 -0
  20. package/dist/templates/index.js.map +1 -0
  21. package/dist/tugger-3d.d.ts +7 -0
  22. package/dist/tugger-3d.js +140 -0
  23. package/dist/tugger-3d.js.map +1 -0
  24. package/dist/tugger.d.ts +40 -0
  25. package/dist/tugger.js +135 -0
  26. package/dist/tugger.js.map +1 -0
  27. package/dist/worker-3d.d.ts +7 -0
  28. package/dist/worker-3d.js +199 -0
  29. package/dist/worker-3d.js.map +1 -0
  30. package/dist/worker.d.ts +44 -0
  31. package/dist/worker.js +130 -0
  32. package/dist/worker.js.map +1 -0
  33. package/icons/agv.png +0 -0
  34. package/icons/forklift.png +0 -0
  35. package/icons/tugger.png +0 -0
  36. package/icons/worker.png +0 -0
  37. package/package.json +44 -0
  38. package/src/agv-3d.ts +283 -0
  39. package/src/agv.ts +207 -0
  40. package/src/forklift-3d.ts +591 -0
  41. package/src/forklift.ts +200 -0
  42. package/src/index.ts +14 -0
  43. package/src/templates/index.ts +73 -0
  44. package/src/tugger-3d.ts +169 -0
  45. package/src/tugger.ts +169 -0
  46. package/src/worker-3d.ts +232 -0
  47. package/src/worker.ts +164 -0
  48. package/things-scene.config.js +5 -0
  49. package/translations/en.json +9 -0
  50. package/translations/ja.json +9 -0
  51. package/translations/ko.json +9 -0
  52. package/translations/ms.json +9 -0
  53. package/translations/zh.json +9 -0
  54. package/tsconfig.json +23 -0
  55. package/tsconfig.tsbuildinfo +1 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,26 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [10.0.0-beta.22](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.21...v10.0.0-beta.22) (2026-04-30)
7
+
8
+
9
+ ### :rocket: New Features
10
+
11
+ * **scene-facility,base:** add Floor + placeholder icons + base config fix ([830d57e](https://github.com/things-scene/operato-scene/commit/830d57ee8ecd7a31e7289f7cd6fe52027aa0380a))
12
+ * **scene-transport,storage:** rebuild Forklift3D from scratch + align Pallet decks ([83aa335](https://github.com/things-scene/operato-scene/commit/83aa33531ce24615dc15e295d28872dec85e29de))
13
+ * **scene-transport:** add Agv (payload) + Tugger (towing) — Phase A4 ([094fd1c](https://github.com/things-scene/operato-scene/commit/094fd1caea1bfb4e6a8c581147972a491436c99b))
14
+ * **scene-transport:** Forklift3D — sculpted side profile + aesthetic detail ([3603a62](https://github.com/things-scene/operato-scene/commit/3603a62f89763469859d96fdf6b4665ea96da7f1))
15
+ * **scene-transport:** polish 3D models + add 2D top-view silhouettes ([9315c01](https://github.com/things-scene/operato-scene/commit/9315c01574d77ec35dc20731151b5eb8372fd545))
16
+ * **scene:** @operato/scene-transport — Forklift + Worker (Phase A3) ([5ca4953](https://github.com/things-scene/operato-scene/commit/5ca4953b7c7cd96e608d174aa45361a92de69e9e))
17
+
18
+
19
+ ### :bug: Bug Fix
20
+
21
+ * **scene-base,storage,transport:** rescale heights to scene units, clamp mast travel, operation cargo defaults to floor ([bd545d2](https://github.com/things-scene/operato-scene/commit/bd545d2ba9f41d97acc8d75b249e16aff4720258))
22
+
23
+
24
+ ### :house: Code Refactoring
25
+
26
+ * **templates:** split warehouse → facility/conveyance/storage/transport + i18n ([4d0b726](https://github.com/things-scene/operato-scene/commit/4d0b7260fb515c904d6c1ebd8c37ef3193244ab8))
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @operato/scene-transport
2
+
3
+ **Transport** domain components for things-scene — the moving parts of smart-factory and logistics floors.
4
+
5
+ ## Domain context
6
+
7
+ This package collects the *vehicles and operators* of a logistics line — the components that *move* across the floor between fixed conveyance and storage assets. Each component declares its placement archetype via `@operato/scene-base` (typically `floor` + `bottom`) so vertical positioning is automatic against the scene's `StandardHeights`.
8
+
9
+ Initial set: Forklift, Worker. Planned: AGV (two types — towing / payload), AMR.
10
+
11
+ ## First package to use scene-base from scratch
12
+
13
+ Conveyance was migrated retroactively. Transport is the first domain package to use `@operato/scene-base` *as designed* — components declare `static legends` and `static placement` from day one, no FILL_STYLES inline arrays to remove.
14
+
15
+ <!-- AUTOGEN_BEGIN: do not edit between markers (run scripts/regenerate-readmes.mjs to update) -->
16
+
17
+ ## Components
18
+
19
+ - `Forklift`
20
+ - `Worker`
21
+ - `Agv`
22
+ - `Tugger`
23
+
24
+ ## Templates (things-scene catalog)
25
+
26
+ | type | group | description |
27
+ |---|---|---|
28
+ | `forklift` | warehouse | forklift truck |
29
+ | `worker` | warehouse | human worker |
30
+ | `agv` | warehouse | payload AGV (unit-load, Kiva-style) |
31
+ | `tugger` | warehouse | tugger AGV (towing tractor) |
32
+
33
+ ## Install
34
+
35
+ ```bash
36
+ yarn add @operato/scene-transport
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```ts
42
+ import { Forklift, Worker, Agv, Tugger } from '@operato/scene-transport'
43
+ ```
44
+
45
+ ## Build
46
+
47
+ ```bash
48
+ yarn build
49
+ ```
50
+
51
+ Output: ESM module at `dist/index.js` (single bundle, no UMD/IE legacy).
52
+
53
+ _Version: 10.0.0-beta.1_
54
+
55
+ <!-- AUTOGEN_END -->
@@ -0,0 +1,7 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Agv3D extends RealObjectGroup {
3
+ build(): void;
4
+ updateDimension(): void;
5
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
6
+ updateAlpha(): void;
7
+ }
package/dist/agv-3d.js ADDED
@@ -0,0 +1,233 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Agv 3D — payload (Kiva-style) automated guided vehicle.
5
+ *
6
+ * LO-POLY but with **rounded volume** — the Kiva AGV's signature is a
7
+ * pill-shaped, low-slung chassis with rounded corners, not a flat box.
8
+ *
9
+ * Structure:
10
+ * - rounded-box chassis (volumetric, with bevels — the visual identity)
11
+ * - top deck with raised lift pad at center (Kiva-style under-shelf lift)
12
+ * - LED strip running around the perimeter
13
+ * - safety bumpers (front + rear, hi-vis with black corners)
14
+ * - LiDAR sensor: cylindrical body + transparent dome top
15
+ * - 4 wheel covers (fender bumps integrated into chassis sides)
16
+ * - charging contacts at rear
17
+ * - corner indicator lamps on deck
18
+ */
19
+ import * as THREE from 'three';
20
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
21
+ import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
22
+ import { RealObjectGroup } from '@hatiolab/things-scene';
23
+ const CHASSIS_COLOR_DARK = 0x444444;
24
+ const DECK_COLOR = 0x666677;
25
+ const LIFT_PAD_COLOR = 0x4a4a55;
26
+ const TIRE_COLOR = 0x202020;
27
+ const WHEEL_COVER_COLOR = 0x3a3a3a;
28
+ const LIDAR_BODY = 0x222233;
29
+ const LIDAR_DOME = 0x445566;
30
+ const BUMPER_COLOR = 0xeeaa00;
31
+ const BUMPER_BLACK = 0x111111;
32
+ const CHARGE_COPPER = 0xc77a00;
33
+ export class Agv3D extends RealObjectGroup {
34
+ build() {
35
+ super.build();
36
+ const { width, height, depth = 0 } = this.component.state;
37
+ const bodyColor = this.component.state.bodyColor || '#999';
38
+ const emissiveColor = this.component.state.lampEmissive || '#222222';
39
+ const status = this.component.state.status;
40
+ const lampIntensity = status && status !== 'idle' ? 1.5 : 0.2;
41
+ const chassisH = depth * 0.85;
42
+ const deckH = depth * 0.05;
43
+ const wheelR = chassisH * 0.30;
44
+ const baseY = -depth / 2;
45
+ // ── Rounded chassis (volumetric — the Kiva AGV signature) ────────
46
+ const chassisRadius = Math.min(width, height) * 0.10;
47
+ const chassisGeo = new RoundedBoxGeometry(width * 0.95, chassisH, height * 0.95, 4, chassisRadius);
48
+ const chassisMesh = new THREE.Mesh(chassisGeo, new THREE.MeshStandardMaterial({
49
+ color: CHASSIS_COLOR_DARK,
50
+ metalness: 0.4,
51
+ roughness: 0.5
52
+ }));
53
+ chassisMesh.position.set(0, baseY + chassisH / 2, 0);
54
+ chassisMesh.castShadow = true;
55
+ chassisMesh.receiveShadow = true;
56
+ this.object3d.add(chassisMesh);
57
+ // ── Top deck (rounded slab) ───────────────────────────────────────
58
+ const deckGeo = new RoundedBoxGeometry(width * 0.95, deckH, height * 0.95, 2, chassisRadius * 0.5);
59
+ const deckMesh = new THREE.Mesh(deckGeo, new THREE.MeshStandardMaterial({ color: DECK_COLOR, metalness: 0.5, roughness: 0.4 }));
60
+ deckMesh.position.set(0, baseY + chassisH + deckH / 2, 0);
61
+ deckMesh.castShadow = true;
62
+ deckMesh.receiveShadow = true;
63
+ this.object3d.add(deckMesh);
64
+ // ── Lift mechanism (round pad — Kiva-style under-shelf lift) ─────
65
+ const liftR = Math.min(width, height) * 0.30;
66
+ const liftH = deckH * 1.5;
67
+ const liftGeo = new THREE.CylinderGeometry(liftR, liftR * 1.05, liftH, 24);
68
+ const liftMesh = new THREE.Mesh(liftGeo, new THREE.MeshStandardMaterial({ color: LIFT_PAD_COLOR, metalness: 0.6, roughness: 0.35 }));
69
+ liftMesh.position.set(0, baseY + chassisH + deckH + liftH / 2, 0);
70
+ liftMesh.castShadow = true;
71
+ this.object3d.add(liftMesh);
72
+ // Body color band on lift rim (status hint)
73
+ const tintH = liftH * 0.3;
74
+ const tintGeo = new THREE.CylinderGeometry(liftR * 1.03, liftR * 1.03, tintH, 24, 1, true);
75
+ const tintMesh = new THREE.Mesh(tintGeo, new THREE.MeshStandardMaterial({
76
+ color: bodyColor,
77
+ side: THREE.DoubleSide,
78
+ transparent: true,
79
+ opacity: 0.7,
80
+ metalness: 0.1,
81
+ roughness: 0.6
82
+ }));
83
+ tintMesh.position.set(0, baseY + chassisH + deckH + tintH / 2, 0);
84
+ this.object3d.add(tintMesh);
85
+ // ── LED strip (4 sides, rounded corners follow chassis bevel) ────
86
+ const stripH = chassisH * 0.10;
87
+ const stripT = Math.min(width, height) * 0.015;
88
+ const stripY = baseY + chassisH * 0.55;
89
+ const stripMaterial = new THREE.MeshStandardMaterial({
90
+ color: emissiveColor,
91
+ emissive: emissiveColor,
92
+ emissiveIntensity: lampIntensity,
93
+ metalness: 0.1,
94
+ roughness: 0.4
95
+ });
96
+ for (const zSign of [-1, 1]) {
97
+ const stripGeo = new THREE.BoxGeometry(width * 0.85, stripH, stripT);
98
+ const stripMesh = new THREE.Mesh(stripGeo, stripMaterial);
99
+ stripMesh.position.set(0, stripY, zSign * (height * 0.475 - stripT / 2));
100
+ this.object3d.add(stripMesh);
101
+ }
102
+ for (const xSign of [-1, 1]) {
103
+ const stripGeo = new THREE.BoxGeometry(stripT, stripH, height * 0.85);
104
+ const stripMesh = new THREE.Mesh(stripGeo, stripMaterial);
105
+ stripMesh.position.set(xSign * (width * 0.475 - stripT / 2), stripY, 0);
106
+ this.object3d.add(stripMesh);
107
+ }
108
+ // ── Safety bumpers (front + rear) ────────────────────────────────
109
+ const bumperH = chassisH * 0.18;
110
+ const bumperT = Math.min(width, height) * 0.025;
111
+ const bumperY = baseY + bumperH / 2 + wheelR * 0.5;
112
+ const bumperMaterial = new THREE.MeshStandardMaterial({
113
+ color: BUMPER_COLOR,
114
+ metalness: 0.1,
115
+ roughness: 0.6
116
+ });
117
+ const bumperBlackMaterial = new THREE.MeshStandardMaterial({
118
+ color: BUMPER_BLACK,
119
+ metalness: 0.2,
120
+ roughness: 0.7
121
+ });
122
+ for (const zSign of [-1, 1]) {
123
+ const yellow = new RoundedBoxGeometry(width * 0.7, bumperH, bumperT, 2, bumperT * 0.4);
124
+ const yMesh = new THREE.Mesh(yellow, bumperMaterial);
125
+ yMesh.position.set(0, bumperY, zSign * (height * 0.475 + bumperT / 2));
126
+ this.object3d.add(yMesh);
127
+ for (const xSign of [-1, 1]) {
128
+ const black = new RoundedBoxGeometry(width * 0.13, bumperH, bumperT, 2, bumperT * 0.4);
129
+ const bMesh = new THREE.Mesh(black, bumperBlackMaterial);
130
+ bMesh.position.set(xSign * (width * 0.7 / 2 + width * 0.065), bumperY, zSign * (height * 0.475 + bumperT / 2));
131
+ this.object3d.add(bMesh);
132
+ }
133
+ }
134
+ // ── LiDAR sensor (cylinder body + transparent dome) ──────────────
135
+ const lidarR = Math.min(width, height) * 0.10;
136
+ const lidarBodyH = lidarR * 0.7;
137
+ const lidarBodyGeo = new THREE.CylinderGeometry(lidarR, lidarR * 1.1, lidarBodyH, 16);
138
+ const lidarBodyMesh = new THREE.Mesh(lidarBodyGeo, new THREE.MeshStandardMaterial({ color: LIDAR_BODY, metalness: 0.6, roughness: 0.4 }));
139
+ lidarBodyMesh.position.set(0, baseY + chassisH + lidarBodyH / 2, height * 0.35);
140
+ lidarBodyMesh.castShadow = true;
141
+ this.object3d.add(lidarBodyMesh);
142
+ const domeGeo = new THREE.SphereGeometry(lidarR * 0.95, 24, 16, 0, Math.PI * 2, 0, Math.PI / 2);
143
+ const domeMesh = new THREE.Mesh(domeGeo, new THREE.MeshStandardMaterial({
144
+ color: LIDAR_DOME,
145
+ metalness: 0.3,
146
+ roughness: 0.2,
147
+ transparent: true,
148
+ opacity: 0.8
149
+ }));
150
+ domeMesh.position.set(0, baseY + chassisH + lidarBodyH, height * 0.35);
151
+ this.object3d.add(domeMesh);
152
+ // Status hint on LiDAR top
153
+ const hintR = lidarR * 0.25;
154
+ const hintMesh = new THREE.Mesh(new THREE.SphereGeometry(hintR, 24, 8), new THREE.MeshStandardMaterial({
155
+ color: emissiveColor,
156
+ emissive: emissiveColor,
157
+ emissiveIntensity: lampIntensity,
158
+ roughness: 0.3
159
+ }));
160
+ hintMesh.position.set(0, baseY + chassisH + lidarBodyH + lidarR * 0.85, height * 0.35);
161
+ this.object3d.add(hintMesh);
162
+ // ── Wheels with covers ───────────────────────────────────────────
163
+ const wheelW = width * 0.07;
164
+ const tireMaterial = new THREE.MeshStandardMaterial({ color: TIRE_COLOR, roughness: 0.95 });
165
+ const coverMaterial = new THREE.MeshStandardMaterial({
166
+ color: WHEEL_COVER_COLOR,
167
+ metalness: 0.5,
168
+ roughness: 0.6
169
+ });
170
+ const tireGeos = [];
171
+ for (const xSign of [-1, 1]) {
172
+ for (const zSign of [-1, 1]) {
173
+ const tire = new THREE.CylinderGeometry(wheelR, wheelR, wheelW, 24);
174
+ tire.rotateZ(Math.PI / 2);
175
+ tire.translate(xSign * width * 0.42, baseY + wheelR, zSign * height * 0.32);
176
+ tireGeos.push(tire);
177
+ // Rounded fender cover
178
+ const cover = new RoundedBoxGeometry(wheelW * 1.5, wheelR * 1.4, wheelR * 1.6, 2, wheelR * 0.25);
179
+ cover.translate(xSign * (width * 0.42 + wheelW * 0.4), baseY + wheelR * 1.2, zSign * height * 0.32);
180
+ const coverMesh = new THREE.Mesh(cover, coverMaterial);
181
+ coverMesh.castShadow = true;
182
+ this.object3d.add(coverMesh);
183
+ }
184
+ }
185
+ const tireMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(tireGeos), tireMaterial);
186
+ tireMesh.castShadow = true;
187
+ this.object3d.add(tireMesh);
188
+ // ── Charging contacts (rear) ─────────────────────────────────────
189
+ const chargeMaterial = new THREE.MeshStandardMaterial({
190
+ color: CHARGE_COPPER,
191
+ metalness: 0.9,
192
+ roughness: 0.2
193
+ });
194
+ const chargeT = wheelR * 0.15;
195
+ const chargeH = wheelR * 0.6;
196
+ const chargeW = width * 0.08;
197
+ for (const xSign of [-1, 1]) {
198
+ const chargeGeo = new RoundedBoxGeometry(chargeW, chargeH, chargeT, 1, chargeT * 0.3);
199
+ const chargeMesh = new THREE.Mesh(chargeGeo, chargeMaterial);
200
+ chargeMesh.position.set(xSign * width * 0.10, baseY + chargeH / 2 + wheelR * 0.2, -height * 0.475 - chargeT / 2);
201
+ this.object3d.add(chargeMesh);
202
+ }
203
+ // ── Corner indicator lamps ───────────────────────────────────────
204
+ const cornerR = Math.min(width, height) * 0.022;
205
+ for (const xSign of [-1, 1]) {
206
+ for (const zSign of [-1, 1]) {
207
+ const cornerMesh = new THREE.Mesh(new THREE.SphereGeometry(cornerR, 24, 6), new THREE.MeshStandardMaterial({
208
+ color: emissiveColor,
209
+ emissive: emissiveColor,
210
+ emissiveIntensity: lampIntensity * 0.6,
211
+ roughness: 0.3
212
+ }));
213
+ cornerMesh.position.set(xSign * (width * 0.42), baseY + chassisH + deckH + cornerR * 0.6, zSign * (height * 0.42));
214
+ this.object3d.add(cornerMesh);
215
+ }
216
+ }
217
+ }
218
+ updateDimension() { }
219
+ onchange(after, before) {
220
+ if ('status' in after ||
221
+ 'bodyColor' in after ||
222
+ 'lampEmissive' in after ||
223
+ 'width' in after ||
224
+ 'height' in after ||
225
+ 'depth' in after) {
226
+ this.update();
227
+ return;
228
+ }
229
+ super.onchange(after, before);
230
+ }
231
+ updateAlpha() { }
232
+ }
233
+ //# sourceMappingURL=agv-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agv-3d.js","sourceRoot":"","sources":["../src/agv-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Agv 3D — payload (Kiva-style) automated guided vehicle.\n *\n * LO-POLY but with **rounded volume** — the Kiva AGV's signature is a\n * pill-shaped, low-slung chassis with rounded corners, not a flat box.\n *\n * Structure:\n * - rounded-box chassis (volumetric, with bevels — the visual identity)\n * - top deck with raised lift pad at center (Kiva-style under-shelf lift)\n * - LED strip running around the perimeter\n * - safety bumpers (front + rear, hi-vis with black corners)\n * - LiDAR sensor: cylindrical body + transparent dome top\n * - 4 wheel covers (fender bumps integrated into chassis sides)\n * - charging contacts at rear\n * - corner indicator lamps on deck\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst CHASSIS_COLOR_DARK = 0x444444\nconst DECK_COLOR = 0x666677\nconst LIFT_PAD_COLOR = 0x4a4a55\nconst TIRE_COLOR = 0x202020\nconst WHEEL_COVER_COLOR = 0x3a3a3a\nconst LIDAR_BODY = 0x222233\nconst LIDAR_DOME = 0x445566\nconst BUMPER_COLOR = 0xeeaa00\nconst BUMPER_BLACK = 0x111111\nconst CHARGE_COPPER = 0xc77a00\n\nexport class Agv3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 0 } = this.component.state\n const bodyColor = (this.component.state.bodyColor as string) || '#999'\n const emissiveColor = (this.component.state.lampEmissive as string) || '#222222'\n const status = this.component.state.status\n const lampIntensity = status && status !== 'idle' ? 1.5 : 0.2\n\n const chassisH = depth * 0.85\n const deckH = depth * 0.05\n const wheelR = chassisH * 0.30\n const baseY = -depth / 2\n\n // ── Rounded chassis (volumetric — the Kiva AGV signature) ────────\n const chassisRadius = Math.min(width, height) * 0.10\n const chassisGeo = new RoundedBoxGeometry(width * 0.95, chassisH, height * 0.95, 4, chassisRadius)\n const chassisMesh = new THREE.Mesh(\n chassisGeo,\n new THREE.MeshStandardMaterial({\n color: CHASSIS_COLOR_DARK,\n metalness: 0.4,\n roughness: 0.5\n })\n )\n chassisMesh.position.set(0, baseY + chassisH / 2, 0)\n chassisMesh.castShadow = true\n chassisMesh.receiveShadow = true\n this.object3d.add(chassisMesh)\n\n // ── Top deck (rounded slab) ───────────────────────────────────────\n const deckGeo = new RoundedBoxGeometry(width * 0.95, deckH, height * 0.95, 2, chassisRadius * 0.5)\n const deckMesh = new THREE.Mesh(\n deckGeo,\n new THREE.MeshStandardMaterial({ color: DECK_COLOR, metalness: 0.5, roughness: 0.4 })\n )\n deckMesh.position.set(0, baseY + chassisH + deckH / 2, 0)\n deckMesh.castShadow = true\n deckMesh.receiveShadow = true\n this.object3d.add(deckMesh)\n\n // ── Lift mechanism (round pad — Kiva-style under-shelf lift) ─────\n const liftR = Math.min(width, height) * 0.30\n const liftH = deckH * 1.5\n const liftGeo = new THREE.CylinderGeometry(liftR, liftR * 1.05, liftH, 24)\n const liftMesh = new THREE.Mesh(\n liftGeo,\n new THREE.MeshStandardMaterial({ color: LIFT_PAD_COLOR, metalness: 0.6, roughness: 0.35 })\n )\n liftMesh.position.set(0, baseY + chassisH + deckH + liftH / 2, 0)\n liftMesh.castShadow = true\n this.object3d.add(liftMesh)\n\n // Body color band on lift rim (status hint)\n const tintH = liftH * 0.3\n const tintGeo = new THREE.CylinderGeometry(liftR * 1.03, liftR * 1.03, tintH, 24, 1, true)\n const tintMesh = new THREE.Mesh(\n tintGeo,\n new THREE.MeshStandardMaterial({\n color: bodyColor,\n side: THREE.DoubleSide,\n transparent: true,\n opacity: 0.7,\n metalness: 0.1,\n roughness: 0.6\n })\n )\n tintMesh.position.set(0, baseY + chassisH + deckH + tintH / 2, 0)\n this.object3d.add(tintMesh)\n\n // ── LED strip (4 sides, rounded corners follow chassis bevel) ────\n const stripH = chassisH * 0.10\n const stripT = Math.min(width, height) * 0.015\n const stripY = baseY + chassisH * 0.55\n const stripMaterial = new THREE.MeshStandardMaterial({\n color: emissiveColor,\n emissive: emissiveColor,\n emissiveIntensity: lampIntensity,\n metalness: 0.1,\n roughness: 0.4\n })\n for (const zSign of [-1, 1]) {\n const stripGeo = new THREE.BoxGeometry(width * 0.85, stripH, stripT)\n const stripMesh = new THREE.Mesh(stripGeo, stripMaterial)\n stripMesh.position.set(0, stripY, zSign * (height * 0.475 - stripT / 2))\n this.object3d.add(stripMesh)\n }\n for (const xSign of [-1, 1]) {\n const stripGeo = new THREE.BoxGeometry(stripT, stripH, height * 0.85)\n const stripMesh = new THREE.Mesh(stripGeo, stripMaterial)\n stripMesh.position.set(xSign * (width * 0.475 - stripT / 2), stripY, 0)\n this.object3d.add(stripMesh)\n }\n\n // ── Safety bumpers (front + rear) ────────────────────────────────\n const bumperH = chassisH * 0.18\n const bumperT = Math.min(width, height) * 0.025\n const bumperY = baseY + bumperH / 2 + wheelR * 0.5\n const bumperMaterial = new THREE.MeshStandardMaterial({\n color: BUMPER_COLOR,\n metalness: 0.1,\n roughness: 0.6\n })\n const bumperBlackMaterial = new THREE.MeshStandardMaterial({\n color: BUMPER_BLACK,\n metalness: 0.2,\n roughness: 0.7\n })\n for (const zSign of [-1, 1]) {\n const yellow = new RoundedBoxGeometry(width * 0.7, bumperH, bumperT, 2, bumperT * 0.4)\n const yMesh = new THREE.Mesh(yellow, bumperMaterial)\n yMesh.position.set(0, bumperY, zSign * (height * 0.475 + bumperT / 2))\n this.object3d.add(yMesh)\n for (const xSign of [-1, 1]) {\n const black = new RoundedBoxGeometry(width * 0.13, bumperH, bumperT, 2, bumperT * 0.4)\n const bMesh = new THREE.Mesh(black, bumperBlackMaterial)\n bMesh.position.set(xSign * (width * 0.7 / 2 + width * 0.065), bumperY, zSign * (height * 0.475 + bumperT / 2))\n this.object3d.add(bMesh)\n }\n }\n\n // ── LiDAR sensor (cylinder body + transparent dome) ──────────────\n const lidarR = Math.min(width, height) * 0.10\n const lidarBodyH = lidarR * 0.7\n const lidarBodyGeo = new THREE.CylinderGeometry(lidarR, lidarR * 1.1, lidarBodyH, 16)\n const lidarBodyMesh = new THREE.Mesh(\n lidarBodyGeo,\n new THREE.MeshStandardMaterial({ color: LIDAR_BODY, metalness: 0.6, roughness: 0.4 })\n )\n lidarBodyMesh.position.set(0, baseY + chassisH + lidarBodyH / 2, height * 0.35)\n lidarBodyMesh.castShadow = true\n this.object3d.add(lidarBodyMesh)\n\n const domeGeo = new THREE.SphereGeometry(lidarR * 0.95, 24, 16, 0, Math.PI * 2, 0, Math.PI / 2)\n const domeMesh = new THREE.Mesh(\n domeGeo,\n new THREE.MeshStandardMaterial({\n color: LIDAR_DOME,\n metalness: 0.3,\n roughness: 0.2,\n transparent: true,\n opacity: 0.8\n })\n )\n domeMesh.position.set(0, baseY + chassisH + lidarBodyH, height * 0.35)\n this.object3d.add(domeMesh)\n\n // Status hint on LiDAR top\n const hintR = lidarR * 0.25\n const hintMesh = new THREE.Mesh(\n new THREE.SphereGeometry(hintR, 24, 8),\n new THREE.MeshStandardMaterial({\n color: emissiveColor,\n emissive: emissiveColor,\n emissiveIntensity: lampIntensity,\n roughness: 0.3\n })\n )\n hintMesh.position.set(0, baseY + chassisH + lidarBodyH + lidarR * 0.85, height * 0.35)\n this.object3d.add(hintMesh)\n\n // ── Wheels with covers ───────────────────────────────────────────\n const wheelW = width * 0.07\n const tireMaterial = new THREE.MeshStandardMaterial({ color: TIRE_COLOR, roughness: 0.95 })\n const coverMaterial = new THREE.MeshStandardMaterial({\n color: WHEEL_COVER_COLOR,\n metalness: 0.5,\n roughness: 0.6\n })\n\n const tireGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const tire = new THREE.CylinderGeometry(wheelR, wheelR, wheelW, 24)\n tire.rotateZ(Math.PI / 2)\n tire.translate(xSign * width * 0.42, baseY + wheelR, zSign * height * 0.32)\n tireGeos.push(tire)\n // Rounded fender cover\n const cover = new RoundedBoxGeometry(wheelW * 1.5, wheelR * 1.4, wheelR * 1.6, 2, wheelR * 0.25)\n cover.translate(xSign * (width * 0.42 + wheelW * 0.4), baseY + wheelR * 1.2, zSign * height * 0.32)\n const coverMesh = new THREE.Mesh(cover, coverMaterial)\n coverMesh.castShadow = true\n this.object3d.add(coverMesh)\n }\n }\n const tireMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(tireGeos), tireMaterial)\n tireMesh.castShadow = true\n this.object3d.add(tireMesh)\n\n // ── Charging contacts (rear) ─────────────────────────────────────\n const chargeMaterial = new THREE.MeshStandardMaterial({\n color: CHARGE_COPPER,\n metalness: 0.9,\n roughness: 0.2\n })\n const chargeT = wheelR * 0.15\n const chargeH = wheelR * 0.6\n const chargeW = width * 0.08\n for (const xSign of [-1, 1]) {\n const chargeGeo = new RoundedBoxGeometry(chargeW, chargeH, chargeT, 1, chargeT * 0.3)\n const chargeMesh = new THREE.Mesh(chargeGeo, chargeMaterial)\n chargeMesh.position.set(xSign * width * 0.10, baseY + chargeH / 2 + wheelR * 0.2, -height * 0.475 - chargeT / 2)\n this.object3d.add(chargeMesh)\n }\n\n // ── Corner indicator lamps ───────────────────────────────────────\n const cornerR = Math.min(width, height) * 0.022\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const cornerMesh = new THREE.Mesh(\n new THREE.SphereGeometry(cornerR, 24, 6),\n new THREE.MeshStandardMaterial({\n color: emissiveColor,\n emissive: emissiveColor,\n emissiveIntensity: lampIntensity * 0.6,\n roughness: 0.3\n })\n )\n cornerMesh.position.set(\n xSign * (width * 0.42),\n baseY + chassisH + deckH + cornerR * 0.6,\n zSign * (height * 0.42)\n )\n this.object3d.add(cornerMesh)\n }\n }\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'status' in after ||\n 'bodyColor' in after ||\n 'lampEmissive' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
package/dist/agv.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
2
+ import { type Alignment, type Heights, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
3
+ /**
4
+ * Agv status — common to both payload and towing AGVs (kept narrow on purpose).
5
+ *
6
+ * - `idle` — parked, awaiting task
7
+ * - `moving` — driving along a path / executing transport
8
+ * - `charging` — at a charging dock / battery station
9
+ * - `error` — fault / blocked / e-stop
10
+ *
11
+ * Loaded-vs-empty distinction is *not* in the status enum because for a
12
+ * Kiva-style payload AGV it's already obvious from the children (cargo
13
+ * components) parented to it — duplicating that as a status flag would
14
+ * invite drift.
15
+ */
16
+ export type AgvStatus = 'idle' | 'moving' | 'charging' | 'error';
17
+ declare const Base: typeof Component;
18
+ /**
19
+ * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck
20
+ * AGV that drives under or carries cargo to/from operation surfaces.
21
+ *
22
+ * **Container-based for cargo containment.** A payload Agv has a flat top
23
+ * deck whose surface is at the scene's operation height. Boxes, parcels,
24
+ * loaded pallets (anything with `placement: 'operation'`) can be added as
25
+ * children — when they are, their natural archetype-derived zPos puts them
26
+ * exactly on the AGV's deck (since AGV depth = operation - floor).
27
+ *
28
+ * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.
29
+ */
30
+ export default class Agv extends Base {
31
+ static legends: Record<string, LegendBinding>;
32
+ /**
33
+ * AGV sits on its wheels — `floor` archetype. Default depth = operation,
34
+ * so the top deck lands at the scene's operation height. This is the
35
+ * design point of payload AGVs: the deck height matches conveyor belt
36
+ * height, equipment ports, and forklift fork height — cargo transfers
37
+ * across all of them at the same level.
38
+ */
39
+ static placement: PlacementArchetype;
40
+ static align: Alignment;
41
+ static defaultDepth: (h: Heights) => number;
42
+ get nature(): ComponentNature;
43
+ get anchors(): never[];
44
+ /** Accept logistics packages (placement='operation') as deck cargo. */
45
+ containable(component: Component): boolean;
46
+ /**
47
+ * 2D — render() sets up the rounded chassis path; the framework fills it
48
+ * with `fillStyle` (= bodyColor from Legendable) and strokes with
49
+ * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,
50
+ * direction triangle) are drawn in `postrender()`.
51
+ */
52
+ render(ctx: CanvasRenderingContext2D): void;
53
+ /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */
54
+ postrender(ctx: CanvasRenderingContext2D): void;
55
+ buildRealObject(): RealObject | undefined;
56
+ }
57
+ export {};
package/dist/agv.js ADDED
@@ -0,0 +1,171 @@
1
+ import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ */
5
+ import { Container, sceneComponent } from '@hatiolab/things-scene';
6
+ import { Legendable, Placeable } from '@operato/scene-base';
7
+ import { Agv3D } from './agv-3d.js';
8
+ /**
9
+ * Body color — neutral industrial gray base, slightly modulated by status.
10
+ * AGVs typically have status-color LED strips rather than full body color
11
+ * change; the body legend stays subtle so the LED strip + lamp do the
12
+ * communicating.
13
+ */
14
+ const BODY_LEGEND = {
15
+ idle: '#999',
16
+ moving: '#aaa',
17
+ charging: '#aaa',
18
+ error: '#c66',
19
+ default: '#999'
20
+ };
21
+ /**
22
+ * LED strip emissive — the dominant status indicator for AGVs (typically a
23
+ * color-band running around the chassis perimeter).
24
+ */
25
+ const LAMP_EMISSIVE_LEGEND = {
26
+ idle: '#222222',
27
+ moving: '#44ff44', // green (operating)
28
+ charging: '#ffaa00', // amber (charging)
29
+ error: '#ff3333', // red
30
+ default: '#222222'
31
+ };
32
+ const NATURE = {
33
+ mutable: false,
34
+ resizable: true,
35
+ rotatable: true,
36
+ properties: [
37
+ {
38
+ type: 'select',
39
+ label: 'status',
40
+ name: 'status',
41
+ property: {
42
+ options: [
43
+ { display: 'Idle', value: 'idle' },
44
+ { display: 'Moving', value: 'moving' },
45
+ { display: 'Charging', value: 'charging' },
46
+ { display: 'Error', value: 'error' }
47
+ ]
48
+ }
49
+ },
50
+ {
51
+ type: 'number',
52
+ label: 'battery',
53
+ name: 'battery',
54
+ placeholder: '0..1'
55
+ }
56
+ ],
57
+ help: 'scene/component/agv'
58
+ };
59
+ const Base = Legendable(Placeable(Container));
60
+ /**
61
+ * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck
62
+ * AGV that drives under or carries cargo to/from operation surfaces.
63
+ *
64
+ * **Container-based for cargo containment.** A payload Agv has a flat top
65
+ * deck whose surface is at the scene's operation height. Boxes, parcels,
66
+ * loaded pallets (anything with `placement: 'operation'`) can be added as
67
+ * children — when they are, their natural archetype-derived zPos puts them
68
+ * exactly on the AGV's deck (since AGV depth = operation - floor).
69
+ *
70
+ * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.
71
+ */
72
+ let Agv = class Agv extends Base {
73
+ static legends = {
74
+ bodyColor: { from: 'status', legend: BODY_LEGEND },
75
+ lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
76
+ };
77
+ /**
78
+ * AGV sits on its wheels — `floor` archetype. Default depth = operation,
79
+ * so the top deck lands at the scene's operation height. This is the
80
+ * design point of payload AGVs: the deck height matches conveyor belt
81
+ * height, equipment ports, and forklift fork height — cargo transfers
82
+ * across all of them at the same level.
83
+ */
84
+ static placement = 'floor';
85
+ static align = 'bottom';
86
+ static defaultDepth = (h) => h.operation - h.floor;
87
+ get nature() {
88
+ return NATURE;
89
+ }
90
+ get anchors() {
91
+ return [];
92
+ }
93
+ /** Accept logistics packages (placement='operation') as deck cargo. */
94
+ containable(component) {
95
+ const archetype = component.constructor.placement;
96
+ if (archetype === 'operation')
97
+ return true;
98
+ return component.isDescendible(this);
99
+ }
100
+ /**
101
+ * 2D — render() sets up the rounded chassis path; the framework fills it
102
+ * with `fillStyle` (= bodyColor from Legendable) and strokes with
103
+ * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,
104
+ * direction triangle) are drawn in `postrender()`.
105
+ */
106
+ render(ctx) {
107
+ const { width, height, left, top } = this.state;
108
+ const radius = Math.min(width, height) * 0.12;
109
+ ctx.beginPath();
110
+ ctx.roundRect(left, top, width, height, radius);
111
+ }
112
+ /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */
113
+ postrender(ctx) {
114
+ super.postrender?.(ctx);
115
+ const { width, height, left, top } = this.state;
116
+ const cx = left + width / 2;
117
+ const cy = top + height / 2;
118
+ const accentColor = this.state.lampEmissive || '#44ff44';
119
+ ctx.save();
120
+ // Hi-vis bumper strips (front + rear)
121
+ const bumperT = Math.min(width, height) * 0.06;
122
+ ctx.fillStyle = '#eeaa00';
123
+ ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT);
124
+ ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT);
125
+ ctx.fillStyle = '#111';
126
+ ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT);
127
+ ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT);
128
+ ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT);
129
+ ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT);
130
+ // Lift pad (center circle — Kiva-style under-shelf lift)
131
+ const padR = Math.min(width, height) * 0.22;
132
+ ctx.fillStyle = '#4a4a55';
133
+ ctx.strokeStyle = '#222';
134
+ ctx.lineWidth = 1;
135
+ ctx.beginPath();
136
+ ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2);
137
+ ctx.fill();
138
+ ctx.stroke();
139
+ // LiDAR sensor (small filled circle near front)
140
+ const lidarR = Math.min(width, height) * 0.07;
141
+ ctx.fillStyle = '#222233';
142
+ ctx.beginPath();
143
+ ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2);
144
+ ctx.fill();
145
+ ctx.stroke();
146
+ // Status hint on LiDAR
147
+ ctx.fillStyle = accentColor;
148
+ ctx.beginPath();
149
+ ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2);
150
+ ctx.fill();
151
+ // Direction-of-travel triangle
152
+ ctx.fillStyle = accentColor;
153
+ ctx.strokeStyle = '#111';
154
+ ctx.beginPath();
155
+ ctx.moveTo(cx, top + height * 0.06);
156
+ ctx.lineTo(cx - width * 0.06, top + height * 0.13);
157
+ ctx.lineTo(cx + width * 0.06, top + height * 0.13);
158
+ ctx.closePath();
159
+ ctx.fill();
160
+ ctx.stroke();
161
+ ctx.restore();
162
+ }
163
+ buildRealObject() {
164
+ return new Agv3D(this);
165
+ }
166
+ };
167
+ Agv = __decorate([
168
+ sceneComponent('agv')
169
+ ], Agv);
170
+ export default Agv;
171
+ //# sourceMappingURL=agv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, Container, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Legendable,\n Placeable,\n type Alignment,\n type Heights,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Agv3D } from './agv-3d.js'\n\n/**\n * Agv status — common to both payload and towing AGVs (kept narrow on purpose).\n *\n * - `idle` — parked, awaiting task\n * - `moving` — driving along a path / executing transport\n * - `charging` — at a charging dock / battery station\n * - `error` — fault / blocked / e-stop\n *\n * Loaded-vs-empty distinction is *not* in the status enum because for a\n * Kiva-style payload AGV it's already obvious from the children (cargo\n * components) parented to it — duplicating that as a status flag would\n * invite drift.\n */\nexport type AgvStatus = 'idle' | 'moving' | 'charging' | 'error'\n\n/**\n * Body color — neutral industrial gray base, slightly modulated by status.\n * AGVs typically have status-color LED strips rather than full body color\n * change; the body legend stays subtle so the LED strip + lamp do the\n * communicating.\n */\nconst BODY_LEGEND = {\n idle: '#999',\n moving: '#aaa',\n charging: '#aaa',\n error: '#c66',\n default: '#999'\n}\n\n/**\n * LED strip emissive — the dominant status indicator for AGVs (typically a\n * color-band running around the chassis perimeter).\n */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44', // green (operating)\n charging: '#ffaa00', // amber (charging)\n error: '#ff3333', // red\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: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n }\n ],\n help: 'scene/component/agv'\n}\n\nconst Base = Legendable(Placeable(Container)) as unknown as typeof Component\n\n/**\n * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck\n * AGV that drives under or carries cargo to/from operation surfaces.\n *\n * **Container-based for cargo containment.** A payload Agv has a flat top\n * deck whose surface is at the scene's operation height. Boxes, parcels,\n * loaded pallets (anything with `placement: 'operation'`) can be added as\n * children — when they are, their natural archetype-derived zPos puts them\n * exactly on the AGV's deck (since AGV depth = operation - floor).\n *\n * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.\n */\n@sceneComponent('agv')\nexport default class Agv extends Base {\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * AGV sits on its wheels — `floor` archetype. Default depth = operation,\n * so the top deck lands at the scene's operation height. This is the\n * design point of payload AGVs: the deck height matches conveyor belt\n * height, equipment ports, and forklift fork height — cargo transfers\n * across all of them at the same level.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /** Accept logistics packages (placement='operation') as deck cargo. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this as any)\n }\n\n /**\n * 2D — render() sets up the rounded chassis path; the framework fills it\n * with `fillStyle` (= bodyColor from Legendable) and strokes with\n * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,\n * direction triangle) are drawn in `postrender()`.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.12\n ctx.beginPath()\n ctx.roundRect(left, top, width, height, radius)\n }\n\n /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const cy = top + height / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hi-vis bumper strips (front + rear)\n const bumperT = Math.min(width, height) * 0.06\n ctx.fillStyle = '#eeaa00'\n ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT)\n ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT)\n ctx.fillStyle = '#111'\n ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT)\n\n // Lift pad (center circle — Kiva-style under-shelf lift)\n const padR = Math.min(width, height) * 0.22\n ctx.fillStyle = '#4a4a55'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor (small filled circle near front)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n // Status hint on LiDAR\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Direction-of-travel triangle\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.moveTo(cx, top + height * 0.06)\n ctx.lineTo(cx - width * 0.06, top + height * 0.13)\n ctx.lineTo(cx + width * 0.06, top + height * 0.13)\n ctx.closePath()\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Agv3D(this as any)\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Forklift3D extends RealObjectGroup {
3
+ /** Local-frame position of the fork-tip top surface — where cargo sits. */
4
+ get cargoMount(): {
5
+ x: number;
6
+ y: number;
7
+ z: number;
8
+ width: number;
9
+ depth: number;
10
+ };
11
+ build(): void;
12
+ updateDimension(): void;
13
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
14
+ updateAlpha(): void;
15
+ }