@operato/scene-storage 10.0.0-beta.43 → 10.0.0-beta.46
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 +37 -0
- package/dist/box-3d.d.ts +2 -0
- package/dist/box-3d.js +103 -64
- package/dist/box-3d.js.map +1 -1
- package/dist/crane-3d.d.ts +10 -0
- package/dist/crane-3d.js +34 -5
- package/dist/crane-3d.js.map +1 -1
- package/dist/crane.d.ts +136 -6
- package/dist/crane.js +567 -46
- package/dist/crane.js.map +1 -1
- package/dist/pallet-3d.d.ts +2 -0
- package/dist/pallet-3d.js +103 -53
- package/dist/pallet-3d.js.map +1 -1
- package/dist/parcel-3d.d.ts +1 -0
- package/dist/parcel-3d.js +18 -1
- package/dist/parcel-3d.js.map +1 -1
- package/dist/rack-grid-3d.js +26 -8
- package/dist/rack-grid-3d.js.map +1 -1
- package/dist/rack-grid.d.ts +94 -10
- package/dist/rack-grid.js +468 -86
- package/dist/rack-grid.js.map +1 -1
- package/dist/storage-rack-3d.js +1 -1
- package/dist/storage-rack-3d.js.map +1 -1
- package/dist/storage-rack.d.ts +31 -6
- package/dist/storage-rack.js +96 -14
- package/dist/storage-rack.js.map +1 -1
- package/package.json +3 -3
- package/src/box-3d.ts +121 -68
- package/src/crane-3d.ts +34 -4
- package/src/crane.ts +615 -55
- package/src/pallet-3d.ts +122 -55
- package/src/parcel-3d.ts +19 -1
- package/src/rack-grid-3d.ts +31 -8
- package/src/rack-grid.ts +488 -82
- package/src/storage-rack-3d.ts +1 -1
- package/src/storage-rack.ts +96 -14
- package/test/test-coord-alignment.ts +2 -2
- package/test/test-crane-bay-match.ts +130 -0
- package/test/test-crane-binding-resolve.ts +168 -0
- package/test/test-crane-duration.ts +90 -0
- package/test/test-crane-rotation-reach.ts +218 -0
- package/test/test-rack-grid-3d-alignment.ts +235 -0
- package/test/test-rack-grid-3d-attach-real.ts +375 -0
- package/test/test-rack-grid-cell.ts +2 -2
- package/test/test-rack-grid-location.ts +2 -2
- package/test/test-rack-grid-occupied-slots.ts +165 -0
- package/test/test-rack-grid-picking-position.ts +154 -0
- package/test/test-rack-grid-slot-api.ts +483 -0
- package/test/test-slot-ids-enumeration.ts +137 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,43 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.0.0-beta.46](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.45...v10.0.0-beta.46) (2026-05-24)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
* capability 기반 simulate + binding + RackGrid 셀 리스트 정보 정확화 ([3dbcf66](https://github.com/things-scene/operato-scene/commit/3dbcf6645a1d0d6212523a85197b357380f9815a))
|
|
12
|
+
* **crane:** capability 기반 자동 simulate — 인접 SlottedHolder 의 slot ([16bba83](https://github.com/things-scene/operato-scene/commit/16bba83ecf1cc678fcaee78f04542e50f2bebd14))
|
|
13
|
+
* **crane:** Phase Z PR-2 — dispatcher 명시 작업 통합 main loop ([08f8e65](https://github.com/things-scene/operato-scene/commit/08f8e65cdc038c4dd15244c603c0bd002431747f))
|
|
14
|
+
* **slotted-holder:** occupiedSlotIds/emptySlotIds 에 filter predicate 추가 ([5adecd5](https://github.com/things-scene/operato-scene/commit/5adecd5c87eee2dc12d50c72e0842aec222b9084))
|
|
15
|
+
* **slotted-holder:** slotIds() capability — slot enumeration entry point ([a9de95a](https://github.com/things-scene/operato-scene/commit/a9de95a04dde1758fe2236d80dd177ffad9307ed))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### :bug: Bug Fix
|
|
19
|
+
|
|
20
|
+
* **crane:** bayKey = first segment (col), reach 박스 X 를 rail 폭으로 정정 ([b3a10e9](https://github.com/things-scene/operato-scene/commit/b3a10e96da0ce1739f8420df0f70210094d90f35))
|
|
21
|
+
* **crane:** rail axis = matrixWorld X — 2D rotation 매핑 무관 + duration 거리 비례 ([4944ea7](https://github.com/things-scene/operato-scene/commit/4944ea7a87d5c9205387ef8272e3d6cafb2a2c74))
|
|
22
|
+
* **crane:** reach 박스 = rail 전체 X + fork 최대 extend Z, row 별 대표 검사 ([9fb88e7](https://github.com/things-scene/operato-scene/commit/9fb88e7a7c7ca8e612a07692f3e399d2a9793ee5))
|
|
23
|
+
* **crane:** 마지막 shelf 도달 못 하는 carriageHeight clamp 의 magic 0.85 제거 ([e6a9c1a](https://github.com/things-scene/operato-scene/commit/e6a9c1af1b762f874ad7a584137efe9fba120fa4))
|
|
24
|
+
* **rack-grid:** get layout() 을 TABLE_LAYOUT 으로 복원 — isEmpty 시각 회귀 차단 ([82d9d79](https://github.com/things-scene/operato-scene/commit/82d9d79d4862658faba96ca8171a8ba1a4ae9552))
|
|
25
|
+
* **storage:** RackGrid SlottedHolder 4 메서드 자체 처리 + NaN safety + carrier carried placement ([84f0392](https://github.com/things-scene/operato-scene/commit/84f0392a6d3093c9a93efa15682538719d4a77e1))
|
|
26
|
+
* **storage:** RackGrid 의 handoff carrier follow + visible size 회복 ([22f51e8](https://github.com/things-scene/operato-scene/commit/22f51e8d8813c9e20d765ac87a687771e4d329fa))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### :house: Code Refactoring
|
|
30
|
+
|
|
31
|
+
* **storage:** public/private helper 이름의 cell* → slot* rename ([9de4ea0](https://github.com/things-scene/operato-scene/commit/9de4ea07f39fb349f7b52831aa7d4c3c8165f435))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## [10.0.0-beta.44](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.43...v10.0.0-beta.44) (2026-05-21)
|
|
36
|
+
|
|
37
|
+
**Note:** Version bump only for package @operato/scene-storage
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
6
43
|
## [10.0.0-beta.43](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.42...v10.0.0-beta.43) (2026-05-21)
|
|
7
44
|
|
|
8
45
|
**Note:** Version bump only for package @operato/scene-storage
|
package/dist/box-3d.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare class Box3D extends RealObjectGroup {
|
|
|
3
3
|
build(): void;
|
|
4
4
|
/** Wood crate — visible slats, 4 corner posts, open top. */
|
|
5
5
|
private buildWoodCrate;
|
|
6
|
+
private getWoodGeometries;
|
|
6
7
|
/** Plastic tote — solid molded walls + top stackable lip. */
|
|
7
8
|
private buildPlasticTote;
|
|
9
|
+
private getPlasticGeometries;
|
|
8
10
|
updateDimension(): void;
|
|
9
11
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
|
|
10
12
|
updateAlpha(): void;
|
package/dist/box-3d.js
CHANGED
|
@@ -14,6 +14,49 @@
|
|
|
14
14
|
import * as THREE from 'three';
|
|
15
15
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
16
16
|
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
17
|
+
// ── Material cache by bodyColor (shared across all Box3D instances) ──
|
|
18
|
+
const woodBodyMaterials = new Map();
|
|
19
|
+
const woodPostMaterials = new Map();
|
|
20
|
+
const plasticToteMaterials = new Map();
|
|
21
|
+
const plasticLipMaterials = new Map();
|
|
22
|
+
function getWoodBodyMaterial(bodyColor) {
|
|
23
|
+
let m = woodBodyMaterials.get(bodyColor);
|
|
24
|
+
if (!m) {
|
|
25
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0, roughness: 0.85 });
|
|
26
|
+
woodBodyMaterials.set(bodyColor, m);
|
|
27
|
+
}
|
|
28
|
+
return m;
|
|
29
|
+
}
|
|
30
|
+
function getWoodPostMaterial(bodyColor) {
|
|
31
|
+
let m = woodPostMaterials.get(bodyColor);
|
|
32
|
+
if (!m) {
|
|
33
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.8);
|
|
34
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0, roughness: 0.9 });
|
|
35
|
+
woodPostMaterials.set(bodyColor, m);
|
|
36
|
+
}
|
|
37
|
+
return m;
|
|
38
|
+
}
|
|
39
|
+
function getPlasticToteMaterial(bodyColor) {
|
|
40
|
+
let m = plasticToteMaterials.get(bodyColor);
|
|
41
|
+
if (!m) {
|
|
42
|
+
m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.05, roughness: 0.55 });
|
|
43
|
+
plasticToteMaterials.set(bodyColor, m);
|
|
44
|
+
}
|
|
45
|
+
return m;
|
|
46
|
+
}
|
|
47
|
+
function getPlasticLipMaterial(bodyColor) {
|
|
48
|
+
let m = plasticLipMaterials.get(bodyColor);
|
|
49
|
+
if (!m) {
|
|
50
|
+
const tint = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
51
|
+
m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.05, roughness: 0.55 });
|
|
52
|
+
plasticLipMaterials.set(bodyColor, m);
|
|
53
|
+
}
|
|
54
|
+
return m;
|
|
55
|
+
}
|
|
56
|
+
// ── Geometry cache by size (shared across all Box3D instances) ──
|
|
57
|
+
// Floor geo는 local 좌표 + mesh position 으로 두어 mesh 별 position 만 다름.
|
|
58
|
+
const woodGeoCache = new Map();
|
|
59
|
+
const plasticGeoCache = new Map();
|
|
17
60
|
export class Box3D extends RealObjectGroup {
|
|
18
61
|
build() {
|
|
19
62
|
super.build();
|
|
@@ -29,24 +72,32 @@ export class Box3D extends RealObjectGroup {
|
|
|
29
72
|
}
|
|
30
73
|
/** Wood crate — visible slats, 4 corner posts, open top. */
|
|
31
74
|
buildWoodCrate(width, height, depth, bodyColor) {
|
|
75
|
+
const { posts, slats, floor, floorY } = this.getWoodGeometries(width, height, depth);
|
|
76
|
+
const woodMaterial = getWoodBodyMaterial(bodyColor);
|
|
77
|
+
const postMaterial = getWoodPostMaterial(bodyColor);
|
|
78
|
+
const postMesh = new THREE.Mesh(posts, postMaterial);
|
|
79
|
+
postMesh.castShadow = true;
|
|
80
|
+
this.object3d.add(postMesh);
|
|
81
|
+
const slatMesh = new THREE.Mesh(slats, woodMaterial);
|
|
82
|
+
slatMesh.castShadow = true;
|
|
83
|
+
slatMesh.receiveShadow = true;
|
|
84
|
+
this.object3d.add(slatMesh);
|
|
85
|
+
const floorMesh = new THREE.Mesh(floor, woodMaterial);
|
|
86
|
+
floorMesh.position.set(0, floorY, 0);
|
|
87
|
+
floorMesh.receiveShadow = true;
|
|
88
|
+
this.object3d.add(floorMesh);
|
|
89
|
+
}
|
|
90
|
+
getWoodGeometries(width, height, depth) {
|
|
91
|
+
const key = `${width}|${height}|${depth}`;
|
|
92
|
+
let cached = woodGeoCache.get(key);
|
|
93
|
+
if (cached)
|
|
94
|
+
return cached;
|
|
32
95
|
const baseY = -depth / 2;
|
|
33
96
|
const wallThickness = Math.min(width, height) * 0.04;
|
|
34
97
|
const postW = wallThickness * 1.6;
|
|
35
98
|
const slatH = depth * 0.10;
|
|
36
99
|
const slatGap = slatH * 0.6;
|
|
37
100
|
const floorH = depth * 0.05;
|
|
38
|
-
const woodMaterial = new THREE.MeshStandardMaterial({
|
|
39
|
-
color: bodyColor,
|
|
40
|
-
metalness: 0,
|
|
41
|
-
roughness: 0.85
|
|
42
|
-
});
|
|
43
|
-
const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8);
|
|
44
|
-
const postMaterial = new THREE.MeshStandardMaterial({
|
|
45
|
-
color: postColor,
|
|
46
|
-
metalness: 0,
|
|
47
|
-
roughness: 0.9
|
|
48
|
-
});
|
|
49
|
-
// ── 4 corner posts ───────────────────────────────────────────────
|
|
50
101
|
const postGeos = [];
|
|
51
102
|
for (const xSign of [-1, 1]) {
|
|
52
103
|
for (const zSign of [-1, 1]) {
|
|
@@ -55,99 +106,87 @@ export class Box3D extends RealObjectGroup {
|
|
|
55
106
|
postGeos.push(post);
|
|
56
107
|
}
|
|
57
108
|
}
|
|
58
|
-
const
|
|
59
|
-
postMesh.castShadow = true;
|
|
60
|
-
this.object3d.add(postMesh);
|
|
61
|
-
// ── Slatted walls (horizontal slats with gaps) ───────────────────
|
|
109
|
+
const posts = BufferGeometryUtils.mergeGeometries(postGeos);
|
|
62
110
|
const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)));
|
|
63
111
|
const slatGeos = [];
|
|
64
112
|
for (let row = 0; row < slatRowCount; row++) {
|
|
65
113
|
const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2;
|
|
66
|
-
// Long walls (front / back)
|
|
67
114
|
for (const zSign of [-1, 1]) {
|
|
68
115
|
const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness);
|
|
69
116
|
slat.translate(0, y, zSign * (height / 2 - wallThickness / 2));
|
|
70
117
|
slatGeos.push(slat);
|
|
71
118
|
}
|
|
72
|
-
// Short walls (left / right)
|
|
73
119
|
for (const xSign of [-1, 1]) {
|
|
74
120
|
const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2);
|
|
75
121
|
slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0);
|
|
76
122
|
slatGeos.push(slat);
|
|
77
123
|
}
|
|
78
124
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const floorMesh = new THREE.Mesh(floorGeo, woodMaterial);
|
|
86
|
-
floorMesh.position.set(0, baseY + floorH / 2, 0);
|
|
87
|
-
floorMesh.receiveShadow = true;
|
|
88
|
-
this.object3d.add(floorMesh);
|
|
125
|
+
const slats = BufferGeometryUtils.mergeGeometries(slatGeos);
|
|
126
|
+
const floor = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2);
|
|
127
|
+
const floorY = baseY + floorH / 2;
|
|
128
|
+
cached = { posts, slats, floor, floorY };
|
|
129
|
+
woodGeoCache.set(key, cached);
|
|
130
|
+
return cached;
|
|
89
131
|
}
|
|
90
132
|
/** Plastic tote — solid molded walls + top stackable lip. */
|
|
91
133
|
buildPlasticTote(width, height, depth, bodyColor) {
|
|
134
|
+
const { walls, lip, floor, floorY } = this.getPlasticGeometries(width, height, depth);
|
|
135
|
+
const totMaterial = getPlasticToteMaterial(bodyColor);
|
|
136
|
+
const lipMaterial = getPlasticLipMaterial(bodyColor);
|
|
137
|
+
const wallMesh = new THREE.Mesh(walls, totMaterial);
|
|
138
|
+
wallMesh.castShadow = true;
|
|
139
|
+
wallMesh.receiveShadow = true;
|
|
140
|
+
this.object3d.add(wallMesh);
|
|
141
|
+
const lipMesh = new THREE.Mesh(lip, lipMaterial);
|
|
142
|
+
lipMesh.castShadow = true;
|
|
143
|
+
this.object3d.add(lipMesh);
|
|
144
|
+
const floorMesh = new THREE.Mesh(floor, totMaterial);
|
|
145
|
+
floorMesh.position.set(0, floorY, 0);
|
|
146
|
+
floorMesh.receiveShadow = true;
|
|
147
|
+
this.object3d.add(floorMesh);
|
|
148
|
+
}
|
|
149
|
+
getPlasticGeometries(width, height, depth) {
|
|
150
|
+
const key = `${width}|${height}|${depth}`;
|
|
151
|
+
let cached = plasticGeoCache.get(key);
|
|
152
|
+
if (cached)
|
|
153
|
+
return cached;
|
|
92
154
|
const baseY = -depth / 2;
|
|
93
155
|
const wallThickness = Math.min(width, height) * 0.05;
|
|
94
156
|
const lipH = depth * 0.06;
|
|
95
157
|
const floorH = depth * 0.06;
|
|
96
|
-
const totMaterial = new THREE.MeshStandardMaterial({
|
|
97
|
-
color: bodyColor,
|
|
98
|
-
metalness: 0.05,
|
|
99
|
-
roughness: 0.55
|
|
100
|
-
});
|
|
101
|
-
const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85);
|
|
102
|
-
const lipMaterial = new THREE.MeshStandardMaterial({
|
|
103
|
-
color: lipColor,
|
|
104
|
-
metalness: 0.05,
|
|
105
|
-
roughness: 0.55
|
|
106
|
-
});
|
|
107
|
-
// ── 4 solid walls ────────────────────────────────────────────────
|
|
108
158
|
const wallGeos = [];
|
|
109
159
|
const wallH = depth - lipH - floorH;
|
|
110
160
|
const wallY = baseY + floorH + wallH / 2;
|
|
111
|
-
// Long walls
|
|
112
161
|
for (const zSign of [-1, 1]) {
|
|
113
162
|
const wall = new THREE.BoxGeometry(width, wallH, wallThickness);
|
|
114
163
|
wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2));
|
|
115
164
|
wallGeos.push(wall);
|
|
116
165
|
}
|
|
117
|
-
// Short walls
|
|
118
166
|
for (const xSign of [-1, 1]) {
|
|
119
167
|
const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness);
|
|
120
168
|
wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0);
|
|
121
169
|
wallGeos.push(wall);
|
|
122
170
|
}
|
|
123
|
-
const
|
|
124
|
-
wallMesh.castShadow = true;
|
|
125
|
-
wallMesh.receiveShadow = true;
|
|
126
|
-
this.object3d.add(wallMesh);
|
|
127
|
-
// ── Top lip (stackable rim — slightly wider than walls) ──────────
|
|
171
|
+
const walls = BufferGeometryUtils.mergeGeometries(wallGeos);
|
|
128
172
|
const lipGeos = [];
|
|
129
173
|
const lipY = baseY + depth - lipH / 2;
|
|
130
|
-
// Long sides
|
|
131
174
|
for (const zSign of [-1, 1]) {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
lipGeos.push(
|
|
175
|
+
const lipG = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5);
|
|
176
|
+
lipG.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75));
|
|
177
|
+
lipGeos.push(lipG);
|
|
135
178
|
}
|
|
136
|
-
// Short sides
|
|
137
179
|
for (const xSign of [-1, 1]) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
lipGeos.push(
|
|
180
|
+
const lipG = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5);
|
|
181
|
+
lipG.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0);
|
|
182
|
+
lipGeos.push(lipG);
|
|
141
183
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
floorMesh.position.set(0, baseY + floorH / 2, 0);
|
|
149
|
-
floorMesh.receiveShadow = true;
|
|
150
|
-
this.object3d.add(floorMesh);
|
|
184
|
+
const lip = BufferGeometryUtils.mergeGeometries(lipGeos);
|
|
185
|
+
const floor = new THREE.BoxGeometry(width, floorH, height);
|
|
186
|
+
const floorY = baseY + floorH / 2;
|
|
187
|
+
cached = { walls, lip, floor, floorY };
|
|
188
|
+
plasticGeoCache.set(key, cached);
|
|
189
|
+
return cached;
|
|
151
190
|
}
|
|
152
191
|
updateDimension() { }
|
|
153
192
|
onchange(after, before) {
|
package/dist/box-3d.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"box-3d.js","sourceRoot":"","sources":["../src/box-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Box 3D — wood crate and plastic tote variants.\n *\n * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the\n * typical industrial wooden crate look. Forklift-friendly.\n * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.\n *\n * Both have a defined floor (so they look like containers, not just walls)\n * and an opening at top — as you'd expect from a real crate / tote that's\n * open or has a removable lid.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nexport class Box3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 300 } = this.component.state\n const material = (this.component.state.material as string) || 'wood'\n const bodyColor = (this.component.state.bodyColor as string) || '#a87644'\n\n if (material === 'plastic') {\n this.buildPlasticTote(width, height, depth, bodyColor)\n } else {\n this.buildWoodCrate(width, height, depth, bodyColor)\n }\n }\n\n /** Wood crate — visible slats, 4 corner posts, open top. */\n private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.04\n const postW = wallThickness * 1.6\n const slatH = depth * 0.10\n const slatGap = slatH * 0.6\n const floorH = depth * 0.05\n\n const woodMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0,\n roughness: 0.85\n })\n const postColor = new THREE.Color(bodyColor).multiplyScalar(0.8)\n const postMaterial = new THREE.MeshStandardMaterial({\n color: postColor,\n metalness: 0,\n roughness: 0.9\n })\n\n // ── 4 corner posts ───────────────────────────────────────────────\n const postGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(\n xSign * (width / 2 - postW / 2),\n baseY + depth / 2,\n zSign * (height / 2 - postW / 2)\n )\n postGeos.push(post)\n }\n }\n const postMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(postGeos), postMaterial)\n postMesh.castShadow = true\n this.object3d.add(postMesh)\n\n // ── Slatted walls (horizontal slats with gaps) ───────────────────\n const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))\n const slatGeos: THREE.BufferGeometry[] = []\n\n for (let row = 0; row < slatRowCount; row++) {\n const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2\n // Long walls (front / back)\n for (const zSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)\n slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))\n slatGeos.push(slat)\n }\n // Short walls (left / right)\n for (const xSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)\n slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)\n slatGeos.push(slat)\n }\n }\n const slatMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(slatGeos), woodMaterial)\n slatMesh.castShadow = true\n slatMesh.receiveShadow = true\n this.object3d.add(slatMesh)\n\n // ── Floor (bottom panel) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)\n const floorMesh = new THREE.Mesh(floorGeo, woodMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n /** Plastic tote — solid molded walls + top stackable lip. */\n private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.05\n const lipH = depth * 0.06\n const floorH = depth * 0.06\n\n const totMaterial = new THREE.MeshStandardMaterial({\n color: bodyColor,\n metalness: 0.05,\n roughness: 0.55\n })\n const lipColor = new THREE.Color(bodyColor).multiplyScalar(0.85)\n const lipMaterial = new THREE.MeshStandardMaterial({\n color: lipColor,\n metalness: 0.05,\n roughness: 0.55\n })\n\n // ── 4 solid walls ────────────────────────────────────────────────\n const wallGeos: THREE.BufferGeometry[] = []\n const wallH = depth - lipH - floorH\n const wallY = baseY + floorH + wallH / 2\n\n // Long walls\n for (const zSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(width, wallH, wallThickness)\n wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))\n wallGeos.push(wall)\n }\n // Short walls\n for (const xSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)\n wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)\n wallGeos.push(wall)\n }\n const wallMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wallGeos), totMaterial)\n wallMesh.castShadow = true\n wallMesh.receiveShadow = true\n this.object3d.add(wallMesh)\n\n // ── Top lip (stackable rim — slightly wider than walls) ──────────\n const lipGeos: THREE.BufferGeometry[] = []\n const lipY = baseY + depth - lipH / 2\n // Long sides\n for (const zSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)\n lip.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))\n lipGeos.push(lip)\n }\n // Short sides\n for (const xSign of [-1, 1]) {\n const lip = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)\n lip.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)\n lipGeos.push(lip)\n }\n const lipMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(lipGeos), lipMaterial)\n lipMesh.castShadow = true\n this.object3d.add(lipMesh)\n\n // ── Floor (solid bottom) ─────────────────────────────────────────\n const floorGeo = new THREE.BoxGeometry(width, floorH, height)\n const floorMesh = new THREE.Mesh(floorGeo, totMaterial)\n floorMesh.position.set(0, baseY + floorH / 2, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'material' in after ||\n 'bodyColor' 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"]}
|
|
1
|
+
{"version":3,"file":"box-3d.js","sourceRoot":"","sources":["../src/box-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Box 3D — wood crate and plastic tote variants.\n *\n * Wood crate: 4 vertical corner posts + horizontal slats with gaps → the\n * typical industrial wooden crate look. Forklift-friendly.\n * Plastic tote: solid 4 walls + visible top lip / handle cutouts. Stackable.\n *\n * Both have a defined floor (so they look like containers, not just walls)\n * and an opening at top — as you'd expect from a real crate / tote that's\n * open or has a removable lid.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\n// ── Material cache by bodyColor (shared across all Box3D instances) ──\nconst woodBodyMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst woodPostMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticToteMaterials = new Map<string, THREE.MeshStandardMaterial>()\nconst plasticLipMaterials = new Map<string, THREE.MeshStandardMaterial>()\n\nfunction getWoodBodyMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = woodBodyMaterials.get(bodyColor)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0, roughness: 0.85 })\n woodBodyMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getWoodPostMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = woodPostMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.8)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0, roughness: 0.9 })\n woodPostMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticToteMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticToteMaterials.get(bodyColor)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: bodyColor, metalness: 0.05, roughness: 0.55 })\n plasticToteMaterials.set(bodyColor, m)\n }\n return m\n}\n\nfunction getPlasticLipMaterial(bodyColor: string): THREE.MeshStandardMaterial {\n let m = plasticLipMaterials.get(bodyColor)\n if (!m) {\n const tint = new THREE.Color(bodyColor).multiplyScalar(0.85)\n m = new THREE.MeshStandardMaterial({ color: tint, metalness: 0.05, roughness: 0.55 })\n plasticLipMaterials.set(bodyColor, m)\n }\n return m\n}\n\n// ── Geometry cache by size (shared across all Box3D instances) ──\n// Floor geo는 local 좌표 + mesh position 으로 두어 mesh 별 position 만 다름.\nconst woodGeoCache = new Map<string, { posts: THREE.BufferGeometry; slats: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()\nconst plasticGeoCache = new Map<string, { walls: THREE.BufferGeometry; lip: THREE.BufferGeometry; floor: THREE.BufferGeometry; floorY: number }>()\n\nexport class Box3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 300 } = this.component.state\n const material = (this.component.state.material as string) || 'wood'\n const bodyColor = (this.component.state.bodyColor as string) || '#a87644'\n\n if (material === 'plastic') {\n this.buildPlasticTote(width, height, depth, bodyColor)\n } else {\n this.buildWoodCrate(width, height, depth, bodyColor)\n }\n }\n\n /** Wood crate — visible slats, 4 corner posts, open top. */\n private buildWoodCrate(width: number, height: number, depth: number, bodyColor: string) {\n const { posts, slats, floor, floorY } = this.getWoodGeometries(width, height, depth)\n\n const woodMaterial = getWoodBodyMaterial(bodyColor)\n const postMaterial = getWoodPostMaterial(bodyColor)\n\n const postMesh = new THREE.Mesh(posts, postMaterial)\n postMesh.castShadow = true\n this.object3d.add(postMesh)\n\n const slatMesh = new THREE.Mesh(slats, woodMaterial)\n slatMesh.castShadow = true\n slatMesh.receiveShadow = true\n this.object3d.add(slatMesh)\n\n const floorMesh = new THREE.Mesh(floor, woodMaterial)\n floorMesh.position.set(0, floorY, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n private getWoodGeometries(width: number, height: number, depth: number) {\n const key = `${width}|${height}|${depth}`\n let cached = woodGeoCache.get(key)\n if (cached) return cached\n\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.04\n const postW = wallThickness * 1.6\n const slatH = depth * 0.10\n const slatGap = slatH * 0.6\n const floorH = depth * 0.05\n\n const postGeos: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const post = new THREE.BoxGeometry(postW, depth, postW)\n post.translate(\n xSign * (width / 2 - postW / 2),\n baseY + depth / 2,\n zSign * (height / 2 - postW / 2)\n )\n postGeos.push(post)\n }\n }\n const posts = BufferGeometryUtils.mergeGeometries(postGeos)\n\n const slatRowCount = Math.max(2, Math.floor((depth - floorH) / (slatH + slatGap)))\n const slatGeos: THREE.BufferGeometry[] = []\n for (let row = 0; row < slatRowCount; row++) {\n const y = baseY + floorH + slatGap + row * (slatH + slatGap) + slatH / 2\n for (const zSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(width - postW * 2, slatH, wallThickness)\n slat.translate(0, y, zSign * (height / 2 - wallThickness / 2))\n slatGeos.push(slat)\n }\n for (const xSign of [-1, 1]) {\n const slat = new THREE.BoxGeometry(wallThickness, slatH, height - postW * 2)\n slat.translate(xSign * (width / 2 - wallThickness / 2), y, 0)\n slatGeos.push(slat)\n }\n }\n const slats = BufferGeometryUtils.mergeGeometries(slatGeos)\n\n const floor = new THREE.BoxGeometry(width - postW * 2, floorH, height - postW * 2)\n const floorY = baseY + floorH / 2\n\n cached = { posts, slats, floor, floorY }\n woodGeoCache.set(key, cached)\n return cached\n }\n\n /** Plastic tote — solid molded walls + top stackable lip. */\n private buildPlasticTote(width: number, height: number, depth: number, bodyColor: string) {\n const { walls, lip, floor, floorY } = this.getPlasticGeometries(width, height, depth)\n\n const totMaterial = getPlasticToteMaterial(bodyColor)\n const lipMaterial = getPlasticLipMaterial(bodyColor)\n\n const wallMesh = new THREE.Mesh(walls, totMaterial)\n wallMesh.castShadow = true\n wallMesh.receiveShadow = true\n this.object3d.add(wallMesh)\n\n const lipMesh = new THREE.Mesh(lip, lipMaterial)\n lipMesh.castShadow = true\n this.object3d.add(lipMesh)\n\n const floorMesh = new THREE.Mesh(floor, totMaterial)\n floorMesh.position.set(0, floorY, 0)\n floorMesh.receiveShadow = true\n this.object3d.add(floorMesh)\n }\n\n private getPlasticGeometries(width: number, height: number, depth: number) {\n const key = `${width}|${height}|${depth}`\n let cached = plasticGeoCache.get(key)\n if (cached) return cached\n\n const baseY = -depth / 2\n const wallThickness = Math.min(width, height) * 0.05\n const lipH = depth * 0.06\n const floorH = depth * 0.06\n\n const wallGeos: THREE.BufferGeometry[] = []\n const wallH = depth - lipH - floorH\n const wallY = baseY + floorH + wallH / 2\n for (const zSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(width, wallH, wallThickness)\n wall.translate(0, wallY, zSign * (height / 2 - wallThickness / 2))\n wallGeos.push(wall)\n }\n for (const xSign of [-1, 1]) {\n const wall = new THREE.BoxGeometry(wallThickness, wallH, height - 2 * wallThickness)\n wall.translate(xSign * (width / 2 - wallThickness / 2), wallY, 0)\n wallGeos.push(wall)\n }\n const walls = BufferGeometryUtils.mergeGeometries(wallGeos)\n\n const lipGeos: THREE.BufferGeometry[] = []\n const lipY = baseY + depth - lipH / 2\n for (const zSign of [-1, 1]) {\n const lipG = new THREE.BoxGeometry(width * 1.02, lipH, wallThickness * 1.5)\n lipG.translate(0, lipY, zSign * (height / 2 - wallThickness * 0.75))\n lipGeos.push(lipG)\n }\n for (const xSign of [-1, 1]) {\n const lipG = new THREE.BoxGeometry(wallThickness * 1.5, lipH, height * 1.02 - 2 * wallThickness * 1.5)\n lipG.translate(xSign * (width / 2 - wallThickness * 0.75), lipY, 0)\n lipGeos.push(lipG)\n }\n const lip = BufferGeometryUtils.mergeGeometries(lipGeos)\n\n const floor = new THREE.BoxGeometry(width, floorH, height)\n const floorY = baseY + floorH / 2\n\n cached = { walls, lip, floor, floorY }\n plasticGeoCache.set(key, cached)\n return cached\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'material' in after ||\n 'bodyColor' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
|
package/dist/crane-3d.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
3
3
|
export declare class Crane3D extends RealObjectGroup {
|
|
4
|
+
/**
|
|
5
|
+
* carriageHeight 의 실제 최댓값 — *mast 의 carriage 이동 가능 범위* 기반.
|
|
6
|
+
* mastH = D - railH * 2 - baseH - topFrameH - topGuideH (mast 의 실제 길이).
|
|
7
|
+
* carriage 의 *top edge 가 mast top 안에 머물도록* carriageH/2 추가 차감.
|
|
8
|
+
*
|
|
9
|
+
* 이전 D * 0.85 magic 대체 — base/top frame 의 실제 차지 비율과 무관한 추정값
|
|
10
|
+
* 이라 crane.depth = rack.depth 동일 모델에서도 carriage 가 rack 상위 cell
|
|
11
|
+
* 도달 못 함. 정확한 mastH 사용으로 *동일 size 모델에서 정상 도달* 보장.
|
|
12
|
+
*/
|
|
13
|
+
static _maxCarriageHeight(D: number, width: number, height: number): number;
|
|
4
14
|
private _forkGroup?;
|
|
5
15
|
private _carrierBaseY;
|
|
6
16
|
private _bladeMidZ;
|
package/dist/crane-3d.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* carriageHeight, forkExtension (±), forkLift
|
|
31
31
|
*/
|
|
32
32
|
import * as THREE from 'three';
|
|
33
|
-
import {
|
|
33
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
34
34
|
const MAST_COLOR = 0xff7a00; // mast — orange
|
|
35
35
|
const TROLLEY_COLOR = 0x3a4048; // base / top — dark charcoal
|
|
36
36
|
const CARRIAGE_COLOR = 0xffcc00; // carriage (shuttle) — yellow
|
|
@@ -39,6 +39,25 @@ const FORK_COLOR = 0xffcc00; // fork — yellow (same as carriage)
|
|
|
39
39
|
const RAIL_COLOR = 0x1a1f24; // rail — dark steel
|
|
40
40
|
const LAMP_OFF = 0x222222;
|
|
41
41
|
export class Crane3D extends RealObjectGroup {
|
|
42
|
+
/**
|
|
43
|
+
* carriageHeight 의 실제 최댓값 — *mast 의 carriage 이동 가능 범위* 기반.
|
|
44
|
+
* mastH = D - railH * 2 - baseH - topFrameH - topGuideH (mast 의 실제 길이).
|
|
45
|
+
* carriage 의 *top edge 가 mast top 안에 머물도록* carriageH/2 추가 차감.
|
|
46
|
+
*
|
|
47
|
+
* 이전 D * 0.85 magic 대체 — base/top frame 의 실제 차지 비율과 무관한 추정값
|
|
48
|
+
* 이라 crane.depth = rack.depth 동일 모델에서도 carriage 가 rack 상위 cell
|
|
49
|
+
* 도달 못 함. 정확한 mastH 사용으로 *동일 size 모델에서 정상 도달* 보장.
|
|
50
|
+
*/
|
|
51
|
+
static _maxCarriageHeight(D, width, height) {
|
|
52
|
+
const S = Math.min(width, height);
|
|
53
|
+
const railH = S * 0.04;
|
|
54
|
+
const baseH = S * 0.18;
|
|
55
|
+
const topFrameH = S * 0.1;
|
|
56
|
+
const topGuideH = S * 0.1;
|
|
57
|
+
const carriageH = S * 0.12;
|
|
58
|
+
const mastH = Math.max(D - railH * 2 - baseH - topFrameH - topGuideH, S * 0.5);
|
|
59
|
+
return Math.max(0, mastH - carriageH / 2);
|
|
60
|
+
}
|
|
42
61
|
_forkGroup;
|
|
43
62
|
_carrierBaseY = 0;
|
|
44
63
|
_bladeMidZ = 0;
|
|
@@ -72,7 +91,12 @@ export class Crane3D extends RealObjectGroup {
|
|
|
72
91
|
// Actuators
|
|
73
92
|
const D = numOr(depth, Math.max(width, height) * 4);
|
|
74
93
|
const carriageRaw = numOr(this.component.state.carriageHeight, this.component._canonicalDefault?.('carriageHeight') ?? D * 0.4);
|
|
75
|
-
|
|
94
|
+
// carriageHeight clamp — 실제 mast 의 carriage 이동 가능 범위 기반. helper 사용
|
|
95
|
+
// (render + onchange partial update 둘 다 동일 식). 이전엔 D * 0.85 magic 사용 —
|
|
96
|
+
// base/top frame 의 실제 차지 비율과 무관한 추정. crane.depth = rack.depth 동일
|
|
97
|
+
// 모델에서도 carriage 가 rack 상위 cell 도달 불가 → 14 levels rack 의 *마지막
|
|
98
|
+
// shelf 만 한 층 아래에서 포킹* 증상의 root cause 였음.
|
|
99
|
+
const carriageHeight = Math.max(0, Math.min(carriageRaw, Crane3D._maxCarriageHeight(D, width, height)));
|
|
76
100
|
const forkLength = numOr(this.component.state.forkLength, height * 0.6);
|
|
77
101
|
const forkExtensionRaw = numOr(this.component.state.forkExtension, 0);
|
|
78
102
|
const forkExtension = Math.max(-forkLength, Math.min(forkLength, forkExtensionRaw));
|
|
@@ -322,9 +346,11 @@ export class Crane3D extends RealObjectGroup {
|
|
|
322
346
|
let meshUpdated = false;
|
|
323
347
|
if (('carriageHeight' in after || 'forkLiftRT' in after) && this._carriageLiftGroup) {
|
|
324
348
|
const state = this.component.state;
|
|
325
|
-
const
|
|
349
|
+
const W = numOr(state.width, 100);
|
|
350
|
+
const H = numOr(state.height, 100);
|
|
351
|
+
const D = numOr(state.depth, Math.max(W, H) * 4);
|
|
326
352
|
const carriageRaw = numOr(state.carriageHeight, D * 0.4);
|
|
327
|
-
const carriageHeight = Math.max(0, Math.min(carriageRaw, D
|
|
353
|
+
const carriageHeight = Math.max(0, Math.min(carriageRaw, Crane3D._maxCarriageHeight(D, W, H)));
|
|
328
354
|
const forkLift = numOr(state.forkLiftRT, 0);
|
|
329
355
|
this._carriageLiftGroup.position.y = this._computeLiftGroupY(carriageHeight, forkLift);
|
|
330
356
|
meshUpdated = true;
|
|
@@ -415,7 +441,10 @@ export class Crane3D extends RealObjectGroup {
|
|
|
415
441
|
if (this._forkGroup) {
|
|
416
442
|
for (const child of this._forkGroup.children) {
|
|
417
443
|
const ctx = child.userData?.context;
|
|
418
|
-
|
|
444
|
+
// `ctx instanceof RealObject` 는 *cross-module identity 실패* 가능
|
|
445
|
+
// (Parcel3D 가 다른 things-scene 인스턴스의 RealObject 를 extend 한 경우
|
|
446
|
+
// instanceof 가 false 반환). duck-type 으로 완화 — *crane 자신만 제외*.
|
|
447
|
+
if (ctx && ctx !== this) {
|
|
419
448
|
child.position.z = this._bladeMidZ;
|
|
420
449
|
}
|
|
421
450
|
}
|