@operato/scene-storage 10.0.0-beta.33 → 10.0.0-beta.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/crane-3d.d.ts +14 -0
  3. package/dist/crane-3d.js +238 -0
  4. package/dist/crane-3d.js.map +1 -0
  5. package/dist/crane.d.ts +157 -0
  6. package/dist/crane.js +440 -0
  7. package/dist/crane.js.map +1 -0
  8. package/dist/index.d.ts +13 -6
  9. package/dist/index.js +9 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/mobile-storage-rack.d.ts +17 -0
  12. package/dist/mobile-storage-rack.js +55 -0
  13. package/dist/mobile-storage-rack.js.map +1 -0
  14. package/dist/rack-column.d.ts +35 -0
  15. package/dist/rack-column.js +258 -0
  16. package/dist/rack-column.js.map +1 -0
  17. package/dist/rack-grid-3d.d.ts +13 -0
  18. package/dist/rack-grid-3d.js +94 -0
  19. package/dist/rack-grid-3d.js.map +1 -0
  20. package/dist/rack-grid-cell.d.ts +341 -0
  21. package/dist/rack-grid-cell.js +321 -0
  22. package/dist/rack-grid-cell.js.map +1 -0
  23. package/dist/rack-grid-helpers.d.ts +28 -0
  24. package/dist/rack-grid-helpers.js +71 -0
  25. package/dist/rack-grid-helpers.js.map +1 -0
  26. package/dist/rack-grid-location.d.ts +37 -0
  27. package/dist/rack-grid-location.js +227 -0
  28. package/dist/rack-grid-location.js.map +1 -0
  29. package/dist/rack-grid.d.ts +80 -0
  30. package/dist/rack-grid.js +829 -0
  31. package/dist/rack-grid.js.map +1 -0
  32. package/dist/stock.d.ts +78 -0
  33. package/dist/stock.js +333 -0
  34. package/dist/stock.js.map +1 -0
  35. package/dist/{rack-cell-3d.d.ts → storage-cell-3d.d.ts} +1 -1
  36. package/dist/{rack-cell-3d.js → storage-cell-3d.js} +3 -3
  37. package/dist/storage-cell-3d.js.map +1 -0
  38. package/dist/{rack-cell.d.ts → storage-cell.d.ts} +12 -6
  39. package/dist/{rack-cell.js → storage-cell.js} +9 -9
  40. package/dist/storage-cell.js.map +1 -0
  41. package/dist/{asrs-rack-3d.d.ts → storage-rack-3d.d.ts} +1 -1
  42. package/dist/{asrs-rack-3d.js → storage-rack-3d.js} +4 -4
  43. package/dist/storage-rack-3d.js.map +1 -0
  44. package/dist/{asrs-rack.d.ts → storage-rack.d.ts} +22 -16
  45. package/dist/{asrs-rack.js → storage-rack.js} +32 -26
  46. package/dist/storage-rack.js.map +1 -0
  47. package/dist/templates/index.d.ts +60 -0
  48. package/dist/templates/index.js +59 -17
  49. package/dist/templates/index.js.map +1 -1
  50. package/package.json +2 -2
  51. package/src/crane-3d.ts +273 -0
  52. package/src/crane.ts +538 -0
  53. package/src/index.ts +13 -6
  54. package/src/mobile-storage-rack.ts +56 -0
  55. package/src/rack-column.ts +340 -0
  56. package/src/rack-grid-3d.ts +128 -0
  57. package/src/rack-grid-cell.ts +404 -0
  58. package/src/rack-grid-helpers.ts +77 -0
  59. package/src/rack-grid-location.ts +286 -0
  60. package/src/rack-grid.ts +994 -0
  61. package/src/stock.ts +426 -0
  62. package/src/{rack-cell-3d.ts → storage-cell-3d.ts} +2 -2
  63. package/src/{rack-cell.ts → storage-cell.ts} +19 -13
  64. package/src/{asrs-rack-3d.ts → storage-rack-3d.ts} +3 -3
  65. package/src/{asrs-rack.ts → storage-rack.ts} +31 -25
  66. package/src/templates/index.ts +59 -17
  67. package/test/test-rack-grid-crane.ts +212 -0
  68. package/test/test-rack-grid.ts +77 -0
  69. package/test/{test-asrs-crane.ts → test-storage-rack-crane.ts} +8 -8
  70. package/translations/en.json +55 -7
  71. package/translations/ja.json +52 -4
  72. package/translations/ko.json +52 -4
  73. package/translations/ms.json +55 -7
  74. package/translations/zh.json +52 -4
  75. package/tsconfig.tsbuildinfo +1 -1
  76. package/dist/asrs-crane-3d.d.ts +0 -17
  77. package/dist/asrs-crane-3d.js +0 -181
  78. package/dist/asrs-crane-3d.js.map +0 -1
  79. package/dist/asrs-crane.d.ts +0 -98
  80. package/dist/asrs-crane.js +0 -216
  81. package/dist/asrs-crane.js.map +0 -1
  82. package/dist/asrs-rack-3d.js.map +0 -1
  83. package/dist/asrs-rack.js.map +0 -1
  84. package/dist/rack-cell-3d.js.map +0 -1
  85. package/dist/rack-cell.js.map +0 -1
  86. package/src/asrs-crane-3d.ts +0 -211
  87. package/src/asrs-crane.ts +0 -275
  88. /package/icons/{asrs-crane.png → crane.png} +0 -0
  89. /package/icons/{asrs-rack.png → storage-rack.png} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"rack-cell-3d.js","sourceRoot":"","sources":["../src/rack-cell-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell 3D — invisible anchor group positioned at the cell's location\n * within the parent rack's 3D coordinate space.\n *\n * RackCell has no geometry of its own. Its sole 3D purpose is to provide\n * an Object3D that carriers can be attached to (via Three.js `.attach()`),\n * placed at the exact cell position within the rack. The position is derived\n * from the parent AsrsRack's CellMap (by cellId), not from 2D state fields —\n * rack cells occupy 3D levels that have no 2D analogue.\n *\n * updateTransform() override: things-scene's standard updateTransform\n * reads `component.center` (2D) and flattens it to 3D. For rack cells this\n * is wrong — we need the 3D cell position (bay x, level y, row z) from the\n * parent rack. So we override and read it directly from the CellMap.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class RackCell3D extends RealObjectGroup {\n build() {\n super.build()\n this._repositionFromCellMap()\n }\n\n updateDimension() {\n // intentional no-op — size comes from the cell definition, not state\n }\n\n updateTransform() {\n this._repositionFromCellMap()\n }\n\n updateAlpha() {\n // invisible — no materials to update\n }\n\n /**\n * Position this group at the cell's localPosition within the rack's 3D space.\n *\n * CellMap.grid() places cell origins at:\n * x = b * bayWidth, y = l * levelHeight, z = r * rowDepth\n * (starting from 0,0,0 at the rack's bottom-left-front corner).\n *\n * The rack's object3d is centered: X spans [-width/2, +width/2],\n * Y spans [-depth/2, +depth/2], Z spans [-height/2, +height/2].\n *\n * So the cell centre in rack-local 3D space:\n * x3d = cellPos.x + bayWidth/2 - width/2\n * y3d = cellPos.y + levelHeight/2 - rackDepth/2\n * z3d = cellPos.z + rowDepth/2 - rackHeight/2\n */\n _repositionFromCellMap() {\n const rack = (this.component as any).parent\n if (!rack?.cellMap) return\n\n const cellId = this.component.state.cellId as string | undefined\n if (!cellId) return\n\n const cell = rack.cellMap.findById(cellId)\n if (!cell) return\n\n const rs = rack.state as any\n const rackWidth = (rs?.width as number) || 1000\n const rackDepth = (rs?.depth as number) || 3000 // Y dimension (floor→ceiling)\n const rackHeight = (rs?.height as number) || 600 // Z dimension (front→back, 2D height)\n const bays = Math.max(1, Math.floor(rs?.bays || 5))\n const levels = Math.max(1, Math.floor(rs?.levels || 4))\n const rows = 1\n\n const bayWidth = rackWidth / bays\n const levelHeight = rackDepth / levels\n const rowDepth = rackHeight / rows\n\n const x3d = cell.localPosition.x + bayWidth / 2 - rackWidth / 2\n const y3d = cell.localPosition.y + levelHeight / 2 - rackDepth / 2\n const z3d = cell.localPosition.z + rowDepth / 2 - rackHeight / 2\n\n this.object3d.position.set(x3d, y3d, z3d)\n\n // Optionally visualise cells in debug mode (outline box, very faint)\n if (process.env.NODE_ENV !== 'production' && (rack.state as any)?.debugCells) {\n this._addDebugBox(bayWidth, levelHeight, rowDepth)\n }\n }\n\n private _debugBox?: THREE.LineSegments\n\n private _addDebugBox(w: number, h: number, d: number) {\n if (this._debugBox) return\n const geo = new THREE.BoxGeometry(w * 0.98, h * 0.98, d * 0.98)\n const edges = new THREE.EdgesGeometry(geo)\n this._debugBox = new THREE.LineSegments(\n edges,\n new THREE.LineBasicMaterial({ color: 0x00ff88, transparent: true, opacity: 0.2 })\n )\n this.object3d.add(this._debugBox)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rack-cell.js","sourceRoot":"","sources":["../src/rack-cell.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * RackCell — a single storage slot within an AsrsRack.\n *\n * A RackCell is a virtual component: it occupies a specific (bay, row, level)\n * coordinate within the parent rack and acts as a CarrierHolder for one carrier\n * (or several, depending on `cellType`).\n *\n * The crane (AsrsCrane) navigates toward a RackCell as its `place()` destination —\n * because the rack cell is a discrete component, Mover.moveTo() can target it\n * directly and arrive at exactly the right bay × level position.\n *\n * Visual: invisible in 2D (no visible 2D footprint — rack cells don't make\n * sense as 2D top-down boxes). In 3D, each cell is an invisible Group\n * positioned within the rack's coordinate space (RackCell3D handles\n * this via updateTransform override).\n *\n * Lifecycle: AsrsRack._buildCells() instantiates RackCell children.\n * Do not create RackCell components manually — they are managed by the rack.\n *\n * Domain aliases:\n * cell.store(carrier) ← cell.receive(carrier)\n * cell.retrieve(carrier, target) ← cell.dispatch(carrier, target)\n */\n\nimport {\n Component,\n ComponentNature,\n ContainerAbstract,\n RealObject,\n TRANSFER_SLOT_KEY,\n sceneComponent\n} from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport { CarrierHolder, type AttachFrame } from '@operato/scene-base'\n\nimport { RackCell3D } from './rack-cell-3d.js'\n\n/**\n * How many carriers a cell can hold simultaneously.\n * - single: exactly 1 (typical pallet bay)\n * - multi: small stack (up to 4, e.g. a multi-deep tray)\n * - bulk: unlimited (e.g. a floor area measured in slots)\n */\nexport type RackCellType = 'single' | 'multi' | 'bulk'\n\n/** RackCell 컴포넌트 state */\nexport interface RackCellState extends State {\n // ── 식별 ──\n cellId?: string\n cellType?: RackCellType\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: false,\n rotatable: false,\n properties: [\n {\n type: 'string',\n label: 'cell-id',\n name: 'cellId',\n placeholder: 'e.g. 0-0-0'\n },\n {\n type: 'select',\n label: 'cell-type',\n name: 'cellType',\n property: {\n options: [\n { display: 'Single', value: 'single' },\n { display: 'Multi', value: 'multi' },\n { display: 'Bulk', value: 'bulk' }\n ]\n }\n }\n ],\n help: 'scene/component/rack-cell'\n}\n\n/**\n * RackCell — single-slot storage cell inside an AsrsRack.\n *\n * Mixin chain: CarrierHolder(ContainerAbstract)\n * - CarrierHolder: publishes attachPointFor(), gates containable() to Carriables\n * - ContainerAbstract: manages child carrier components\n *\n * No Placeable mixin — RackCell3D self-positions from the parent rack's\n * CellMap (via updateTransform override), bypassing things-scene's standard\n * 2D→3D coordinate mapping which cannot express 3D levels.\n */\n@sceneComponent('rack-cell')\nexport default class RackCell extends CarrierHolder(ContainerAbstract) {\n declare state: RackCellState\n\n // ── Identification ────────────────────────────────────────────────────────\n\n get cellId(): string {\n return this.state.cellId ?? ''\n }\n\n get cellType(): RackCellType {\n return this.state.cellType ?? 'single'\n }\n\n /** Maximum carrier count for this cell based on cellType. */\n get capacity(): number {\n switch (this.cellType) {\n case 'single': return 1\n case 'multi': return 4\n case 'bulk': return Infinity\n }\n }\n\n // ── Interface ─────────────────────────────────────────────────────────────\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors(): [] {\n return []\n }\n\n // ── Transfer protocol ─────────────────────────────────────────────────────\n\n /** True when fewer carriers are currently held than capacity. */\n canReceive(_component?: any): boolean {\n const occupied = (this.components as Component[] | undefined)?.length ?? 0\n return occupied < this.capacity\n }\n\n /**\n * Accept a carrier into this cell.\n * Sets TRANSFER_SLOT_KEY = cellId on the carrier, then reparents.\n * Fires 'transfer-received' so monitors can react.\n */\n async receive(carrier: any, options: any = {}): Promise<void> {\n if (!this.canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'no-slot'\n })\n return\n }\n carrier[TRANSFER_SLOT_KEY] = this.cellId\n this.reparent(carrier, options)\n this.trigger('transfer-received', {\n type: 'transfer-received',\n component: carrier,\n container: this,\n slotId: this.cellId\n })\n }\n\n /**\n * Release a carrier from this cell to `target`.\n * Delegates to `target.receive()` if available, otherwise `target.reparent()`.\n */\n async dispatch(carrier: any, target: any, options: any = {}): Promise<void> {\n if (target?.canReceive && !target.canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'target-full'\n })\n return\n }\n delete carrier[TRANSFER_SLOT_KEY]\n if (typeof target?.receive === 'function') {\n await target.receive(carrier, options)\n } else {\n ;(target as any).reparent?.(carrier, options)\n }\n this.trigger('transfer-dispatched', {\n type: 'transfer-dispatched',\n component: carrier,\n container: this,\n target\n })\n }\n\n // ── Domain aliases ────────────────────────────────────────────────────────\n\n /** Alias for receive() — semantic sugar for the storage domain. */\n store(carrier: any, options?: any): Promise<void> {\n return this.receive(carrier, options)\n }\n\n /** Alias for dispatch() — semantic sugar for the storage domain. */\n retrieve(carrier: any, target: any, options?: any): Promise<void> {\n return this.dispatch(carrier, target, options)\n }\n\n // ── 3D attach frame ───────────────────────────────────────────────────────\n\n /**\n * Return the 3D attach frame for carriers placed in this cell.\n * Carriers are lifted by their own halfDepth so the bottom face\n * rests at the cell's Y-center (which is levelHeight/2 above the beam).\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const root = this._realObject?.object3d\n if (!root) return null\n const carrierDepth = resolveCarrierDepth(carrier)\n return {\n attach: root,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n // ── 2D rendering ──────────────────────────────────────────────────────────\n\n /** RackCell has no 2D visual — the rack draws its own structure. */\n render(_ctx: CanvasRenderingContext2D) {\n // intentional no-op\n }\n\n // ── 3D ───────────────────────────────────────────────────────────────────\n\n buildRealObject(): RealObject | undefined {\n return new RackCell3D(this)\n }\n}\n\nfunction resolveCarrierDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n"]}
@@ -1,211 +0,0 @@
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
-
23
- import * as THREE from 'three'
24
- import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
25
- import { RealObjectGroup } from '@hatiolab/things-scene'
26
-
27
- const MAST_COLOR = 0x4a5060
28
- const BASE_COLOR = 0x33394a
29
- const SHUTTLE_COLOR = 0x222233
30
- const RAIL_COLOR = 0x222222
31
-
32
- export class AsrsCrane3D extends RealObjectGroup {
33
- build() {
34
- super.build()
35
-
36
- const { width, height, depth = 200 } = this.component.state
37
- const bodyColor = (this.component.state.bodyColor as string) || '#888'
38
- const emissiveColor = (this.component.state.lampEmissive as string) || '#222222'
39
- const status = this.component.state.status
40
- const lampIntensity = status && status !== 'idle' ? 1.5 : 0.2
41
- // Clamp carriageHeight to the available mast travel — keeps the carriage
42
- // inside the shaft even if state.carriageHeight is stale (e.g. left over
43
- // from an older height-scale convention).
44
- const carriageRaw = (this.component.state.carriageHeight as number) ?? depth * 0.4
45
- const carriageHeight = Math.max(0, Math.min(carriageRaw, depth * 0.85))
46
-
47
- const baseY = -depth / 2
48
-
49
- // Proportions
50
- const baseH = depth * 0.10
51
- const topGuideH = depth * 0.05
52
- const mastH = depth - baseH - topGuideH
53
- const mastW = width * 0.35
54
- const mastD = height * 0.35
55
-
56
- const bodyMaterial = new THREE.MeshStandardMaterial({
57
- color: bodyColor,
58
- metalness: 0.4,
59
- roughness: 0.5
60
- })
61
- const mastMaterial = new THREE.MeshStandardMaterial({
62
- color: MAST_COLOR,
63
- metalness: 0.85,
64
- roughness: 0.3
65
- })
66
- const baseMaterial = new THREE.MeshStandardMaterial({
67
- color: BASE_COLOR,
68
- metalness: 0.7,
69
- roughness: 0.4
70
- })
71
- const shuttleMaterial = new THREE.MeshStandardMaterial({
72
- color: SHUTTLE_COLOR,
73
- metalness: 0.85,
74
- roughness: 0.3
75
- })
76
-
77
- // ── Floor rail (visible track under the base) ─────────────────────
78
- const railH = baseH * 0.25
79
- const railGeo = new THREE.BoxGeometry(width * 1.15, railH, mastD * 0.4)
80
- const railMesh = new THREE.Mesh(
81
- railGeo,
82
- new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 })
83
- )
84
- railMesh.position.set(0, baseY + railH / 2, 0)
85
- railMesh.receiveShadow = true
86
- this.object3d.add(railMesh)
87
-
88
- // ── Base unit (motor housing on floor rail) ───────────────────────
89
- const baseGeo = new THREE.BoxGeometry(width * 0.95, baseH, height * 0.85)
90
- const baseMesh = new THREE.Mesh(baseGeo, baseMaterial)
91
- baseMesh.position.set(0, baseY + railH + baseH / 2, 0)
92
- baseMesh.castShadow = true
93
- baseMesh.receiveShadow = true
94
- this.object3d.add(baseMesh)
95
-
96
- // Body color tint band on base unit (subtle status indication)
97
- const tintH = baseH * 0.15
98
- const tintGeo = new THREE.BoxGeometry(width * 0.95, tintH, height * 0.85)
99
- const tintMaterial = new THREE.MeshStandardMaterial({
100
- color: bodyColor,
101
- transparent: true,
102
- opacity: 0.6,
103
- metalness: 0.1,
104
- roughness: 0.6
105
- })
106
- const tintMesh = new THREE.Mesh(tintGeo, tintMaterial)
107
- tintMesh.position.set(0, baseY + railH + baseH - tintH / 2, 0)
108
- this.object3d.add(tintMesh)
109
-
110
- // ── Mast (vertical column from base top to ceiling) ───────────────
111
- const mastY = baseY + railH + baseH + mastH / 2
112
- const mastGeo = new THREE.BoxGeometry(mastW, mastH, mastD)
113
- const mastMesh = new THREE.Mesh(mastGeo, mastMaterial)
114
- mastMesh.position.set(0, mastY, 0)
115
- mastMesh.castShadow = true
116
- this.object3d.add(mastMesh)
117
-
118
- // ── Carriage (horizontal block sliding on mast at carriageHeight) ─
119
- const carriageW = width * 0.85
120
- const carriageH = baseH * 0.7
121
- const carriageD = mastD * 1.4
122
- const carriageY = baseY + railH + baseH + carriageHeight + carriageH / 2
123
- const carriageGeo = new THREE.BoxGeometry(carriageW, carriageH, carriageD)
124
- const carriageMesh = new THREE.Mesh(carriageGeo, bodyMaterial)
125
- carriageMesh.position.set(0, carriageY, 0)
126
- carriageMesh.castShadow = true
127
- this.object3d.add(carriageMesh)
128
-
129
- // ── Shuttle / fork attachment on carriage (extends sideways) ──────
130
- const shuttleW = width * 1.05
131
- const shuttleH = carriageH * 0.5
132
- const shuttleD = carriageD * 0.6
133
- const shuttleGeo = new THREE.BoxGeometry(shuttleW, shuttleH, shuttleD)
134
- const shuttleMesh = new THREE.Mesh(shuttleGeo, shuttleMaterial)
135
- shuttleMesh.position.set(0, carriageY - carriageH / 2 - shuttleH / 2, 0)
136
- shuttleMesh.castShadow = true
137
- this.object3d.add(shuttleMesh)
138
-
139
- // ── Top guide (sliding block on ceiling rail) ─────────────────────
140
- const topGuideGeo = new THREE.BoxGeometry(width * 0.7, topGuideH, height * 0.6)
141
- const topGuideMesh = new THREE.Mesh(topGuideGeo, baseMaterial)
142
- topGuideMesh.position.set(0, baseY + depth - topGuideH / 2, 0)
143
- topGuideMesh.castShadow = true
144
- this.object3d.add(topGuideMesh)
145
-
146
- // Ceiling rail (small visual cue at very top)
147
- const ceilingRailGeo = new THREE.BoxGeometry(width * 1.15, topGuideH * 0.4, mastD * 0.4)
148
- const ceilingRailMesh = new THREE.Mesh(
149
- ceilingRailGeo,
150
- new THREE.MeshStandardMaterial({ color: RAIL_COLOR, metalness: 0.9, roughness: 0.3 })
151
- )
152
- ceilingRailMesh.position.set(0, baseY + depth - topGuideH * 0.2, 0)
153
- this.object3d.add(ceilingRailMesh)
154
-
155
- // ── Status lamp on top of base unit ───────────────────────────────
156
- const lampR = Math.min(width, height) * 0.04
157
- const lampH = lampR * 1.5
158
- const lampMaterial = new THREE.MeshStandardMaterial({
159
- color: emissiveColor,
160
- emissive: emissiveColor,
161
- emissiveIntensity: lampIntensity,
162
- metalness: 0,
163
- roughness: 0.3
164
- })
165
- const lampGeo = new THREE.CylinderGeometry(lampR, lampR * 0.85, lampH, 12)
166
- const lampMesh = new THREE.Mesh(lampGeo, lampMaterial)
167
- // Place lamp near the corner of the base, away from the mast
168
- lampMesh.position.set(width * 0.3, baseY + railH + baseH + lampH / 2, height * 0.3)
169
- this.object3d.add(lampMesh)
170
-
171
- // ── Carriage frame (invisible anchor for carrier attach) ──────────
172
- // Placed at the top of the shuttle, where cargo rests.
173
- this._carriageFrame = new THREE.Object3D()
174
- this._carriageFrame.name = 'crane-carriage-tcp'
175
- this._carriageFrame.position.set(0, carriageY - carriageH / 2 - shuttleH, 0)
176
- this.object3d.add(this._carriageFrame)
177
- }
178
-
179
- /** Sub-frame where carriers attach during transport (fork tool-centre-point). */
180
- private _carriageFrame?: THREE.Object3D
181
-
182
- /**
183
- * Return the carriage TCP anchor. Carriers attached to this frame will
184
- * follow carriage movement as `carriageHeight` changes and the crane rebuilds.
185
- *
186
- * Callers should re-fetch this after any state change that triggers rebuild.
187
- */
188
- getCarriageFrame(): THREE.Object3D | undefined {
189
- return this._carriageFrame
190
- }
191
-
192
- updateDimension() {}
193
-
194
- onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
195
- if (
196
- 'status' in after ||
197
- 'bodyColor' in after ||
198
- 'lampEmissive' in after ||
199
- 'carriageHeight' in after ||
200
- 'width' in after ||
201
- 'height' in after ||
202
- 'depth' in after
203
- ) {
204
- this.update()
205
- return
206
- }
207
- super.onchange(after, before)
208
- }
209
-
210
- updateAlpha() {}
211
- }
package/src/asrs-crane.ts DELETED
@@ -1,275 +0,0 @@
1
- /*
2
- * Copyright © HatioLab Inc. All rights reserved.
3
- */
4
- import { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'
5
- import type { SlotDef, State, Material3D } from '@hatiolab/things-scene'
6
- import {
7
- CarrierHolder,
8
- Legendable,
9
- Mover,
10
- Placeable,
11
- type AttachFrame,
12
- type Alignment,
13
- type Heights,
14
- type LegendBinding,
15
- type MoveOptions,
16
- type PlacementArchetype
17
- } from '@operato/scene-base'
18
-
19
- import { AsrsCrane3D } from './asrs-crane-3d.js'
20
-
21
- /**
22
- * AsrsCrane status — the operating state of a stacker crane in an AS/RS aisle.
23
- *
24
- * - `idle` — parked at home position
25
- * - `moving` — translating along the aisle (horizontal) or along the
26
- * mast (vertical); the two motions are typically combined
27
- * - `loading` — extracting a pallet from a rack cell
28
- * - `unloading` — placing a pallet into a rack cell
29
- * - `error` — fault / e-stop / collision warning
30
- */
31
- export type AsrsCraneStatus = 'idle' | 'moving' | 'loading' | 'unloading' | 'error'
32
-
33
- /** AsrsCrane 컴포넌트 state */
34
- export interface AsrsCraneState extends State {
35
- // ── 운영 상태 ──
36
- status?: AsrsCraneStatus
37
-
38
- // ── 액추에이터 ──
39
- carriageHeight?: number
40
-
41
- // ── 3D 재질 ──
42
- material3d?: Material3D
43
- }
44
-
45
- const BODY_LEGEND = {
46
- idle: '#888',
47
- moving: '#aabbcc',
48
- loading: '#ffaa00',
49
- unloading: '#ffaa00',
50
- error: '#c66',
51
- default: '#888'
52
- }
53
-
54
- const LAMP_EMISSIVE_LEGEND = {
55
- idle: '#222222',
56
- moving: '#44ff44',
57
- loading: '#ffaa00',
58
- unloading: '#ffaa00',
59
- error: '#ff3333',
60
- default: '#222222'
61
- }
62
-
63
- const NATURE: ComponentNature = {
64
- mutable: false,
65
- resizable: true,
66
- rotatable: true,
67
- properties: [
68
- {
69
- type: 'select',
70
- label: 'status',
71
- name: 'status',
72
- property: {
73
- options: [
74
- { display: 'Idle', value: 'idle' },
75
- { display: 'Moving', value: 'moving' },
76
- { display: 'Loading', value: 'loading' },
77
- { display: 'Unloading', value: 'unloading' },
78
- { display: 'Error', value: 'error' }
79
- ]
80
- }
81
- },
82
- {
83
- type: 'number',
84
- label: 'carriage-height',
85
- name: 'carriageHeight',
86
- placeholder: 'mm — height of carriage on mast'
87
- }
88
- ],
89
- help: 'scene/component/asrs-crane'
90
- }
91
-
92
- // Mixin chain: Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract
93
- //
94
- // Mover: pick / place / pickAndPlace / moveTo / engage primitives
95
- // CarrierHolder: attachPointFor() — where the carrier sits on the crane (carriage fork)
96
- // ContainerCapacity: receive() / dispatch() / canReceive() / slots — slot tracking +
97
- // TRANSFER_SLOT_KEY bookkeeping during transit
98
- // Legendable: status → bodyColor / lampEmissive colour mapping
99
- // Placeable: floor-archetype 3D positioning
100
- // ContainerAbstract: child management — carrier becomes a child while in transit
101
- //
102
- // Note: ContainerAbstract replaces Shape. The 2D outline is drawn manually in
103
- // render() below (a simple top-down rectangle), matching the old Shape output
104
- // without the Shape base-class overhead.
105
- /**
106
- * AsrsCrane — the stacker / retrieval crane that runs in the aisle of an
107
- * AS/RS, moving cargo between the load port and the rack cells.
108
- *
109
- * Structure: a tall vertical mast that translates along a floor + ceiling
110
- * rail (the aisle), with a carriage that slides up/down the mast carrying a
111
- * shuttle / forks.
112
- *
113
- * **Monitoring mode**: crane status is driven by data binding
114
- * (`state.status`, `state.carriageHeight`). The carrier is referenced
115
- * via data binding — it is NOT a child of the crane in monitoring mode.
116
- *
117
- * **Simulation mode**: call `crane.pick(carrier)` / `crane.place(carrier, rackCell)`
118
- * (or `crane.pickAndPlace(carrier, rackCell)`). Mover handles navigation +
119
- * engage + reparent. During transit the carrier IS a child of the crane.
120
- */
121
- @sceneComponent('asrs-crane')
122
- export default class AsrsCrane extends Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract))))) {
123
- declare state: AsrsCraneState
124
- declare _realObject?: AsrsCrane3D
125
-
126
- static legends: Record<string, LegendBinding> = {
127
- bodyColor: { from: 'status', legend: BODY_LEGEND },
128
- lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
129
- }
130
-
131
- static placement: PlacementArchetype = 'floor'
132
- static align: Alignment = 'bottom'
133
- static defaultDepth = (h: Heights) => h.ceiling - h.floor
134
-
135
- /** Yaw offset: crane model is drawn with the aisle axis along X (right = forward). */
136
- static yawOffset = 0
137
-
138
- /**
139
- * Phase H — ASRS crane 은 telescoping forks 로 rack cell 안의 carrier 픽업.
140
- * forklift-fork 와 동일 mechanism — pallet 의 fork pocket 진입.
141
- * (Mover mixin chain 이 :any 라 override 키워드 불가, getter 만 정의.)
142
- */
143
- get toolType(): string {
144
- return 'forklift-fork'
145
- }
146
-
147
- get nature() {
148
- return NATURE
149
- }
150
-
151
- get anchors() {
152
- return []
153
- }
154
-
155
- // ── ContainerCapacity ─────────────────────────────────────────────────────
156
-
157
- /** Stacker crane carries at most one load at a time on its forks. */
158
- get slots(): SlotDef[] {
159
- return [{ id: 'forks', maxCount: 1 }]
160
- }
161
-
162
- // ── CarrierHolder — attach frame (carriage fork position) ─────────────────
163
-
164
- /**
165
- * Return the 3D attach frame on the crane's carriage (fork tip).
166
- * Carriers are attached here while the crane is in transit (pick phase).
167
- *
168
- * The AsrsCrane3D exposes `getCarriageFrame()` — a sub-Object3D that
169
- * tracks the carriage height and sits at the fork TCP. If the 3D object
170
- * isn't built yet (e.g. before scene initialization), fall back to the
171
- * crane's own object3d centre.
172
- */
173
- attachPointFor(carrier: Component): AttachFrame | null {
174
- const ro = this._realObject
175
- const frame = ro?.getCarriageFrame?.()
176
- if (frame) {
177
- const carrierDepth = resolveCarrierDepth(carrier)
178
- return { attach: frame, localPosition: { x: 0, y: carrierDepth / 2, z: 0 } }
179
- }
180
- const root = this._realObject?.object3d
181
- if (!root) return null
182
- return { attach: root }
183
- }
184
-
185
- // ── Mover overrides ───────────────────────────────────────────────────────
186
-
187
- /**
188
- * Domain-specific actuation between arrival and reparent.
189
- *
190
- * Simulation sequence for PICK:
191
- * 1. Mover.pick() navigates crane to carrier position (moveTo).
192
- * 2. engage('pick') → snap carriage height + status 'loading'.
193
- * 3. Carrier is reparented to crane (becomes child).
194
- *
195
- * For now: set status and snap carriage height. A full ASRS simulation
196
- * would tween the carriageHeight here (animate AsrsCrane3D).
197
- *
198
- * Status lifecycle:
199
- * idle → (moveTo running) → engage fires → loading/unloading → (reparent) → idle
200
- * The 'moving' state is not set from Mover.moveTo() because TypeScript
201
- * can't call super.moveTo() on an `: any`-typed mixin. WCS data binding
202
- * sets 'moving' in monitoring mode; override pick()/place() to set it
203
- * in full simulation environments.
204
- */
205
- async engage(
206
- target: Component,
207
- kind: 'pick' | 'place',
208
- _options: MoveOptions = {}
209
- ): Promise<void> {
210
- if (kind === 'pick') {
211
- this.setState({ status: 'loading' as AsrsCraneStatus })
212
- const carrierY = resolveCarrierCenterY(target)
213
- if (carrierY !== null) {
214
- this.setState({ carriageHeight: carrierY })
215
- }
216
- } else {
217
- this.setState({ status: 'unloading' as AsrsCraneStatus })
218
- }
219
- // In a full simulation: await carriage-motion tween here.
220
- }
221
-
222
- // ── Domain aliases ────────────────────────────────────────────────────────
223
-
224
- /** Fetch a carrier from a rack cell (semantically = pick). */
225
- fetch(carrier: Component, options?: MoveOptions): Promise<void> {
226
- return this.pick(carrier, options)
227
- }
228
-
229
- /** Deposit a carrier into a rack cell (semantically = place). */
230
- deposit(carrier: Component, cell: Component, options?: MoveOptions): Promise<void> {
231
- return this.place(carrier, cell, options)
232
- }
233
-
234
- // ── 2D rendering ─────────────────────────────────────────────────────────
235
-
236
- /**
237
- * 2D — top-down rectangle showing the crane's footprint along the aisle.
238
- * The crane is much taller than wide, so the 2D mark is small.
239
- */
240
- render(ctx: CanvasRenderingContext2D) {
241
- const { width, height, left, top } = this.state
242
- const fillColor = (this.state.bodyColor as string) || '#888'
243
- ctx.save()
244
- ctx.fillStyle = fillColor
245
- ctx.beginPath()
246
- ctx.rect(left, top, width, height)
247
- ctx.fill()
248
- ctx.restore()
249
- }
250
-
251
- // ── 3D ───────────────────────────────────────────────────────────────────
252
-
253
- buildRealObject(): RealObject | undefined {
254
- return new AsrsCrane3D(this)
255
- }
256
- }
257
-
258
- function resolveCarrierDepth(c: Component): number {
259
- const eff = (c as any)._realObject?.effectiveDepth
260
- if (typeof eff === 'number' && Number.isFinite(eff)) return eff
261
- return numOr((c as any)?.state?.depth, 0)
262
- }
263
-
264
- function resolveCarrierCenterY(c: Component): number | null {
265
- const pos = (c as any).state
266
- if (!pos) return null
267
- // zPos is the 3D Y center of a Placeable component in things-scene
268
- const zPos = numOr(pos.zPos, NaN)
269
- if (!Number.isNaN(zPos)) return zPos
270
- return null
271
- }
272
-
273
- function numOr(v: unknown, dflt: number): number {
274
- return typeof v === 'number' && Number.isFinite(v) ? v : dflt
275
- }
File without changes
File without changes