@operato/scene-storage 10.0.0-beta.22 → 10.0.0-beta.27
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 +23 -0
- package/dist/asrs-rack.js +5 -2
- package/dist/asrs-rack.js.map +1 -1
- package/dist/generic-container-3d.d.ts +6 -0
- package/dist/generic-container-3d.js +42 -0
- package/dist/generic-container-3d.js.map +1 -0
- package/dist/generic-container.d.ts +19 -0
- package/dist/generic-container.js +100 -0
- package/dist/generic-container.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/pallet.d.ts +15 -1
- package/dist/pallet.js +70 -5
- package/dist/pallet.js.map +1 -1
- package/dist/spot.js +8 -4
- package/dist/spot.js.map +1 -1
- package/dist/templates/index.js +3 -3
- package/dist/templates/index.js.map +1 -1
- package/package.json +3 -3
- package/src/asrs-rack.ts +5 -2
- package/src/generic-container-3d.ts +45 -0
- package/src/generic-container.ts +126 -0
- package/src/index.ts +4 -0
- package/src/pallet.ts +74 -5
- package/src/spot.ts +8 -4
- package/src/templates/index.ts +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@operato/scene-storage",
|
|
3
3
|
"description": "Storage-domain components for things-scene (smart factory / logistics) — pallet, box, parcel; AS/RS and shelves planned.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "10.0.0-beta.
|
|
5
|
+
"version": "10.0.0-beta.27",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@hatiolab/things-scene": "^10.0.0-beta.1",
|
|
28
|
-
"@operato/scene-base": "^10.0.0-beta.
|
|
28
|
+
"@operato/scene-base": "^10.0.0-beta.24",
|
|
29
29
|
"three": "^0.183.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"typescript": "^5.0.4"
|
|
41
41
|
},
|
|
42
42
|
"prettier": "@hatiolab/prettier-config",
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "17135d7379cf0dee5e36332d08d75b58fbf2ab47"
|
|
44
44
|
}
|
package/src/asrs-rack.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import { Component, ComponentNature,
|
|
4
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
5
5
|
import {
|
|
6
6
|
Placeable,
|
|
7
7
|
type Alignment,
|
|
@@ -32,7 +32,10 @@ const NATURE: ComponentNature = {
|
|
|
32
32
|
help: 'scene/component/asrs-rack'
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
36
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
37
|
+
// addObject DOM-skip gate. ASRS rack lives only in the 3D scene graph.
|
|
38
|
+
const Base = Placeable(ContainerAbstract) as unknown as typeof Component
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* AsrsRack — a multi-level high-bay storage rack, the structural backbone of
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* GenericContainer3D — RealObjectGLTF 상속, 보관소 (Stocker / Buffer / Rack 류) 의 3D 표현.
|
|
5
|
+
*
|
|
6
|
+
* 상속 체인:
|
|
7
|
+
* RealObjectGroup → RealObjectExternalModel → RealObjectGLTF → GenericContainer3D
|
|
8
|
+
*
|
|
9
|
+
* 재사용:
|
|
10
|
+
* - GLB load (`_loadExternal` + 캐시)
|
|
11
|
+
* - node index (`getNode`, `nodeNames`)
|
|
12
|
+
* - 노드 상태 매핑 (`nodes` state → color/visible/opacity)
|
|
13
|
+
* - 애니메이션 (`animations` / `playTargets`)
|
|
14
|
+
* - top-view 스냅샷 (2D 렌더용)
|
|
15
|
+
* - dispose / clear
|
|
16
|
+
*
|
|
17
|
+
* 추가:
|
|
18
|
+
* - actuator (state 값 → 노드 transform). rack 의 lift, shutter 열림 등 동적 표현.
|
|
19
|
+
*
|
|
20
|
+
* GenericTransport3D 와 차이: mount / cargo reparent 미사용 (cargo holder 가 아니라 placeholder
|
|
21
|
+
* 단계의 정적 보관소). cargo 보유는 사용자가 구체 type 으로 변환 후에 부여.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { RealObjectGLTF } from '@hatiolab/things-scene'
|
|
25
|
+
import { applyActuators } from '@operato/scene-base'
|
|
26
|
+
|
|
27
|
+
export class GenericContainer3D extends RealObjectGLTF {
|
|
28
|
+
protected _onLoaded(gltf: any) {
|
|
29
|
+
super._onLoaded(gltf)
|
|
30
|
+
this._applyActuators()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private _applyActuators() {
|
|
34
|
+
const state = this.component.state as any
|
|
35
|
+
applyActuators(this, state.actuators, state.actuatorValues)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
39
|
+
if ('actuatorValues' in after || 'actuators' in after) {
|
|
40
|
+
this._applyActuators()
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
super.onchange(after, before)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* GenericContainer — GLB 모델 기반 범용 보관소 컴포넌트.
|
|
5
|
+
*
|
|
6
|
+
* 합성 구조:
|
|
7
|
+
* GltfComponent (things-scene) — GLB 컨벤션 (src, 2D 스냅샷, ratioLock,
|
|
8
|
+
* gltf-* 에디터, RealObjectGLTF)
|
|
9
|
+
* ⊕ ContainerAbstract (things-scene) — 자식 보유 (DOM-free 컨테이너)
|
|
10
|
+
* ⊕ Placeable (scene-base) — floor placement, depth=operation
|
|
11
|
+
* ⊕ Legendable (scene-base) — fill 별 bodyColor / lampEmissive
|
|
12
|
+
*
|
|
13
|
+
* 추가 (이 클래스 고유):
|
|
14
|
+
* - state.actuators / actuatorValues — 동적 노드 transform (rack lift, shutter 열림 등)
|
|
15
|
+
*
|
|
16
|
+
* 동일 컴포넌트 하나로 stocker / buffer / shelf / rack 모두 표현 가능 — 각 보관소별로
|
|
17
|
+
* 다른 GLB + 다른 actuator 매핑만 다름.
|
|
18
|
+
*
|
|
19
|
+
* GenericTransport / GenericFacility 와의 차이:
|
|
20
|
+
* - cargo holder 가 아님 — 사용자가 구체 type (e.g. AsrsRack) 으로 변환 후에 cargo 부여
|
|
21
|
+
* - container 카테고리 (정적 보관소). 분류는 동일 floor placement 지만 의미는 다름
|
|
22
|
+
*
|
|
23
|
+
* board-import 의 categoryFallback 에서 container 카테고리의 default placeholder. 사용자가
|
|
24
|
+
* 모델러에서 src 부착하거나 type 변경으로 도메인 type (Stocker / Buffer / AsrsRack 등) 으로 변환.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
Component,
|
|
29
|
+
ComponentNature,
|
|
30
|
+
ContainerAbstract,
|
|
31
|
+
GltfComponent,
|
|
32
|
+
gltfNatureProperties,
|
|
33
|
+
sceneComponent
|
|
34
|
+
} from '@hatiolab/things-scene'
|
|
35
|
+
import {
|
|
36
|
+
Legendable,
|
|
37
|
+
Placeable,
|
|
38
|
+
type Alignment,
|
|
39
|
+
type Heights,
|
|
40
|
+
type LegendBinding,
|
|
41
|
+
type PlacementArchetype
|
|
42
|
+
} from '@operato/scene-base'
|
|
43
|
+
|
|
44
|
+
import { GenericContainer3D } from './generic-container-3d.js'
|
|
45
|
+
import type { ActuatorDef } from '@operato/scene-base'
|
|
46
|
+
|
|
47
|
+
export type ContainerStatus = 'empty' | 'partial' | 'full' | 'error'
|
|
48
|
+
|
|
49
|
+
const BODY_LEGEND = {
|
|
50
|
+
empty: '#a8b8c4',
|
|
51
|
+
partial: '#7d96b0',
|
|
52
|
+
full: '#5a7c9a',
|
|
53
|
+
error: '#e9746b',
|
|
54
|
+
default: '#a8b8c4'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const LAMP_EMISSIVE_LEGEND = {
|
|
58
|
+
empty: '#222222',
|
|
59
|
+
partial: '#3388cc',
|
|
60
|
+
full: '#2266aa',
|
|
61
|
+
error: '#ff3333',
|
|
62
|
+
default: '#222222'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const NATURE: ComponentNature = {
|
|
66
|
+
mutable: false,
|
|
67
|
+
resizable: true,
|
|
68
|
+
rotatable: true,
|
|
69
|
+
properties: [
|
|
70
|
+
...gltfNatureProperties,
|
|
71
|
+
{
|
|
72
|
+
type: 'select',
|
|
73
|
+
label: 'fill',
|
|
74
|
+
name: 'fill',
|
|
75
|
+
property: {
|
|
76
|
+
options: [
|
|
77
|
+
{ display: 'Empty', value: 'empty' },
|
|
78
|
+
{ display: 'Partial', value: 'partial' },
|
|
79
|
+
{ display: 'Full', value: 'full' },
|
|
80
|
+
{ display: 'Error', value: 'error' }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
help: 'scene/component/generic-container'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 합성 순서: 안쪽부터 → ContainerAbstract → Placeable → Legendable → GltfComponent
|
|
89
|
+
// (GenericFacility 와 동일 패턴)
|
|
90
|
+
const Base = GltfComponent(Legendable(Placeable(ContainerAbstract))) as unknown as typeof Component
|
|
91
|
+
|
|
92
|
+
@sceneComponent('container')
|
|
93
|
+
export default class GenericContainer extends Base {
|
|
94
|
+
static legends: Record<string, LegendBinding> = {
|
|
95
|
+
bodyColor: { from: 'fill', legend: BODY_LEGEND },
|
|
96
|
+
lampEmissive: { from: 'fill', legend: LAMP_EMISSIVE_LEGEND }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static placement: PlacementArchetype = 'floor'
|
|
100
|
+
static align: Alignment = 'bottom'
|
|
101
|
+
static defaultDepth = (h: Heights) => h.operation - h.floor
|
|
102
|
+
|
|
103
|
+
get nature() {
|
|
104
|
+
return NATURE
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get anchors() {
|
|
108
|
+
return []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get actuators(): Record<string, ActuatorDef> {
|
|
112
|
+
return ((this.state as any).actuators as Record<string, ActuatorDef> | undefined) ?? {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get actuatorValues(): Record<string, number> {
|
|
116
|
+
return ((this.state as any).actuatorValues as Record<string, number> | undefined) ?? {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
containable(component: Component): boolean {
|
|
120
|
+
return component.isDescendible(this as any)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
buildRealObject() {
|
|
124
|
+
return new GenericContainer3D(this as any)
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,3 +15,7 @@ export type { AsrsCraneStatus } from './asrs-crane.js'
|
|
|
15
15
|
|
|
16
16
|
export { default as Spot } from './spot.js'
|
|
17
17
|
export { Spot3D } from './spot-3d.js'
|
|
18
|
+
|
|
19
|
+
export { default as GenericContainer } from './generic-container.js'
|
|
20
|
+
export type { ContainerStatus } from './generic-container.js'
|
|
21
|
+
export { GenericContainer3D } from './generic-container-3d.js'
|
package/src/pallet.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
3
|
*/
|
|
4
|
-
import { Component, ComponentNature,
|
|
4
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
5
5
|
import {
|
|
6
6
|
Carriable,
|
|
7
7
|
Legendable,
|
|
@@ -53,9 +53,14 @@ const NATURE: ComponentNature = {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// Carriable: a pallet can sit on AGV / Forklift / robot-arm gripper / Spot
|
|
56
|
-
// and also accept boxes / parcels as children (
|
|
57
|
-
// child-container behavior; Carriable only adds the
|
|
58
|
-
|
|
56
|
+
// and also accept boxes / parcels as children (ContainerAbstract base
|
|
57
|
+
// provides the child-container behavior; Carriable only adds the
|
|
58
|
+
// holder-mount hook).
|
|
59
|
+
//
|
|
60
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
61
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
62
|
+
// addObject DOM-skip gate. Pallet renders only as a 3D mesh.
|
|
63
|
+
const Base = Carriable(Legendable(Placeable(ContainerAbstract))) as unknown as typeof Component
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
66
|
* Pallet — a flat transport structure that goods are stacked and stored on.
|
|
@@ -108,13 +113,77 @@ export default class Pallet extends Base {
|
|
|
108
113
|
return component.isDescendible(this as any)
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
/**
|
|
116
|
+
/**
|
|
117
|
+
* 2D — top-down silhouette. Body is a flat rectangle (wood/plastic deck);
|
|
118
|
+
* `postrender()` adds the deck pattern + edge stroke so the pallet reads
|
|
119
|
+
* as a pallet instead of a featureless rectangle.
|
|
120
|
+
*/
|
|
112
121
|
render(ctx: CanvasRenderingContext2D) {
|
|
113
122
|
const { width, height, left, top } = this.state
|
|
114
123
|
ctx.beginPath()
|
|
115
124
|
ctx.rect(left, top, width, height)
|
|
116
125
|
}
|
|
117
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Deck pattern + edge stroke. Wood: parallel slats with darker grooves
|
|
129
|
+
* between (typical EUR pallet deck). Plastic: cross-cutout pattern
|
|
130
|
+
* suggesting the molded reinforcement ribs.
|
|
131
|
+
*
|
|
132
|
+
* Slats run along the *short* axis of the rectangle (= along the longer
|
|
133
|
+
* stringer direction in real life), so a 1200×800 pallet shows multiple
|
|
134
|
+
* narrow slats across the 1200mm dimension — matching the EUR layout.
|
|
135
|
+
*/
|
|
136
|
+
postrender(ctx: CanvasRenderingContext2D) {
|
|
137
|
+
super.postrender?.(ctx)
|
|
138
|
+
|
|
139
|
+
const { width, height, left, top } = this.state
|
|
140
|
+
const isPlastic = ((this.state as any).material as PalletMaterial) === 'plastic'
|
|
141
|
+
|
|
142
|
+
ctx.save()
|
|
143
|
+
|
|
144
|
+
if (!isPlastic) {
|
|
145
|
+
// Wood — slats. Run them along the longer axis so the grooves are
|
|
146
|
+
// perpendicular to the longer side (typical pallet appearance).
|
|
147
|
+
const longAxisHorizontal = width >= height
|
|
148
|
+
const slatCount = 5
|
|
149
|
+
const grooveColor = '#7a4f25'
|
|
150
|
+
ctx.fillStyle = grooveColor
|
|
151
|
+
if (longAxisHorizontal) {
|
|
152
|
+
// grooves vertical (X direction across the width)
|
|
153
|
+
const grooveW = Math.max(1, width * 0.012)
|
|
154
|
+
const slatW = (width - grooveW * (slatCount - 1)) / slatCount
|
|
155
|
+
for (let i = 1; i < slatCount; i++) {
|
|
156
|
+
const x = left + i * slatW + (i - 1) * grooveW
|
|
157
|
+
ctx.fillRect(x, top + height * 0.05, grooveW, height * 0.9)
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const grooveH = Math.max(1, height * 0.012)
|
|
161
|
+
const slatH = (height - grooveH * (slatCount - 1)) / slatCount
|
|
162
|
+
for (let i = 1; i < slatCount; i++) {
|
|
163
|
+
const y = top + i * slatH + (i - 1) * grooveH
|
|
164
|
+
ctx.fillRect(left + width * 0.05, y, width * 0.9, grooveH)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Plastic — cross + corner cutouts hint
|
|
169
|
+
ctx.strokeStyle = '#3a4956'
|
|
170
|
+
ctx.lineWidth = Math.max(1, Math.min(width, height) * 0.012)
|
|
171
|
+
ctx.beginPath()
|
|
172
|
+
ctx.moveTo(left + width * 0.5, top + height * 0.1)
|
|
173
|
+
ctx.lineTo(left + width * 0.5, top + height * 0.9)
|
|
174
|
+
ctx.moveTo(left + width * 0.1, top + height * 0.5)
|
|
175
|
+
ctx.lineTo(left + width * 0.9, top + height * 0.5)
|
|
176
|
+
ctx.stroke()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Edge stroke
|
|
180
|
+
ctx.strokeStyle = isPlastic ? '#2a3946' : '#5e3818'
|
|
181
|
+
ctx.lineWidth = 1
|
|
182
|
+
ctx.strokeRect(left, top, width, height)
|
|
183
|
+
|
|
184
|
+
ctx.restore()
|
|
185
|
+
}
|
|
186
|
+
|
|
118
187
|
get fillStyle() {
|
|
119
188
|
return (this.state.bodyColor as string) || '#a87644'
|
|
120
189
|
}
|
package/src/spot.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* the top face of the pad (overrides default attachPointFor).
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import { Component, ComponentNature,
|
|
32
|
+
import { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'
|
|
33
33
|
import {
|
|
34
34
|
CarrierHolder,
|
|
35
35
|
Placeable,
|
|
@@ -51,10 +51,14 @@ const NATURE: ComponentNature = {
|
|
|
51
51
|
help: 'scene/component/spot'
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).
|
|
55
55
|
// CarrierHolder mixin only publishes the attach-point hook; the actual
|
|
56
|
-
// child-list management comes from
|
|
57
|
-
|
|
56
|
+
// child-list management comes from things-scene's container abstract.
|
|
57
|
+
//
|
|
58
|
+
// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
|
|
59
|
+
// which forces `isHTMLElement(): true` and trips the 3D pipeline's
|
|
60
|
+
// addObject DOM-skip gate. Spot is purely 3D.
|
|
61
|
+
const Base = CarrierHolder(Placeable(ContainerAbstract)) as unknown as typeof Component
|
|
58
62
|
|
|
59
63
|
@sceneComponent('spot')
|
|
60
64
|
export default class Spot extends Base {
|
package/src/templates/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export default [
|
|
|
21
21
|
type: 'pallet',
|
|
22
22
|
top: 100,
|
|
23
23
|
left: 100,
|
|
24
|
-
width:
|
|
24
|
+
width: 80,
|
|
25
25
|
height: 80,
|
|
26
26
|
material: 'wood'
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ export default [
|
|
|
35
35
|
type: 'pallet',
|
|
36
36
|
top: 100,
|
|
37
37
|
left: 250,
|
|
38
|
-
width:
|
|
38
|
+
width: 80,
|
|
39
39
|
height: 80,
|
|
40
40
|
material: 'plastic'
|
|
41
41
|
}
|
|
@@ -78,7 +78,7 @@ export default [
|
|
|
78
78
|
top: 100,
|
|
79
79
|
left: 640,
|
|
80
80
|
width: 60,
|
|
81
|
-
height:
|
|
81
|
+
height: 80
|
|
82
82
|
}
|
|
83
83
|
},
|
|
84
84
|
{
|