@operato/scene-storage 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 (78) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +59 -0
  3. package/dist/asrs-crane-3d.d.ts +7 -0
  4. package/dist/asrs-crane-3d.js +164 -0
  5. package/dist/asrs-crane-3d.js.map +1 -0
  6. package/dist/asrs-crane.d.ts +47 -0
  7. package/dist/asrs-crane.js +104 -0
  8. package/dist/asrs-crane.js.map +1 -0
  9. package/dist/asrs-rack-3d.d.ts +7 -0
  10. package/dist/asrs-rack-3d.js +129 -0
  11. package/dist/asrs-rack-3d.js.map +1 -0
  12. package/dist/asrs-rack.d.ts +45 -0
  13. package/dist/asrs-rack.js +99 -0
  14. package/dist/asrs-rack.js.map +1 -0
  15. package/dist/box-3d.d.ts +11 -0
  16. package/dist/box-3d.js +166 -0
  17. package/dist/box-3d.js.map +1 -0
  18. package/dist/box.d.ts +36 -0
  19. package/dist/box.js +73 -0
  20. package/dist/box.js.map +1 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +11 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/pallet-3d.d.ts +11 -0
  25. package/dist/pallet-3d.js +162 -0
  26. package/dist/pallet-3d.js.map +1 -0
  27. package/dist/pallet.d.ts +56 -0
  28. package/dist/pallet.js +99 -0
  29. package/dist/pallet.js.map +1 -0
  30. package/dist/parcel-3d.d.ts +7 -0
  31. package/dist/parcel-3d.js +82 -0
  32. package/dist/parcel-3d.js.map +1 -0
  33. package/dist/parcel.d.ts +30 -0
  34. package/dist/parcel.js +67 -0
  35. package/dist/parcel.js.map +1 -0
  36. package/dist/spot-3d.d.ts +30 -0
  37. package/dist/spot-3d.js +176 -0
  38. package/dist/spot-3d.js.map +1 -0
  39. package/dist/spot.d.ts +41 -0
  40. package/dist/spot.js +177 -0
  41. package/dist/spot.js.map +1 -0
  42. package/dist/templates/index.d.ts +92 -0
  43. package/dist/templates/index.js +115 -0
  44. package/dist/templates/index.js.map +1 -0
  45. package/dist/templates/spot.d.ts +24 -0
  46. package/dist/templates/spot.js +26 -0
  47. package/dist/templates/spot.js.map +1 -0
  48. package/icons/asrs-crane.png +0 -0
  49. package/icons/asrs-rack.png +0 -0
  50. package/icons/box-plastic.png +0 -0
  51. package/icons/box-wood.png +0 -0
  52. package/icons/pallet-plastic.png +0 -0
  53. package/icons/pallet-wood.png +0 -0
  54. package/icons/parcel.png +0 -0
  55. package/package.json +44 -0
  56. package/src/asrs-crane-3d.ts +191 -0
  57. package/src/asrs-crane.ts +130 -0
  58. package/src/asrs-rack-3d.ts +146 -0
  59. package/src/asrs-rack.ts +109 -0
  60. package/src/box-3d.ts +189 -0
  61. package/src/box.ts +99 -0
  62. package/src/index.ts +17 -0
  63. package/src/pallet-3d.ts +181 -0
  64. package/src/pallet.ts +125 -0
  65. package/src/parcel-3d.ts +90 -0
  66. package/src/parcel.ts +76 -0
  67. package/src/spot-3d.ts +200 -0
  68. package/src/spot.ts +197 -0
  69. package/src/templates/index.ts +115 -0
  70. package/src/templates/spot.ts +26 -0
  71. package/things-scene.config.js +5 -0
  72. package/translations/en.json +12 -0
  73. package/translations/ja.json +12 -0
  74. package/translations/ko.json +12 -0
  75. package/translations/ms.json +12 -0
  76. package/translations/zh.json +12 -0
  77. package/tsconfig.json +23 -0
  78. package/tsconfig.tsbuildinfo +1 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
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-base,manufacturing,storage,conveyance:** Carriable/CarrierHolder mixins + RobotArm + Spot ([5e3ee78](https://github.com/things-scene/operato-scene/commit/5e3ee784d6fb51addc602921863113b967c7feb3))
12
+ * **scene-facility,base:** add Floor + placeholder icons + base config fix ([830d57e](https://github.com/things-scene/operato-scene/commit/830d57ee8ecd7a31e7289f7cd6fe52027aa0380a))
13
+ * **scene-storage:** add AsrsRack + AsrsCrane (Phase A6) ([a553d2c](https://github.com/things-scene/operato-scene/commit/a553d2c7294182727a42c082d48b57f3167d43f4))
14
+ * **scene-transport,storage:** rebuild Forklift3D from scratch + align Pallet decks ([83aa335](https://github.com/things-scene/operato-scene/commit/83aa33531ce24615dc15e295d28872dec85e29de))
15
+ * **scene:** @operato/scene-storage — Pallet + Box + Parcel (Phase A5) ([314f164](https://github.com/things-scene/operato-scene/commit/314f164a37379e36b7fa07c93178de8e36b41bf3))
16
+
17
+
18
+ ### :bug: Bug Fix
19
+
20
+ * **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))
21
+
22
+
23
+ ### :house: Code Refactoring
24
+
25
+ * **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,59 @@
1
+ # @operato/scene-storage
2
+
3
+ **Storage** domain components for things-scene — the *cargo* and (eventually) the *infrastructure that holds it*.
4
+
5
+ ## Domain context
6
+
7
+ This package collects the things that *flow* through a logistics scene at the operation surface — pallets, boxes, parcels — and (future phases) the structures that store them: shelves, racks, AS/RS, mezzanines.
8
+
9
+ Initial scope (Phase A5): Pallet, Box, Parcel. All declare `placement: 'operation'` — the first concrete use of the *operation* archetype, riding on conveyor surfaces, AGV decks, and forklift forks set up in earlier phases.
10
+
11
+ Planned (Phase A6+): Shelf, Rack, AS/RS, Mezzanine.
12
+
13
+ ## First operation-archetype components
14
+
15
+ Conveyance and Transport are `floor`-archetype (structural). Storage cargo is `operation`-archetype — items that don't sit on the floor of their own accord but ride on the operation surface that conveyance creates. `containable()` filters in Forklift / Agv let these auto-mount when the user adds them as children.
16
+
17
+ <!-- AUTOGEN_BEGIN: do not edit between markers (run scripts/regenerate-readmes.mjs to update) -->
18
+
19
+ ## Components
20
+
21
+ - `Pallet`
22
+ - `Box`
23
+ - `Parcel`
24
+ - `AsrsRack`
25
+ - `AsrsCrane`
26
+
27
+ ## Templates (things-scene catalog)
28
+
29
+ | type | group | description |
30
+ |---|---|---|
31
+ | `pallet` | warehouse | wood pallet (EUR / EPAL) |
32
+ | `box` | warehouse | wood crate |
33
+ | `parcel` | warehouse | cardboard parcel |
34
+ | `asrs-rack` | warehouse | AS/RS storage rack (multi-level) |
35
+ | `asrs-crane` | warehouse | AS/RS stacker crane |
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ yarn add @operato/scene-storage
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```ts
46
+ import { Pallet, Box, Parcel, AsrsRack, AsrsCrane } from '@operato/scene-storage'
47
+ ```
48
+
49
+ ## Build
50
+
51
+ ```bash
52
+ yarn build
53
+ ```
54
+
55
+ Output: ESM module at `dist/index.js` (single bundle, no UMD/IE legacy).
56
+
57
+ _Version: 10.0.0-beta.1_
58
+
59
+ <!-- AUTOGEN_END -->
@@ -0,0 +1,7 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class AsrsCrane3D extends RealObjectGroup {
3
+ build(): void;
4
+ updateDimension(): void;
5
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
6
+ updateAlpha(): void;
7
+ }
@@ -0,0 +1,164 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * AsrsCrane 3D — stacker crane for AS/RS aisles.
5
+ *
6
+ * LO-POLY but structurally complete. The signature parts:
7
+ *
8
+ * - tall vertical mast (the dominant element — runs floor → ceiling)
9
+ * - base unit (motor housing at floor level, runs on the floor rail)
10
+ * - top guide (sliding block at ceiling that runs on the ceiling rail —
11
+ * stabilizes the mast under acceleration)
12
+ * - carriage (horizontal block that slides up/down the mast at
13
+ * `state.carriageHeight`)
14
+ * - shuttle/fork attachment on the carriage (extends sideways into a cell
15
+ * to extract / insert a pallet)
16
+ * - status lamp on top of the base unit
17
+ *
18
+ * The base unit + top guide combo is what visually distinguishes a stacker
19
+ * crane from a forklift — the stacker is *constrained* to the aisle, riding
20
+ * rails top and bottom, and that constraint is the visual signature.
21
+ */
22
+ import * as THREE from 'three';
23
+ import { RealObjectGroup } from '@hatiolab/things-scene';
24
+ const MAST_COLOR = 0x4a5060;
25
+ const BASE_COLOR = 0x33394a;
26
+ const SHUTTLE_COLOR = 0x222233;
27
+ const RAIL_COLOR = 0x222222;
28
+ export class AsrsCrane3D extends RealObjectGroup {
29
+ build() {
30
+ super.build();
31
+ const { width, height, depth = 200 } = this.component.state;
32
+ const bodyColor = this.component.state.bodyColor || '#888';
33
+ const emissiveColor = this.component.state.lampEmissive || '#222222';
34
+ const status = this.component.state.status;
35
+ const lampIntensity = status && status !== 'idle' ? 1.5 : 0.2;
36
+ // Clamp carriageHeight to the available mast travel — keeps the carriage
37
+ // inside the shaft even if state.carriageHeight is stale (e.g. left over
38
+ // from an older height-scale convention).
39
+ const carriageRaw = this.component.state.carriageHeight ?? depth * 0.4;
40
+ const carriageHeight = Math.max(0, Math.min(carriageRaw, depth * 0.85));
41
+ const baseY = -depth / 2;
42
+ // Proportions
43
+ const baseH = depth * 0.10;
44
+ const topGuideH = depth * 0.05;
45
+ const mastH = depth - baseH - topGuideH;
46
+ const mastW = width * 0.35;
47
+ const mastD = height * 0.35;
48
+ const bodyMaterial = new THREE.MeshStandardMaterial({
49
+ color: bodyColor,
50
+ metalness: 0.4,
51
+ roughness: 0.5
52
+ });
53
+ const mastMaterial = new THREE.MeshStandardMaterial({
54
+ color: MAST_COLOR,
55
+ metalness: 0.85,
56
+ roughness: 0.3
57
+ });
58
+ const baseMaterial = new THREE.MeshStandardMaterial({
59
+ color: BASE_COLOR,
60
+ metalness: 0.7,
61
+ roughness: 0.4
62
+ });
63
+ const shuttleMaterial = new THREE.MeshStandardMaterial({
64
+ color: SHUTTLE_COLOR,
65
+ metalness: 0.85,
66
+ roughness: 0.3
67
+ });
68
+ // ── Floor rail (visible track under the base) ─────────────────────
69
+ const railH = baseH * 0.25;
70
+ const railGeo = new THREE.BoxGeometry(width * 1.15, railH, mastD * 0.4);
71
+ const railMesh = new THREE.Mesh(railGeo, new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 }));
72
+ railMesh.position.set(0, baseY + railH / 2, 0);
73
+ railMesh.receiveShadow = true;
74
+ this.object3d.add(railMesh);
75
+ // ── Base unit (motor housing on floor rail) ───────────────────────
76
+ const baseGeo = new THREE.BoxGeometry(width * 0.95, baseH, height * 0.85);
77
+ const baseMesh = new THREE.Mesh(baseGeo, baseMaterial);
78
+ baseMesh.position.set(0, baseY + railH + baseH / 2, 0);
79
+ baseMesh.castShadow = true;
80
+ baseMesh.receiveShadow = true;
81
+ this.object3d.add(baseMesh);
82
+ // Body color tint band on base unit (subtle status indication)
83
+ const tintH = baseH * 0.15;
84
+ const tintGeo = new THREE.BoxGeometry(width * 0.95, tintH, height * 0.85);
85
+ const tintMaterial = new THREE.MeshStandardMaterial({
86
+ color: bodyColor,
87
+ transparent: true,
88
+ opacity: 0.6,
89
+ metalness: 0.1,
90
+ roughness: 0.6
91
+ });
92
+ const tintMesh = new THREE.Mesh(tintGeo, tintMaterial);
93
+ tintMesh.position.set(0, baseY + railH + baseH - tintH / 2, 0);
94
+ this.object3d.add(tintMesh);
95
+ // ── Mast (vertical column from base top to ceiling) ───────────────
96
+ const mastY = baseY + railH + baseH + mastH / 2;
97
+ const mastGeo = new THREE.BoxGeometry(mastW, mastH, mastD);
98
+ const mastMesh = new THREE.Mesh(mastGeo, mastMaterial);
99
+ mastMesh.position.set(0, mastY, 0);
100
+ mastMesh.castShadow = true;
101
+ this.object3d.add(mastMesh);
102
+ // ── Carriage (horizontal block sliding on mast at carriageHeight) ─
103
+ const carriageW = width * 0.85;
104
+ const carriageH = baseH * 0.7;
105
+ const carriageD = mastD * 1.4;
106
+ const carriageY = baseY + railH + baseH + carriageHeight + carriageH / 2;
107
+ const carriageGeo = new THREE.BoxGeometry(carriageW, carriageH, carriageD);
108
+ const carriageMesh = new THREE.Mesh(carriageGeo, bodyMaterial);
109
+ carriageMesh.position.set(0, carriageY, 0);
110
+ carriageMesh.castShadow = true;
111
+ this.object3d.add(carriageMesh);
112
+ // ── Shuttle / fork attachment on carriage (extends sideways) ──────
113
+ const shuttleW = width * 1.05;
114
+ const shuttleH = carriageH * 0.5;
115
+ const shuttleD = carriageD * 0.6;
116
+ const shuttleGeo = new THREE.BoxGeometry(shuttleW, shuttleH, shuttleD);
117
+ const shuttleMesh = new THREE.Mesh(shuttleGeo, shuttleMaterial);
118
+ shuttleMesh.position.set(0, carriageY - carriageH / 2 - shuttleH / 2, 0);
119
+ shuttleMesh.castShadow = true;
120
+ this.object3d.add(shuttleMesh);
121
+ // ── Top guide (sliding block on ceiling rail) ─────────────────────
122
+ const topGuideGeo = new THREE.BoxGeometry(width * 0.7, topGuideH, height * 0.6);
123
+ const topGuideMesh = new THREE.Mesh(topGuideGeo, baseMaterial);
124
+ topGuideMesh.position.set(0, baseY + depth - topGuideH / 2, 0);
125
+ topGuideMesh.castShadow = true;
126
+ this.object3d.add(topGuideMesh);
127
+ // Ceiling rail (small visual cue at very top)
128
+ const ceilingRailGeo = new THREE.BoxGeometry(width * 1.15, topGuideH * 0.4, mastD * 0.4);
129
+ const ceilingRailMesh = new THREE.Mesh(ceilingRailGeo, new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 }));
130
+ ceilingRailMesh.position.set(0, baseY + depth - topGuideH * 0.2, 0);
131
+ this.object3d.add(ceilingRailMesh);
132
+ // ── Status lamp on top of base unit ───────────────────────────────
133
+ const lampR = Math.min(width, height) * 0.04;
134
+ const lampH = lampR * 1.5;
135
+ const lampMaterial = new THREE.MeshStandardMaterial({
136
+ color: emissiveColor,
137
+ emissive: emissiveColor,
138
+ emissiveIntensity: lampIntensity,
139
+ metalness: 0,
140
+ roughness: 0.3
141
+ });
142
+ const lampGeo = new THREE.CylinderGeometry(lampR, lampR * 0.85, lampH, 12);
143
+ const lampMesh = new THREE.Mesh(lampGeo, lampMaterial);
144
+ // Place lamp near the corner of the base, away from the mast
145
+ lampMesh.position.set(width * 0.3, baseY + railH + baseH + lampH / 2, height * 0.3);
146
+ this.object3d.add(lampMesh);
147
+ }
148
+ updateDimension() { }
149
+ onchange(after, before) {
150
+ if ('status' in after ||
151
+ 'bodyColor' in after ||
152
+ 'lampEmissive' in after ||
153
+ 'carriageHeight' in after ||
154
+ 'width' in after ||
155
+ 'height' in after ||
156
+ 'depth' in after) {
157
+ this.update();
158
+ return;
159
+ }
160
+ super.onchange(after, before);
161
+ }
162
+ updateAlpha() { }
163
+ }
164
+ //# sourceMappingURL=asrs-crane-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asrs-crane-3d.js","sourceRoot":"","sources":["../src/asrs-crane-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * AsrsCrane 3D — stacker crane for AS/RS aisles.\n *\n * LO-POLY but structurally complete. The signature parts:\n *\n * - tall vertical mast (the dominant element — runs floor → ceiling)\n * - base unit (motor housing at floor level, runs on the floor rail)\n * - top guide (sliding block at ceiling that runs on the ceiling rail —\n * stabilizes the mast under acceleration)\n * - carriage (horizontal block that slides up/down the mast at\n * `state.carriageHeight`)\n * - shuttle/fork attachment on the carriage (extends sideways into a cell\n * to extract / insert a pallet)\n * - status lamp on top of the base unit\n *\n * The base unit + top guide combo is what visually distinguishes a stacker\n * crane from a forklift — the stacker is *constrained* to the aisle, riding\n * rails top and bottom, and that constraint is the visual signature.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst MAST_COLOR = 0x4a5060\nconst BASE_COLOR = 0x33394a\nconst SHUTTLE_COLOR = 0x222233\nconst RAIL_COLOR = 0x222222\n\nexport class AsrsCrane3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 200 } = this.component.state\n const bodyColor = (this.component.state.bodyColor as string) || '#888'\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 // Clamp carriageHeight to the available mast travel — keeps the carriage\n // inside the shaft even if state.carriageHeight is stale (e.g. left over\n // from an older height-scale convention).\n const carriageRaw = (this.component.state.carriageHeight as number) ?? depth * 0.4\n const carriageHeight = Math.max(0, Math.min(carriageRaw, depth * 0.85))\n\n const baseY = -depth / 2\n\n // Proportions\n const baseH = depth * 0.10\n const topGuideH = depth * 0.05\n const mastH = depth - baseH - topGuideH\n const mastW = width * 0.35\n const mastD = height * 0.35\n\n const bodyMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.4,\n roughness: 0.5\n })\n const mastMaterial = new THREE.MeshStandardMaterial({\n color: MAST_COLOR,\n metalness: 0.85,\n roughness: 0.3\n })\n const baseMaterial = new THREE.MeshStandardMaterial({\n color: BASE_COLOR,\n metalness: 0.7,\n roughness: 0.4\n })\n const shuttleMaterial = new THREE.MeshStandardMaterial({\n color: SHUTTLE_COLOR,\n metalness: 0.85,\n roughness: 0.3\n })\n\n // ── Floor rail (visible track under the base) ─────────────────────\n const railH = baseH * 0.25\n const railGeo = new THREE.BoxGeometry(width * 1.15, railH, mastD * 0.4)\n const railMesh = new THREE.Mesh(\n railGeo,\n new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 })\n )\n railMesh.position.set(0, baseY + railH / 2, 0)\n railMesh.receiveShadow = true\n this.object3d.add(railMesh)\n\n // ── Base unit (motor housing on floor rail) ───────────────────────\n const baseGeo = new THREE.BoxGeometry(width * 0.95, baseH, height * 0.85)\n const baseMesh = new THREE.Mesh(baseGeo, baseMaterial)\n baseMesh.position.set(0, baseY + railH + baseH / 2, 0)\n baseMesh.castShadow = true\n baseMesh.receiveShadow = true\n this.object3d.add(baseMesh)\n\n // Body color tint band on base unit (subtle status indication)\n const tintH = baseH * 0.15\n const tintGeo = new THREE.BoxGeometry(width * 0.95, tintH, height * 0.85)\n const tintMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n transparent: true,\n opacity: 0.6,\n metalness: 0.1,\n roughness: 0.6\n })\n const tintMesh = new THREE.Mesh(tintGeo, tintMaterial)\n tintMesh.position.set(0, baseY + railH + baseH - tintH / 2, 0)\n this.object3d.add(tintMesh)\n\n // ── Mast (vertical column from base top to ceiling) ───────────────\n const mastY = baseY + railH + baseH + mastH / 2\n const mastGeo = new THREE.BoxGeometry(mastW, mastH, mastD)\n const mastMesh = new THREE.Mesh(mastGeo, mastMaterial)\n mastMesh.position.set(0, mastY, 0)\n mastMesh.castShadow = true\n this.object3d.add(mastMesh)\n\n // ── Carriage (horizontal block sliding on mast at carriageHeight) ─\n const carriageW = width * 0.85\n const carriageH = baseH * 0.7\n const carriageD = mastD * 1.4\n const carriageY = baseY + railH + baseH + carriageHeight + carriageH / 2\n const carriageGeo = new THREE.BoxGeometry(carriageW, carriageH, carriageD)\n const carriageMesh = new THREE.Mesh(carriageGeo, bodyMaterial)\n carriageMesh.position.set(0, carriageY, 0)\n carriageMesh.castShadow = true\n this.object3d.add(carriageMesh)\n\n // ── Shuttle / fork attachment on carriage (extends sideways) ──────\n const shuttleW = width * 1.05\n const shuttleH = carriageH * 0.5\n const shuttleD = carriageD * 0.6\n const shuttleGeo = new THREE.BoxGeometry(shuttleW, shuttleH, shuttleD)\n const shuttleMesh = new THREE.Mesh(shuttleGeo, shuttleMaterial)\n shuttleMesh.position.set(0, carriageY - carriageH / 2 - shuttleH / 2, 0)\n shuttleMesh.castShadow = true\n this.object3d.add(shuttleMesh)\n\n // ── Top guide (sliding block on ceiling rail) ─────────────────────\n const topGuideGeo = new THREE.BoxGeometry(width * 0.7, topGuideH, height * 0.6)\n const topGuideMesh = new THREE.Mesh(topGuideGeo, baseMaterial)\n topGuideMesh.position.set(0, baseY + depth - topGuideH / 2, 0)\n topGuideMesh.castShadow = true\n this.object3d.add(topGuideMesh)\n\n // Ceiling rail (small visual cue at very top)\n const ceilingRailGeo = new THREE.BoxGeometry(width * 1.15, topGuideH * 0.4, mastD * 0.4)\n const ceilingRailMesh = new THREE.Mesh(\n ceilingRailGeo,\n new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 })\n )\n ceilingRailMesh.position.set(0, baseY + depth - topGuideH * 0.2, 0)\n this.object3d.add(ceilingRailMesh)\n\n // ── Status lamp on top of base unit ───────────────────────────────\n const lampR = Math.min(width, height) * 0.04\n const lampH = lampR * 1.5\n const lampMaterial = new THREE.MeshStandardMaterial({\n color: emissiveColor,\n emissive: emissiveColor,\n emissiveIntensity: lampIntensity,\n metalness: 0,\n roughness: 0.3\n })\n const lampGeo = new THREE.CylinderGeometry(lampR, lampR * 0.85, lampH, 12)\n const lampMesh = new THREE.Mesh(lampGeo, lampMaterial)\n // Place lamp near the corner of the base, away from the mast\n lampMesh.position.set(width * 0.3, baseY + railH + baseH + lampH / 2, height * 0.3)\n this.object3d.add(lampMesh)\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 'carriageHeight' 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"]}
@@ -0,0 +1,47 @@
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
+ * AsrsCrane status — the operating state of a stacker crane in an AS/RS aisle.
5
+ *
6
+ * - `idle` — parked at home position
7
+ * - `moving` — translating along the aisle (horizontal) or along the
8
+ * mast (vertical); the two motions are typically combined
9
+ * - `loading` — extracting a pallet from a rack cell
10
+ * - `unloading` — placing a pallet into a rack cell
11
+ * - `error` — fault / e-stop / collision warning
12
+ */
13
+ export type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error';
14
+ declare const Base: typeof Component;
15
+ /**
16
+ * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an
17
+ * AS/RS, moving cargo between the load port and the rack cells.
18
+ *
19
+ * Structure: a tall vertical mast that translates along a floor + ceiling
20
+ * rail (the aisle), with a carriage that slides up/down the mast carrying a
21
+ * shuttle / forks. The whole assembly's footprint is narrow (mast width)
22
+ * but its visual height is full ceiling — by far the tallest single
23
+ * component in a typical scene.
24
+ *
25
+ * Currently Shape-based (no children). The carrier the crane is *currently
26
+ * carrying* is best modeled via data binding (a `currentCarrier` data field
27
+ * on the crane → looked up to a Pallet/Box elsewhere in the scene), as in
28
+ * fmsim's CarrierManager pattern. Adding the carrier as a child would mix
29
+ * static placement with the dynamic data-driven flow we deliberately keep
30
+ * separate (see Phase A4 commit notes).
31
+ */
32
+ export default class AsrsCrane extends Base {
33
+ static legends: Record<string, LegendBinding>;
34
+ static placement: PlacementArchetype;
35
+ static align: Alignment;
36
+ static defaultDepth: (h: Heights) => number;
37
+ get nature(): ComponentNature;
38
+ get anchors(): never[];
39
+ /**
40
+ * 2D — top-down rectangle showing the crane's footprint along the aisle.
41
+ * The crane is much taller than wide, so the 2D mark is small.
42
+ */
43
+ render(ctx: CanvasRenderingContext2D): void;
44
+ get fillStyle(): string;
45
+ buildRealObject(): RealObject | undefined;
46
+ }
47
+ export {};
@@ -0,0 +1,104 @@
1
+ import { __decorate } from "tslib";
2
+ /*
3
+ * Copyright © HatioLab Inc. All rights reserved.
4
+ */
5
+ import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
6
+ import { Legendable, Placeable } from '@operato/scene-base';
7
+ import { AsrsCrane3D } from './asrs-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: 'number',
45
+ label: 'carriage-height',
46
+ name: 'carriageHeight',
47
+ placeholder: 'mm — height of carriage on mast'
48
+ }
49
+ ],
50
+ help: 'scene/component/asrs-crane'
51
+ };
52
+ const Base = Legendable(Placeable(RectPath(Shape)));
53
+ /**
54
+ * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an
55
+ * AS/RS, moving cargo between the load port and the rack cells.
56
+ *
57
+ * Structure: a tall vertical mast that translates along a floor + ceiling
58
+ * rail (the aisle), with a carriage that slides up/down the mast carrying a
59
+ * shuttle / forks. The whole assembly's footprint is narrow (mast width)
60
+ * but its visual height is full ceiling — by far the tallest single
61
+ * component in a typical scene.
62
+ *
63
+ * Currently Shape-based (no children). The carrier the crane is *currently
64
+ * carrying* is best modeled via data binding (a `currentCarrier` data field
65
+ * on the crane → looked up to a Pallet/Box elsewhere in the scene), as in
66
+ * fmsim's CarrierManager pattern. Adding the carrier as a child would mix
67
+ * static placement with the dynamic data-driven flow we deliberately keep
68
+ * separate (see Phase A4 commit notes).
69
+ */
70
+ let AsrsCrane = class AsrsCrane extends Base {
71
+ static legends = {
72
+ bodyColor: { from: 'status', legend: BODY_LEGEND },
73
+ lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
74
+ };
75
+ static placement = 'floor';
76
+ static align = 'bottom';
77
+ static defaultDepth = (h) => h.ceiling - h.floor;
78
+ get nature() {
79
+ return NATURE;
80
+ }
81
+ get anchors() {
82
+ return [];
83
+ }
84
+ /**
85
+ * 2D — top-down rectangle showing the crane's footprint along the aisle.
86
+ * The crane is much taller than wide, so the 2D mark is small.
87
+ */
88
+ render(ctx) {
89
+ const { width, height, left, top } = this.state;
90
+ ctx.beginPath();
91
+ ctx.rect(left, top, width, height);
92
+ }
93
+ get fillStyle() {
94
+ return this.state.bodyColor || '#888';
95
+ }
96
+ buildRealObject() {
97
+ return new AsrsCrane3D(this);
98
+ }
99
+ };
100
+ AsrsCrane = __decorate([
101
+ sceneComponent('asrs-crane')
102
+ ], AsrsCrane);
103
+ export default AsrsCrane;
104
+ //# sourceMappingURL=asrs-crane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asrs-crane.js","sourceRoot":"","sources":["../src/asrs-crane.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, RealObject, RectPath, Shape, 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 { AsrsCrane3D } from './asrs-crane-3d.js'\n\n/**\n * AsrsCrane status — the operating state of a stacker crane in an AS/RS aisle.\n *\n * - `idle` — parked at home position\n * - `moving` — translating along the aisle (horizontal) or along the\n * mast (vertical); the two motions are typically combined\n * - `loading` — extracting a pallet from a rack cell\n * - `unloading` — placing a pallet into a rack cell\n * - `error` — fault / e-stop / collision warning\n */\nexport type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'\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: 'number',\n label: 'carriage-height',\n name: 'carriageHeight',\n placeholder: 'mm — height of carriage on mast'\n }\n ],\n help: 'scene/component/asrs-crane'\n}\n\nconst Base = Legendable(Placeable(RectPath(Shape))) as unknown as typeof Component\n\n/**\n * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an\n * AS/RS, moving cargo between the load port and the rack cells.\n *\n * Structure: a tall vertical mast that translates along a floor + ceiling\n * rail (the aisle), with a carriage that slides up/down the mast carrying a\n * shuttle / forks. The whole assembly's footprint is narrow (mast width)\n * but its visual height is full ceiling — by far the tallest single\n * component in a typical scene.\n *\n * Currently Shape-based (no children). The carrier the crane is *currently\n * carrying* is best modeled via data binding (a `currentCarrier` data field\n * on the crane → looked up to a Pallet/Box elsewhere in the scene), as in\n * fmsim's CarrierManager pattern. Adding the carrier as a child would mix\n * static placement with the dynamic data-driven flow we deliberately keep\n * separate (see Phase A4 commit notes).\n */\n@sceneComponent('asrs-crane')\nexport default class AsrsCrane 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 static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.ceiling - h.floor\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — top-down rectangle showing the crane's footprint along the aisle.\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 ctx.beginPath()\n ctx.rect(left, top, width, height)\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#888'\n }\n\n buildRealObject(): RealObject | undefined {\n return new AsrsCrane3D(this as any)\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class AsrsRack3D extends RealObjectGroup {
3
+ build(): void;
4
+ updateDimension(): void;
5
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
6
+ updateAlpha(): void;
7
+ }
@@ -0,0 +1,129 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * AsrsRack 3D — multi-level high-bay storage rack.
5
+ *
6
+ * LO-POLY but visually unambiguous as a rack. The signature parts:
7
+ *
8
+ * - 4 corner uprights (vertical posts running floor → top)
9
+ * - intermediate uprights between bays (one between each adjacent bay pair)
10
+ * - horizontal beams at each level on both front and back faces (defining
11
+ * the cell decks)
12
+ * - diagonal cross-bracing on the back face (the "X" pattern that says
13
+ * this is a load-bearing storage rack, not just a generic frame)
14
+ *
15
+ * No floor / ceiling panels — the rack is open by design (cells are accessed
16
+ * by the stacker crane from the front/aisle side).
17
+ *
18
+ * Cargo (pallets, boxes) added as children render at their own z position.
19
+ * The rack itself is purely structural geometry.
20
+ */
21
+ import * as THREE from 'three';
22
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
23
+ import { RealObjectGroup } from '@hatiolab/things-scene';
24
+ const POST_COLOR = 0x6a7080;
25
+ const BEAM_COLOR = 0x556070;
26
+ const BRACE_COLOR = 0x556070;
27
+ export class AsrsRack3D extends RealObjectGroup {
28
+ build() {
29
+ super.build();
30
+ const { width, height, depth = 3000 } = this.component.state;
31
+ const levels = Math.max(1, Math.floor(this.component.state.levels || 4));
32
+ const bays = Math.max(1, Math.floor(this.component.state.bays || 5));
33
+ const baseY = -depth / 2;
34
+ const postW = Math.min(width / bays, height) * 0.06;
35
+ const beamH = depth * 0.025;
36
+ const braceT = postW * 0.6;
37
+ const postMaterial = new THREE.MeshStandardMaterial({
38
+ color: POST_COLOR,
39
+ metalness: 0.7,
40
+ roughness: 0.4
41
+ });
42
+ const beamMaterial = new THREE.MeshStandardMaterial({
43
+ color: BEAM_COLOR,
44
+ metalness: 0.7,
45
+ roughness: 0.4
46
+ });
47
+ const braceMaterial = new THREE.MeshStandardMaterial({
48
+ color: BRACE_COLOR,
49
+ metalness: 0.7,
50
+ roughness: 0.4
51
+ });
52
+ // ── Uprights (vertical posts at every bay boundary) ──────────────
53
+ // bays + 1 vertical positions; for each, one front post + one back post.
54
+ const postGeos = [];
55
+ for (let i = 0; i <= bays; i++) {
56
+ const xFrac = i / bays - 0.5;
57
+ const x = xFrac * width;
58
+ // Front + back posts
59
+ for (const zSign of [-1, 1]) {
60
+ const post = new THREE.BoxGeometry(postW, depth, postW);
61
+ post.translate(x, 0, zSign * (height / 2 - postW / 2));
62
+ postGeos.push(post);
63
+ }
64
+ }
65
+ const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial);
66
+ postMesh.castShadow = true;
67
+ postMesh.receiveShadow = true;
68
+ this.object3d.add(postMesh);
69
+ // ── Horizontal beams (front + back faces at each level) ──────────
70
+ // levels + 1 vertical positions (level 0 = ground, level N = top).
71
+ const beamGeos = [];
72
+ for (let lv = 0; lv <= levels; lv++) {
73
+ const yFrac = lv / levels;
74
+ const y = baseY + yFrac * depth - beamH / 2 + (lv === 0 ? beamH : 0);
75
+ for (const zSign of [-1, 1]) {
76
+ const beam = new THREE.BoxGeometry(width, beamH, beamH);
77
+ beam.translate(0, y, zSign * (height / 2 - beamH / 2));
78
+ beamGeos.push(beam);
79
+ }
80
+ }
81
+ const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos), beamMaterial);
82
+ beamMesh.castShadow = true;
83
+ beamMesh.receiveShadow = true;
84
+ this.object3d.add(beamMesh);
85
+ // ── Diagonal cross-bracing on the back face (the "X" pattern) ────
86
+ // Two diagonals per level — "/" and "\" — making an X across each
87
+ // bay-tall cell. Visual signature of a load-bearing rack.
88
+ const braceGeos = [];
89
+ const cellW = width / bays;
90
+ const cellH = depth / levels;
91
+ const braceLen = Math.sqrt(cellW * cellW + cellH * cellH);
92
+ const braceAngle = Math.atan2(cellH, cellW);
93
+ const backZ = height / 2 - postW * 0.6;
94
+ for (let bay = 0; bay < bays; bay++) {
95
+ // Brace only every other bay to keep things visually open
96
+ if (bay % 2 !== 0)
97
+ continue;
98
+ const cellCenterX = (bay - bays / 2 + 0.5) * cellW;
99
+ for (let lv = 0; lv < levels; lv++) {
100
+ const cellCenterY = baseY + (lv + 0.5) * cellH;
101
+ for (const sign of [-1, 1]) {
102
+ const brace = new THREE.BoxGeometry(braceLen, braceT, braceT);
103
+ brace.rotateZ(sign * braceAngle);
104
+ brace.translate(cellCenterX, cellCenterY, backZ);
105
+ braceGeos.push(brace);
106
+ }
107
+ }
108
+ }
109
+ if (braceGeos.length > 0) {
110
+ const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos), braceMaterial);
111
+ braceMesh.castShadow = true;
112
+ this.object3d.add(braceMesh);
113
+ }
114
+ }
115
+ updateDimension() { }
116
+ onchange(after, before) {
117
+ if ('levels' in after ||
118
+ 'bays' in after ||
119
+ 'width' in after ||
120
+ 'height' in after ||
121
+ 'depth' in after) {
122
+ this.update();
123
+ return;
124
+ }
125
+ super.onchange(after, before);
126
+ }
127
+ updateAlpha() { }
128
+ }
129
+ //# sourceMappingURL=asrs-rack-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asrs-rack-3d.js","sourceRoot":"","sources":["../src/asrs-rack-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * AsrsRack 3D — multi-level high-bay storage rack.\n *\n * LO-POLY but visually unambiguous as a rack. The signature parts:\n *\n * - 4 corner uprights (vertical posts running floor → top)\n * - intermediate uprights between bays (one between each adjacent bay pair)\n * - horizontal beams at each level on both front and back faces (defining\n * the cell decks)\n * - diagonal cross-bracing on the back face (the \"X\" pattern that says\n * this is a load-bearing storage rack, not just a generic frame)\n *\n * No floor / ceiling panels — the rack is open by design (cells are accessed\n * by the stacker crane from the front/aisle side).\n *\n * Cargo (pallets, boxes) added as children render at their own z position.\n * The rack itself is purely structural geometry.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst POST_COLOR = 0x6a7080\nconst BEAM_COLOR = 0x556070\nconst BRACE_COLOR = 0x556070\n\nexport class AsrsRack3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 3000 } = this.component.state\n const levels = Math.max(1, Math.floor((this.component.state.levels as number) || 4))\n const bays = Math.max(1, Math.floor((this.component.state.bays as number) || 5))\n\n const baseY = -depth / 2\n const postW = Math.min(width / bays, height) * 0.06\n const beamH = depth * 0.025\n const braceT = postW * 0.6\n\n const postMaterial = new THREE.MeshStandardMaterial({\n color: POST_COLOR,\n metalness: 0.7,\n roughness: 0.4\n })\n const beamMaterial = new THREE.MeshStandardMaterial({\n color: BEAM_COLOR,\n metalness: 0.7,\n roughness: 0.4\n })\n const braceMaterial = new THREE.MeshStandardMaterial({\n color: BRACE_COLOR,\n metalness: 0.7,\n roughness: 0.4\n })\n\n // ── Uprights (vertical posts at every bay boundary) ──────────────\n // bays + 1 vertical positions; for each, one front post + one back post.\n const postGeos: THREE.BufferGeometry[] = []\n for (let i = 0; i <= bays; i++) {\n const xFrac = i / bays - 0.5\n const x = xFrac * width\n // Front + back posts\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(x, 0, zSign * (height / 2 - postW / 2))\n postGeos.push(post)\n }\n }\n const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial)\n postMesh.castShadow = true\n postMesh.receiveShadow = true\n this.object3d.add(postMesh)\n\n // ── Horizontal beams (front + back faces at each level) ──────────\n // levels + 1 vertical positions (level 0 = ground, level N = top).\n const beamGeos: THREE.BufferGeometry[] = []\n for (let lv = 0; lv <= levels; lv++) {\n const yFrac = lv / levels\n const y = baseY + yFrac * depth - beamH / 2 + (lv === 0 ? beamH : 0)\n\n for (const zSign of [-1, 1]) {\n const beam = new THREE.BoxGeometry(width, beamH, beamH)\n beam.translate(0, y, zSign * (height / 2 - beamH / 2))\n beamGeos.push(beam)\n }\n }\n const beamMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(beamGeos), beamMaterial)\n beamMesh.castShadow = true\n beamMesh.receiveShadow = true\n this.object3d.add(beamMesh)\n\n // ── Diagonal cross-bracing on the back face (the \"X\" pattern) ────\n // Two diagonals per level — \"/\" and \"\\\" — making an X across each\n // bay-tall cell. Visual signature of a load-bearing rack.\n const braceGeos: THREE.BufferGeometry[] = []\n const cellW = width / bays\n const cellH = depth / levels\n const braceLen = Math.sqrt(cellW * cellW + cellH * cellH)\n const braceAngle = Math.atan2(cellH, cellW)\n const backZ = height / 2 - postW * 0.6\n\n for (let bay = 0; bay < bays; bay++) {\n // Brace only every other bay to keep things visually open\n if (bay % 2 !== 0) continue\n\n const cellCenterX = (bay - bays / 2 + 0.5) * cellW\n\n for (let lv = 0; lv < levels; lv++) {\n const cellCenterY = baseY + (lv + 0.5) * cellH\n\n for (const sign of [-1, 1]) {\n const brace = new THREE.BoxGeometry(braceLen, braceT, braceT)\n brace.rotateZ(sign * braceAngle)\n brace.translate(cellCenterX, cellCenterY, backZ)\n braceGeos.push(brace)\n }\n }\n }\n if (braceGeos.length > 0) {\n const braceMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(braceGeos), braceMaterial)\n braceMesh.castShadow = true\n this.object3d.add(braceMesh)\n }\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'levels' in after ||\n 'bays' 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"]}