@operato/scene-storage 10.0.0-beta.40 → 10.0.0-beta.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/MIGRATION-plan-a-slot-api.md +266 -0
- package/PLAN-A-rack-as-slot-holder.md +164 -0
- package/dist/box.js +18 -0
- package/dist/box.js.map +1 -1
- package/dist/crane-3d.d.ts +47 -2
- package/dist/crane-3d.js +246 -89
- package/dist/crane-3d.js.map +1 -1
- package/dist/crane.d.ts +96 -12
- package/dist/crane.js +395 -100
- package/dist/crane.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/pallet.d.ts +15 -0
- package/dist/pallet.js +38 -2
- package/dist/pallet.js.map +1 -1
- package/dist/parcel-3d.js +22 -18
- package/dist/parcel-3d.js.map +1 -1
- package/dist/parcel.d.ts +4 -3
- package/dist/parcel.js +24 -5
- package/dist/parcel.js.map +1 -1
- package/dist/rack-grid-3d.d.ts +18 -7
- package/dist/rack-grid-3d.js +372 -69
- package/dist/rack-grid-3d.js.map +1 -1
- package/dist/rack-grid-cell.d.ts +21 -72
- package/dist/rack-grid-cell.js +147 -243
- package/dist/rack-grid-cell.js.map +1 -1
- package/dist/rack-grid.d.ts +277 -56
- package/dist/rack-grid.js +1230 -695
- package/dist/rack-grid.js.map +1 -1
- package/dist/rack-materials.d.ts +9 -0
- package/dist/rack-materials.js +55 -0
- package/dist/rack-materials.js.map +1 -0
- package/dist/storage-rack-3d.d.ts +15 -0
- package/dist/storage-rack-3d.js +165 -29
- package/dist/storage-rack-3d.js.map +1 -1
- package/dist/storage-rack.d.ts +253 -32
- package/dist/storage-rack.js +726 -66
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/box.ts +18 -0
- package/src/crane-3d.ts +258 -93
- package/src/crane.ts +445 -110
- package/src/index.ts +3 -4
- package/src/pallet.ts +50 -1
- package/src/parcel-3d.ts +23 -18
- package/src/parcel.ts +24 -5
- package/src/rack-grid-3d.ts +383 -80
- package/src/rack-grid-cell.ts +161 -305
- package/src/rack-grid.ts +1263 -762
- package/src/rack-materials.ts +61 -0
- package/src/storage-rack-3d.ts +182 -29
- package/src/storage-rack.ts +819 -67
- package/test/test-carrier-lifecycle.ts +361 -0
- package/test/test-coord-alignment.ts +201 -0
- package/test/test-crane-geometry.ts +167 -0
- package/test/test-external-to-rack.ts +461 -0
- package/test/test-mover-concurrent-bug.ts +304 -0
- package/test/test-mover-rollback.ts +290 -0
- package/test/test-phase-h-carrier-pickable.ts +4 -3
- package/test/test-r19-place-absorb.ts +174 -0
- package/test/test-rack-3d-attach-real.ts +301 -0
- package/test/test-rack-concurrent.ts +254 -0
- package/test/test-rack-edge-cases.ts +323 -0
- package/test/test-rack-grid-cell.ts +318 -0
- package/test/test-rack-grid-location.ts +657 -0
- package/test/test-real-3d-positioning.ts +158 -0
- package/test/test-slot-center-convention.ts +116 -0
- package/test/test-slot-target.ts +189 -0
- package/test/test-storage-rack-batched.ts +606 -0
- package/test/test-storage-rack-click.ts +329 -0
- package/test/test-storage-rack-slot-api.ts +357 -0
- package/test/test-toscene-convention.ts +162 -0
- package/test/test-user-scenario-sequential.ts +334 -0
- package/translations/en.json +7 -1
- package/translations/ja.json +7 -1
- package/translations/ko.json +7 -1
- package/translations/ms.json +7 -1
- package/translations/zh.json +7 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/rack-column.d.ts +0 -35
- package/dist/rack-column.js +0 -258
- package/dist/rack-column.js.map +0 -1
- package/dist/rack-grid-helpers.d.ts +0 -28
- package/dist/rack-grid-helpers.js +0 -71
- package/dist/rack-grid-helpers.js.map +0 -1
- package/dist/rack-grid-location.d.ts +0 -37
- package/dist/rack-grid-location.js +0 -227
- package/dist/rack-grid-location.js.map +0 -1
- package/dist/storage-cell-3d.d.ts +0 -25
- package/dist/storage-cell-3d.js +0 -88
- package/dist/storage-cell-3d.js.map +0 -1
- package/dist/storage-cell.d.ts +0 -70
- package/dist/storage-cell.js +0 -197
- package/dist/storage-cell.js.map +0 -1
- package/src/rack-column.ts +0 -340
- package/src/rack-grid-helpers.ts +0 -77
- package/src/rack-grid-location.ts +0 -286
- package/src/storage-cell-3d.ts +0 -101
- package/src/storage-cell.ts +0 -247
- package/test/test-rack-grid.ts +0 -77
package/dist/storage-cell.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storage-cell.js","sourceRoot":"","sources":["../src/storage-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 Rack.\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 (or any picker) 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: Rack._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 { StorageCell3D } from './storage-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 StorageCellType = 'single' | 'multi' | 'bulk'\n\n/** RackCell 컴포넌트 state */\nexport interface StorageCellState extends State {\n // ── 식별 ──\n cellId?: string\n cellType?: StorageCellType\n /**\n * 자동 할당된 location ID — RackTable.assignLocations() 가 set. 외부 시스템\n * (WMS, picker 명령 등) 이 cell 을 지칭하는 사람-친화 ID. RackTable 없이\n * 단독으로 Rack 을 사용할 땐 unset.\n */\n locationId?: string\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/storage-cell'\n}\n\n/**\n * RackCell — single-slot storage cell inside an Rack.\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('storage-cell')\nexport default class RackCell extends CarrierHolder(ContainerAbstract) {\n declare state: StorageCellState\n\n // ── Identification ────────────────────────────────────────────────────────\n\n get cellId(): string {\n return this.state.cellId ?? ''\n }\n\n get cellType(): StorageCellType {\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 StorageCell3D(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"]}
|
package/src/rack-column.ts
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as THREE from 'three'
|
|
6
|
-
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
7
|
-
|
|
8
|
-
import { RealObject } from '@hatiolab/things-scene'
|
|
9
|
-
import { Stock } from './stock.js'
|
|
10
|
-
import RackGrid from './rack-grid.js'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const _color = new THREE.Color()
|
|
14
|
-
const _matrix = new THREE.Matrix4()
|
|
15
|
-
const _position = new THREE.Vector3()
|
|
16
|
-
const _quaternion = new THREE.Quaternion()
|
|
17
|
-
const _scale = new THREE.Vector3()
|
|
18
|
-
|
|
19
|
-
export class Rack extends RealObject {
|
|
20
|
-
private _frame?: THREE.BufferGeometry
|
|
21
|
-
private _board?: THREE.BufferGeometry
|
|
22
|
-
private _instancedMesh?: THREE.InstancedMesh
|
|
23
|
-
private _stocks: Stock[] = []
|
|
24
|
-
|
|
25
|
-
static rackFrameGeometry = new THREE.BoxGeometry(1, 1, 1)
|
|
26
|
-
static boardGeometry = new THREE.PlaneGeometry(1, 1, 1, 1)
|
|
27
|
-
|
|
28
|
-
private static _boardMaterial?: THREE.MeshStandardMaterial
|
|
29
|
-
private static _stockMaterial?: THREE.MeshStandardMaterial
|
|
30
|
-
|
|
31
|
-
get visualizer(): any | undefined {
|
|
32
|
-
var component = this.component
|
|
33
|
-
|
|
34
|
-
while (component) {
|
|
35
|
-
if (component.state.type == 'visualizer') {
|
|
36
|
-
return component as any
|
|
37
|
-
}
|
|
38
|
-
component = component.parent
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Stock 등록 대상: 1순위 조상 Visualizer(하위호환), 2순위 서비스 레지스트리의 stock-hub
|
|
44
|
-
*/
|
|
45
|
-
get stockRegistry(): { putObject(id: string, obj: RealObject): void } | undefined {
|
|
46
|
-
const vis = this.visualizer
|
|
47
|
-
if (vis) return vis
|
|
48
|
-
return (this.component.root as any)?.getService?.('stock')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get rackTable(): RackGrid | undefined {
|
|
52
|
-
var component = this.component
|
|
53
|
-
|
|
54
|
-
while (component) {
|
|
55
|
-
if (component.state.type == 'rack-grid') {
|
|
56
|
-
return component as unknown as RackGrid
|
|
57
|
-
}
|
|
58
|
-
component = component.parent
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get cz() {
|
|
63
|
-
var { shelves = 1, depth = 1 } = this.rackTable!.state
|
|
64
|
-
|
|
65
|
-
return 0.5 * depth * shelves
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
static get boardMaterial() {
|
|
69
|
-
if (!Rack._boardMaterial) {
|
|
70
|
-
Rack._boardMaterial = new THREE.MeshStandardMaterial({
|
|
71
|
-
color: '#dedede',
|
|
72
|
-
side: THREE.DoubleSide,
|
|
73
|
-
polygonOffset: true,
|
|
74
|
-
polygonOffsetFactor: -0.1
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return Rack._boardMaterial
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static get stockMaterial() {
|
|
82
|
-
if (!Rack._stockMaterial) {
|
|
83
|
-
Rack._stockMaterial = new THREE.MeshStandardMaterial({
|
|
84
|
-
color: 0xffffff,
|
|
85
|
-
side: THREE.FrontSide,
|
|
86
|
-
metalness: 0,
|
|
87
|
-
roughness: 1,
|
|
88
|
-
envMapIntensity: 0
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
return Rack._stockMaterial
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
get frame() {
|
|
95
|
-
return this._frame
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
get board() {
|
|
99
|
-
return this._board
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get instancedMesh() {
|
|
103
|
-
return this._instancedMesh
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
get stocks() {
|
|
107
|
-
return this._stocks
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
build() {
|
|
111
|
-
super.build()
|
|
112
|
-
|
|
113
|
-
var { depth, hideRackFrame, shelves = 1, shelfLocations: commonShelfLocation, stockScale = 0.7 } = this.rackTable!.state
|
|
114
|
-
|
|
115
|
-
var { width, height, shelfLocations = commonShelfLocation, binLocations = '' } = this.component.state
|
|
116
|
-
|
|
117
|
-
let scale = stockScale
|
|
118
|
-
|
|
119
|
-
var shelfLocIds
|
|
120
|
-
|
|
121
|
-
if (!shelfLocations) {
|
|
122
|
-
shelfLocIds = []
|
|
123
|
-
for (var i = 0; i < shelves; i++) shelfLocIds.push(i + 1)
|
|
124
|
-
} else shelfLocIds = shelfLocations.split(/\s*,\s*/)
|
|
125
|
-
|
|
126
|
-
var shelfBins = binLocations.trim().split('\n').reverse()
|
|
127
|
-
|
|
128
|
-
if (!hideRackFrame) {
|
|
129
|
-
this._frame = this.createRackFrame(width, height, depth * shelves)
|
|
130
|
-
this._board = this.createRackBoards(shelves, width, height, depth, shelfLocIds)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Stock 인스턴스 정보 수집
|
|
134
|
-
const stockEntries: { stock: Stock; x: number; y: number; z: number; w: number; h: number; d: number; name: string }[] = []
|
|
135
|
-
|
|
136
|
-
for (var i = 0; i < shelves; i++) {
|
|
137
|
-
let bottom = -depth * shelves * 0.5
|
|
138
|
-
if (shelfLocIds[i] == '') {
|
|
139
|
-
continue
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
var bins = (shelfBins[i] || '').trim().split(/\s*,\s*/)
|
|
143
|
-
|
|
144
|
-
var binWidth = width / (bins.length || 1)
|
|
145
|
-
for (var b = 0; b < bins.length; b++) {
|
|
146
|
-
let sw = binWidth * scale
|
|
147
|
-
let sh = height * scale
|
|
148
|
-
let sd = depth * scale
|
|
149
|
-
|
|
150
|
-
let stock = new Stock(this.component, { width: sw, height: sh, depth: sd })
|
|
151
|
-
|
|
152
|
-
let x = (width / 2) * ((2 * b - (bins.length - 1)) / bins.length)
|
|
153
|
-
let y = bottom + depth * i + sd * 0.5
|
|
154
|
-
let z = 0
|
|
155
|
-
|
|
156
|
-
var binCode = (bins[b] || '').replace('.', '')
|
|
157
|
-
let name = `${this.makeLocationString(shelfLocIds[i])}${binCode}`
|
|
158
|
-
|
|
159
|
-
stockEntries.push({ stock, x, y, z, w: sw, h: sh, d: sd, name })
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// InstancedMesh 생성 (전체 Stock을 1개 draw call로)
|
|
164
|
-
if (stockEntries.length > 0) {
|
|
165
|
-
const instMesh = new THREE.InstancedMesh(
|
|
166
|
-
Stock.stockGeometry,
|
|
167
|
-
Rack.stockMaterial,
|
|
168
|
-
stockEntries.length
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
// instanceColor 초기화
|
|
172
|
-
const defaultColor = new THREE.Color(Stock.defaultMaterial.color)
|
|
173
|
-
for (let idx = 0; idx < stockEntries.length; idx++) {
|
|
174
|
-
const { stock, x, y, z, w, h, d, name } = stockEntries[idx]
|
|
175
|
-
|
|
176
|
-
_matrix.compose(
|
|
177
|
-
_position.set(x, y, z),
|
|
178
|
-
_quaternion.identity(),
|
|
179
|
-
_scale.set(w, d, h)
|
|
180
|
-
)
|
|
181
|
-
instMesh.setMatrixAt(idx, _matrix)
|
|
182
|
-
instMesh.setColorAt(idx, defaultColor)
|
|
183
|
-
|
|
184
|
-
// Stock에 instanced 참조 설정
|
|
185
|
-
stock._instancedMesh = instMesh
|
|
186
|
-
stock._instanceIndex = idx
|
|
187
|
-
stock._basePosition = { x, y, z }
|
|
188
|
-
stock._baseScale = { x: w, y: d, z: h }
|
|
189
|
-
|
|
190
|
-
// name 기반 lookup 유지
|
|
191
|
-
this._stocks.push(stock)
|
|
192
|
-
this.stockRegistry?.putObject(name, stock)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
instMesh.instanceMatrix.needsUpdate = true
|
|
196
|
-
instMesh.instanceColor!.needsUpdate = true
|
|
197
|
-
instMesh.receiveShadow = true
|
|
198
|
-
|
|
199
|
-
// focused stock 회전 애니메이션
|
|
200
|
-
const self = this
|
|
201
|
-
instMesh.onBeforeRender = () => {
|
|
202
|
-
let needsUpdate = false
|
|
203
|
-
let hasFocused = false
|
|
204
|
-
for (const stock of self._stocks) {
|
|
205
|
-
if (stock._focused && stock._focusedAt != null) {
|
|
206
|
-
const elapsed = performance.now() - stock._focusedAt
|
|
207
|
-
const angle = 2 * Math.PI * (elapsed / 2000)
|
|
208
|
-
_quaternion.setFromAxisAngle(_position.set(0, 1, 0), angle)
|
|
209
|
-
_matrix.compose(
|
|
210
|
-
_position.set(stock._basePosition!.x, stock._basePosition!.y, stock._basePosition!.z),
|
|
211
|
-
_quaternion,
|
|
212
|
-
_scale.set(stock._baseScale!.x, stock._baseScale!.y, stock._baseScale!.z)
|
|
213
|
-
)
|
|
214
|
-
instMesh.setMatrixAt(stock._instanceIndex, _matrix)
|
|
215
|
-
needsUpdate = true
|
|
216
|
-
hasFocused = true
|
|
217
|
-
} else if (stock._focusedAt != null) {
|
|
218
|
-
delete stock._focusedAt
|
|
219
|
-
_matrix.compose(
|
|
220
|
-
_position.set(stock._basePosition!.x, stock._basePosition!.y, stock._basePosition!.z),
|
|
221
|
-
_quaternion.identity(),
|
|
222
|
-
_scale.set(stock._baseScale!.x, stock._baseScale!.y, stock._baseScale!.z)
|
|
223
|
-
)
|
|
224
|
-
instMesh.setMatrixAt(stock._instanceIndex, _matrix)
|
|
225
|
-
needsUpdate = true
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (needsUpdate) {
|
|
229
|
-
instMesh.instanceMatrix.needsUpdate = true
|
|
230
|
-
}
|
|
231
|
-
if (hasFocused) {
|
|
232
|
-
self.component.invalidate()
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
this.object3d.add(instMesh)
|
|
237
|
-
this._instancedMesh = instMesh
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
createRackFrame(w: number, h: number, d: number): THREE.BufferGeometry {
|
|
242
|
-
const frameWeight = Math.round(Math.min(w, h) / 10)
|
|
243
|
-
const geometries = []
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < 4; i++) {
|
|
246
|
-
const geometry = Rack.rackFrameGeometry.clone()
|
|
247
|
-
geometry.scale(frameWeight, d, frameWeight)
|
|
248
|
-
|
|
249
|
-
switch (i) {
|
|
250
|
-
case 0:
|
|
251
|
-
geometry.translate(w / 2, 0, h / 2)
|
|
252
|
-
break
|
|
253
|
-
case 1:
|
|
254
|
-
geometry.translate(w / 2, 0, -h / 2)
|
|
255
|
-
break
|
|
256
|
-
case 2:
|
|
257
|
-
geometry.translate(-w / 2, 0, h / 2)
|
|
258
|
-
break
|
|
259
|
-
case 3:
|
|
260
|
-
geometry.translate(-w / 2, 0, -h / 2)
|
|
261
|
-
break
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
geometries.push(geometry)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return BufferGeometryUtils.mergeGeometries(geometries)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
createRackBoards(
|
|
271
|
-
shelves: number,
|
|
272
|
-
width: number,
|
|
273
|
-
height: number,
|
|
274
|
-
depth: number,
|
|
275
|
-
shelfLocIds: string[]
|
|
276
|
-
): THREE.BufferGeometry {
|
|
277
|
-
let bottom = -depth * shelves * 0.5
|
|
278
|
-
const geometries = []
|
|
279
|
-
|
|
280
|
-
for (let i = 1; i < shelves; i++) {
|
|
281
|
-
if (shelfLocIds[i] === '') {
|
|
282
|
-
continue
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const geometry = Rack.boardGeometry.clone()
|
|
286
|
-
|
|
287
|
-
geometry.scale(width, height, 1)
|
|
288
|
-
geometry.rotateX(Math.PI / 2)
|
|
289
|
-
geometry.translate(0, bottom + depth * i, 0)
|
|
290
|
-
|
|
291
|
-
geometries.push(geometry)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return BufferGeometryUtils.mergeGeometries(geometries)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
makeLocationString(shelfString: string) {
|
|
298
|
-
var { section = '', unit = '' } = this.component.state
|
|
299
|
-
var { locPattern = '{z}{s}-{u}-{sh}', zone = '' } = this.rackTable!.state
|
|
300
|
-
|
|
301
|
-
var locationString = locPattern
|
|
302
|
-
|
|
303
|
-
locationString = locationString.replace(/{z}/i, zone)
|
|
304
|
-
locationString = locationString.replace(/{s}/i, section)
|
|
305
|
-
locationString = locationString.replace(/{u}/i, unit)
|
|
306
|
-
locationString = locationString.replace(/{sh}/i, shelfString)
|
|
307
|
-
|
|
308
|
-
return locationString
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
makeShelfString(pattern: string, shelf: number, length: number) {
|
|
312
|
-
/**
|
|
313
|
-
* pattern #: 숫자
|
|
314
|
-
* pattern 0: 고정 자리수
|
|
315
|
-
* pattern -: 역순
|
|
316
|
-
*/
|
|
317
|
-
|
|
318
|
-
if (!pattern || !shelf || !length) return
|
|
319
|
-
|
|
320
|
-
var isReverse = /^\-/.test(pattern)
|
|
321
|
-
pattern = pattern.replace(/#+/, '#')
|
|
322
|
-
|
|
323
|
-
var fixedLength = (pattern.match(/0/g) || []).length || 0
|
|
324
|
-
var shelfString = String(isReverse ? length - shelf + 1 : shelf)
|
|
325
|
-
|
|
326
|
-
if (shelfString.length > fixedLength && fixedLength > 0) {
|
|
327
|
-
shelfString = shelfString.substring(shelfString.length - fixedLength)
|
|
328
|
-
} else {
|
|
329
|
-
var prefix = ''
|
|
330
|
-
for (var i = 0; i < fixedLength - shelfString.length; i++) {
|
|
331
|
-
prefix += '0'
|
|
332
|
-
}
|
|
333
|
-
shelfString = prefix + shelfString
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return shelfString
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
updateAlpha() {}
|
|
340
|
-
}
|
package/src/rack-grid-helpers.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
-
*
|
|
4
|
-
* RackTable pure helpers — separated for unit-testability (no scene-base /
|
|
5
|
-
* things-scene runtime deps).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Parse shelf-labels string into an array of length `levels`.
|
|
10
|
-
*
|
|
11
|
-
* Format: comma-separated. Empty positions fall back to `String(level + 1)`.
|
|
12
|
-
* ',,,04' over 4 levels → ['1', '2', '3', '04']
|
|
13
|
-
* '1,2,3' over 4 levels → ['1', '2', '3', '4']
|
|
14
|
-
*/
|
|
15
|
-
export function parseShelfLabels(input: string | undefined, levels: number): string[] {
|
|
16
|
-
const out: string[] = []
|
|
17
|
-
const parts = (input || '').split(',')
|
|
18
|
-
for (let i = 0; i < levels; i++) {
|
|
19
|
-
const p = parts[i]
|
|
20
|
-
out[i] = p && p.trim().length > 0 ? p.trim() : String(i + 1)
|
|
21
|
-
}
|
|
22
|
-
return out
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Apply a location pattern with the four standard placeholders.
|
|
27
|
-
*
|
|
28
|
-
* {z} → zone
|
|
29
|
-
* {s} → section (zero-padded outside)
|
|
30
|
-
* {u} → unit (zero-padded outside)
|
|
31
|
-
* {sh} → shelf
|
|
32
|
-
*
|
|
33
|
-
* Caller is responsible for the zero-padding of section/unit (the digit
|
|
34
|
-
* widths are state-driven, not part of the pattern itself).
|
|
35
|
-
*/
|
|
36
|
-
export function formatLocationId(
|
|
37
|
-
zone: string,
|
|
38
|
-
section: string,
|
|
39
|
-
unit: string,
|
|
40
|
-
shelf: string,
|
|
41
|
-
pattern: string
|
|
42
|
-
): string {
|
|
43
|
-
return pattern
|
|
44
|
-
.replace(/\{z\}/g, zone)
|
|
45
|
-
.replace(/\{s\}/g, section)
|
|
46
|
-
.replace(/\{u\}/g, unit)
|
|
47
|
-
.replace(/\{sh\}/g, shelf)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Distribute `total` over `n` slots, honoring per-slot declared sizes when
|
|
52
|
-
* present. Unspecified slots share the remaining space evenly.
|
|
53
|
-
*
|
|
54
|
-
* distribute(1000, 4, [200, undefined, 300, undefined]) → [200, 250, 300, 250]
|
|
55
|
-
* distribute(1000, 4, []) → [250, 250, 250, 250]
|
|
56
|
-
*/
|
|
57
|
-
export function distribute(total: number, n: number, declared: (number | undefined)[] = []): number[] {
|
|
58
|
-
const out: number[] = []
|
|
59
|
-
let assigned = 0
|
|
60
|
-
let auto = 0
|
|
61
|
-
for (let i = 0; i < n; i++) {
|
|
62
|
-
const v = declared[i]
|
|
63
|
-
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
64
|
-
out[i] = v
|
|
65
|
-
assigned += v
|
|
66
|
-
} else {
|
|
67
|
-
out[i] = -1
|
|
68
|
-
auto++
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const remaining = Math.max(0, total - assigned)
|
|
72
|
-
const autoSize = auto > 0 ? remaining / auto : 0
|
|
73
|
-
for (let i = 0; i < n; i++) {
|
|
74
|
-
if (out[i] === -1) out[i] = autoSize
|
|
75
|
-
}
|
|
76
|
-
return out
|
|
77
|
-
}
|