@operato/scene-wheel-sorter 9.1.1 → 10.0.0-beta.12

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 (40) hide show
  1. package/dist/conveyor-3d.d.ts +22 -0
  2. package/dist/conveyor-3d.js +275 -0
  3. package/dist/conveyor-3d.js.map +1 -0
  4. package/dist/conveyor-join-3d.d.ts +18 -0
  5. package/dist/conveyor-join-3d.js +297 -0
  6. package/dist/conveyor-join-3d.js.map +1 -0
  7. package/dist/conveyor-join-trapezoid-3d.d.ts +19 -0
  8. package/dist/conveyor-join-trapezoid-3d.js +243 -0
  9. package/dist/conveyor-join-trapezoid-3d.js.map +1 -0
  10. package/dist/conveyor-join-trapezoid.d.ts +2 -1
  11. package/dist/conveyor-join-trapezoid.js +10 -1
  12. package/dist/conveyor-join-trapezoid.js.map +1 -1
  13. package/dist/conveyor-join.d.ts +4 -1
  14. package/dist/conveyor-join.js +58 -7
  15. package/dist/conveyor-join.js.map +1 -1
  16. package/dist/conveyor.d.ts +2 -1
  17. package/dist/conveyor.js +22 -1
  18. package/dist/conveyor.js.map +1 -1
  19. package/dist/mixin-conveyor.js +1 -1
  20. package/dist/mixin-conveyor.js.map +1 -1
  21. package/dist/scanner-3d.d.ts +13 -0
  22. package/dist/scanner-3d.js +147 -0
  23. package/dist/scanner-3d.js.map +1 -0
  24. package/dist/scanner.d.ts +2 -1
  25. package/dist/scanner.js +10 -1
  26. package/dist/scanner.js.map +1 -1
  27. package/dist/templates/index.js +3 -3
  28. package/dist/templates/index.js.map +1 -1
  29. package/dist/wheel-sorter-3d.d.ts +14 -0
  30. package/dist/wheel-sorter-3d.js +192 -0
  31. package/dist/wheel-sorter-3d.js.map +1 -0
  32. package/dist/wheel-sorter.d.ts +2 -1
  33. package/dist/wheel-sorter.js +10 -1
  34. package/dist/wheel-sorter.js.map +1 -1
  35. package/package.json +15 -13
  36. package/translations/en.json +2 -1
  37. package/translations/ja.json +2 -1
  38. package/translations/ko.json +2 -1
  39. package/translations/ms.json +2 -1
  40. package/translations/zh.json +2 -1
@@ -0,0 +1,22 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class Conveyor3D extends RealObjectGroup {
3
+ get effectiveDepth(): number;
4
+ get position(): {
5
+ x: number;
6
+ y: any;
7
+ z: number;
8
+ };
9
+ /**
10
+ * syncFromObject3D 역변환 시 zPos 오프셋.
11
+ * position.y = zPos + effectiveDepth/2 이므로, 역변환도 effectiveDepth/2를 빼야 한다.
12
+ */
13
+ protected get syncZPosOffset(): number;
14
+ build(): void;
15
+ private buildRollers;
16
+ private buildBelt;
17
+ /** Control box (motor/controller housing) + status indicator light */
18
+ private buildControlBox;
19
+ updateDimension(): void;
20
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
21
+ updateAlpha(): void;
22
+ }
@@ -0,0 +1,275 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Straight Conveyor 3D Model
5
+ *
6
+ * Roller type: side rails + evenly-spaced cylindrical rollers + 4 legs with cross-bracing
7
+ * Belt type: side rails + two end drums with belt wrapping over them + 4 legs with cross-bracing
8
+ * Both types include a control box at one end and a status indicator light.
9
+ */
10
+ import * as THREE from 'three';
11
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
12
+ import { RealObjectGroup } from '@hatiolab/things-scene';
13
+ const FILL_COLORS = {
14
+ 0: 0xcccccc, // IDLE
15
+ 1: 0xafd0f1, // RUN
16
+ 2: 0xafd0f1, // REVERSE
17
+ 3: 0xffba00, // WARN
18
+ 4: 0xe9746b // ERROR
19
+ };
20
+ const FRAME_COLOR = 0x888899;
21
+ const ROLLER_COLOR = 0xaaaabc;
22
+ const CONTROL_BOX_COLOR = 0x556677;
23
+ const STATUS_EMISSIVE = {
24
+ 0: 0x333333,
25
+ 1: 0x44aaff,
26
+ 2: 0x44aaff,
27
+ 3: 0xffaa00,
28
+ 4: 0xff3333
29
+ };
30
+ export class Conveyor3D extends RealObjectGroup {
31
+ get effectiveDepth() {
32
+ const { width, height, depth } = this.component.state;
33
+ return depth || Math.min(width, height) * 1.5;
34
+ }
35
+ get position() {
36
+ const { zPos = 0 } = this.component.state;
37
+ return {
38
+ x: this.cx,
39
+ y: zPos + this.effectiveDepth / 2,
40
+ z: this.cy
41
+ };
42
+ }
43
+ /**
44
+ * syncFromObject3D 역변환 시 zPos 오프셋.
45
+ * position.y = zPos + effectiveDepth/2 이므로, 역변환도 effectiveDepth/2를 빼야 한다.
46
+ */
47
+ get syncZPosOffset() {
48
+ return this.effectiveDepth / 2;
49
+ }
50
+ build() {
51
+ super.build();
52
+ const { width, height, conveyorType = 0, value = 0, rollWidth = 10, orientation } = this.component.state;
53
+ const depth = this.effectiveDepth;
54
+ // orientation: 'horizontal' = 이송 가로, 'vertical' = 이송 세로, '' = auto(긴 방향)
55
+ const isVertical = orientation === 'vertical' ? true
56
+ : orientation === 'horizontal' ? false
57
+ : width < height;
58
+ // 프레임 두께: rollWidth(롤러 직경)와 동일 단위. 롤러를 감쌀 수 있어야 한다.
59
+ const rollerDiameter = Math.max(rollWidth, 2);
60
+ const frameH = rollerDiameter;
61
+ const legH = Math.max(depth - frameH, 0);
62
+ const railW = Math.max(height * 0.06, 2);
63
+ const legThickness = Math.max(railW * 0.8, 2);
64
+ const frameMaterial = new THREE.MeshStandardMaterial({
65
+ color: FRAME_COLOR,
66
+ metalness: 0.85,
67
+ roughness: 0.35
68
+ });
69
+ // --- Frame: side rails + legs + cross-bracing ---
70
+ const frameGeometries = [];
71
+ const railY = depth / 2 - frameH / 2;
72
+ // Side rails (along X)
73
+ for (const zSign of [-1, 1]) {
74
+ const rail = new THREE.BoxGeometry(width, frameH, railW);
75
+ rail.translate(0, railY, zSign * (height / 2 - railW / 2));
76
+ frameGeometries.push(rail);
77
+ }
78
+ // 4 legs at corners — 레일 바로 아래에 정렬
79
+ const legTopY = depth / 2 - frameH;
80
+ const legCenterY = legTopY - legH / 2;
81
+ for (const xSign of [-1, 1]) {
82
+ for (const zSign of [-1, 1]) {
83
+ const lx = xSign * (width / 2 - legThickness / 2);
84
+ const lz = zSign * (height / 2 - railW / 2);
85
+ const leg = new THREE.BoxGeometry(legThickness, legH, legThickness);
86
+ leg.translate(lx, legCenterY, lz);
87
+ frameGeometries.push(leg);
88
+ }
89
+ }
90
+ // Cross-bracing: 다리 사이 연결
91
+ const braceH = legThickness * 0.6;
92
+ const braceW = legThickness * 0.6;
93
+ const braceLen = height - railW;
94
+ const braceY = legTopY - legH * 0.35;
95
+ for (const xSign of [-1, 1]) {
96
+ const brace = new THREE.BoxGeometry(braceW, braceH, braceLen);
97
+ brace.translate(xSign * (width / 2 - legThickness / 2), braceY, 0);
98
+ frameGeometries.push(brace);
99
+ }
100
+ // Cross-bracing: 좌우 다리 연결
101
+ const braceLenX = width - legThickness;
102
+ for (const zSign of [-1, 1]) {
103
+ const brace = new THREE.BoxGeometry(braceLenX, braceH, braceW);
104
+ brace.translate(0, braceY, zSign * (height / 2 - railW / 2));
105
+ frameGeometries.push(brace);
106
+ }
107
+ const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial);
108
+ frameMesh.castShadow = true;
109
+ frameMesh.receiveShadow = true;
110
+ this.object3d.add(frameMesh);
111
+ // --- Rollers or Belt ---
112
+ if (conveyorType === 1) {
113
+ this.buildBelt(width, height, depth, frameH, railW, value);
114
+ }
115
+ else {
116
+ this.buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical);
117
+ }
118
+ // --- Control box (motor housing at one end) ---
119
+ this.buildControlBox(width, height, depth, frameH, railW, value);
120
+ }
121
+ buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical = false) {
122
+ const rollerRadius = Math.max(rollWidth / 2, 1);
123
+ const diameter = rollerRadius * 2;
124
+ const step = diameter + 1;
125
+ const rollerY = depth / 2 - frameH / 2;
126
+ const rollerGeometries = [];
127
+ if (isVertical) {
128
+ // 롤러 축 = X축(가로), 이송 방향 = Z축(세로)
129
+ const rollerLength = width - railW * 2 - 0.5;
130
+ const count = Math.max(1, Math.floor(height / step));
131
+ const totalSpan = (count - 1) * step;
132
+ const startZ = -totalSpan / 2;
133
+ for (let i = 0; i < count; i++) {
134
+ const z = startZ + i * step;
135
+ const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16);
136
+ roller.rotateZ(Math.PI / 2);
137
+ roller.translate(0, rollerY, z);
138
+ rollerGeometries.push(roller);
139
+ }
140
+ }
141
+ else {
142
+ // 롤러 축 = Z축(세로), 이송 방향 = X축(가로)
143
+ const rollerLength = height - railW * 2 - 0.5;
144
+ const count = Math.max(1, Math.floor(width / step));
145
+ const totalSpan = (count - 1) * step;
146
+ const startX = -totalSpan / 2;
147
+ for (let i = 0; i < count; i++) {
148
+ const x = startX + i * step;
149
+ const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16);
150
+ roller.rotateX(Math.PI / 2);
151
+ roller.translate(x, rollerY, 0);
152
+ rollerGeometries.push(roller);
153
+ }
154
+ }
155
+ if (rollerGeometries.length > 0) {
156
+ const rollerMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(rollerGeometries), new THREE.MeshStandardMaterial({ color: ROLLER_COLOR, metalness: 0.9, roughness: 0.2 }));
157
+ rollerMesh.castShadow = true;
158
+ this.object3d.add(rollerMesh);
159
+ }
160
+ }
161
+ buildBelt(width, height, depth, frameH, railW, value) {
162
+ const drumRadius = frameH * 0.38;
163
+ const drumLength = height - railW * 2;
164
+ const beltY = depth / 2 - frameH / 2;
165
+ const fillColor = FILL_COLORS[value] ?? FILL_COLORS[0];
166
+ const rollerMaterial = new THREE.MeshStandardMaterial({
167
+ color: ROLLER_COLOR,
168
+ metalness: 0.9,
169
+ roughness: 0.2
170
+ });
171
+ // Two end drums
172
+ const drumGeometries = [];
173
+ const drumInset = drumRadius * 1.5;
174
+ const leftDrumX = -width / 2 + drumInset;
175
+ const rightDrumX = width / 2 - drumInset;
176
+ for (const dx of [leftDrumX, rightDrumX]) {
177
+ const drum = new THREE.CylinderGeometry(drumRadius, drumRadius, drumLength, 16);
178
+ drum.rotateX(Math.PI / 2);
179
+ drum.translate(dx, beltY, 0);
180
+ drumGeometries.push(drum);
181
+ }
182
+ const drumMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(drumGeometries), rollerMaterial);
183
+ drumMesh.castShadow = true;
184
+ this.object3d.add(drumMesh);
185
+ // Belt surface: flat top + half-cylinder wraps around each drum end
186
+ const beltThickness = drumRadius * 0.12;
187
+ const beltMaterial = new THREE.MeshStandardMaterial({
188
+ color: fillColor,
189
+ metalness: 0.0,
190
+ roughness: 0.9
191
+ });
192
+ // Flat belt top surface spanning between drums
193
+ const flatLen = rightDrumX - leftDrumX;
194
+ const flatGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength);
195
+ const flatMesh = new THREE.Mesh(flatGeo, beltMaterial);
196
+ flatMesh.position.set(0, beltY + drumRadius, 0);
197
+ flatMesh.castShadow = true;
198
+ this.object3d.add(flatMesh);
199
+ // Belt wrap around drums (half-torus shape using extruded half-circle)
200
+ const wrapSegments = 12;
201
+ for (const [dx, angleStart] of [
202
+ [leftDrumX, Math.PI / 2],
203
+ [rightDrumX, -Math.PI / 2]
204
+ ]) {
205
+ const wrapGeometries = [];
206
+ for (let i = 0; i < wrapSegments; i++) {
207
+ const a0 = angleStart + (Math.PI * i) / wrapSegments;
208
+ const a1 = angleStart + (Math.PI * (i + 1)) / wrapSegments;
209
+ const r = drumRadius + beltThickness / 2;
210
+ const x0 = dx + Math.cos(a0) * r;
211
+ const y0 = beltY + Math.sin(a0) * r;
212
+ const x1 = dx + Math.cos(a1) * r;
213
+ const y1 = beltY + Math.sin(a1) * r;
214
+ const segLen = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) * 1.05;
215
+ const segAngle = Math.atan2(y1 - y0, x1 - x0);
216
+ const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength);
217
+ seg.rotateZ(segAngle);
218
+ seg.translate((x0 + x1) / 2, (y0 + y1) / 2, 0);
219
+ wrapGeometries.push(seg);
220
+ }
221
+ const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial);
222
+ wrapMesh.castShadow = true;
223
+ this.object3d.add(wrapMesh);
224
+ }
225
+ // Flat belt bottom (return path, slightly below)
226
+ const bottomGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85);
227
+ const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial);
228
+ bottomMesh.position.set(0, beltY - drumRadius, 0);
229
+ this.object3d.add(bottomMesh);
230
+ }
231
+ /** Control box (motor/controller housing) + status indicator light */
232
+ buildControlBox(width, height, depth, frameH, railW, value) {
233
+ const boxW = Math.max(width * 0.08, 4);
234
+ const boxH = frameH * 0.7;
235
+ const boxD = Math.max(height * 0.2, 4);
236
+ const railY = depth / 2 - frameH / 2;
237
+ // Box positioned at one end, attached to the side rail
238
+ const boxMaterial = new THREE.MeshStandardMaterial({
239
+ color: CONTROL_BOX_COLOR,
240
+ metalness: 0.6,
241
+ roughness: 0.4
242
+ });
243
+ const boxGeo = new THREE.BoxGeometry(boxW, boxH, boxD);
244
+ const boxMesh = new THREE.Mesh(boxGeo, boxMaterial);
245
+ boxMesh.position.set(-width / 2 + boxW / 2 + railW, railY - frameH / 2 - boxH / 2 + boxH * 0.15, -height / 2 + railW + boxD / 2);
246
+ boxMesh.castShadow = true;
247
+ this.object3d.add(boxMesh);
248
+ // Status indicator light on top of control box
249
+ const lightR = Math.min(boxW, boxD) * 0.25;
250
+ const lightH = lightR * 1.2;
251
+ const emissiveColor = STATUS_EMISSIVE[value] ?? STATUS_EMISSIVE[0];
252
+ const lightIntensity = value > 0 ? 1.5 : 0.2;
253
+ const lightMaterial = new THREE.MeshStandardMaterial({
254
+ color: emissiveColor,
255
+ emissive: emissiveColor,
256
+ emissiveIntensity: lightIntensity,
257
+ metalness: 0.0,
258
+ roughness: 0.3
259
+ });
260
+ const lightGeo = new THREE.CylinderGeometry(lightR, lightR * 0.8, lightH, 12);
261
+ const lightMesh = new THREE.Mesh(lightGeo, lightMaterial);
262
+ lightMesh.position.set(boxMesh.position.x, boxMesh.position.y + boxH / 2 + lightH / 2, boxMesh.position.z);
263
+ this.object3d.add(lightMesh);
264
+ }
265
+ updateDimension() { }
266
+ onchange(after, before) {
267
+ if ('value' in after || 'conveyorType' in after || 'rollWidth' in after || 'orientation' in after || 'width' in after || 'height' in after || 'depth' in after) {
268
+ this.update();
269
+ return;
270
+ }
271
+ super.onchange(after, before);
272
+ }
273
+ updateAlpha() { }
274
+ }
275
+ //# sourceMappingURL=conveyor-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conveyor-3d.js","sourceRoot":"","sources":["../src/conveyor-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Straight Conveyor 3D Model\n *\n * Roller type: side rails + evenly-spaced cylindrical rollers + 4 legs with cross-bracing\n * Belt type: side rails + two end drums with belt wrapping over them + 4 legs with cross-bracing\n * Both types include a control box at one end and a status indicator light.\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst FILL_COLORS: Record<number, number> = {\n 0: 0xcccccc, // IDLE\n 1: 0xafd0f1, // RUN\n 2: 0xafd0f1, // REVERSE\n 3: 0xffba00, // WARN\n 4: 0xe9746b // ERROR\n}\n\nconst FRAME_COLOR = 0x888899\nconst ROLLER_COLOR = 0xaaaabc\nconst CONTROL_BOX_COLOR = 0x556677\nconst STATUS_EMISSIVE: Record<number, number> = {\n 0: 0x333333,\n 1: 0x44aaff,\n 2: 0x44aaff,\n 3: 0xffaa00,\n 4: 0xff3333\n}\n\nexport class Conveyor3D extends RealObjectGroup {\n get effectiveDepth(): number {\n const { width, height, depth } = this.component.state\n return depth || Math.min(width, height) * 1.5\n }\n\n get position() {\n const { zPos = 0 } = this.component.state\n return {\n x: this.cx,\n y: zPos + this.effectiveDepth / 2,\n z: this.cy\n }\n }\n\n /**\n * syncFromObject3D 역변환 시 zPos 오프셋.\n * position.y = zPos + effectiveDepth/2 이므로, 역변환도 effectiveDepth/2를 빼야 한다.\n */\n protected get syncZPosOffset(): number {\n return this.effectiveDepth / 2\n }\n\n build() {\n super.build()\n\n const { width, height, conveyorType = 0, value = 0, rollWidth = 10, orientation } = this.component.state\n const depth = this.effectiveDepth\n\n // orientation: 'horizontal' = 이송 가로, 'vertical' = 이송 세로, '' = auto(긴 방향)\n const isVertical = orientation === 'vertical' ? true\n : orientation === 'horizontal' ? false\n : width < height\n\n // 프레임 두께: rollWidth(롤러 직경)와 동일 단위. 롤러를 감쌀 수 있어야 한다.\n const rollerDiameter = Math.max(rollWidth as number, 2)\n const frameH = rollerDiameter\n const legH = Math.max(depth - frameH, 0)\n const railW = Math.max(height * 0.06, 2)\n const legThickness = Math.max(railW * 0.8, 2)\n\n const frameMaterial = new THREE.MeshStandardMaterial({\n color: FRAME_COLOR,\n metalness: 0.85,\n roughness: 0.35\n })\n\n // --- Frame: side rails + legs + cross-bracing ---\n const frameGeometries: THREE.BufferGeometry[] = []\n const railY = depth / 2 - frameH / 2\n\n // Side rails (along X)\n for (const zSign of [-1, 1]) {\n const rail = new THREE.BoxGeometry(width, frameH, railW)\n rail.translate(0, railY, zSign * (height / 2 - railW / 2))\n frameGeometries.push(rail)\n }\n\n // 4 legs at corners — 레일 바로 아래에 정렬\n const legTopY = depth / 2 - frameH\n const legCenterY = legTopY - legH / 2\n\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const lx = xSign * (width / 2 - legThickness / 2)\n const lz = zSign * (height / 2 - railW / 2)\n const leg = new THREE.BoxGeometry(legThickness, legH, legThickness)\n leg.translate(lx, legCenterY, lz)\n frameGeometries.push(leg)\n }\n }\n\n // Cross-bracing: 다리 사이 연결\n const braceH = legThickness * 0.6\n const braceW = legThickness * 0.6\n const braceLen = height - railW\n const braceY = legTopY - legH * 0.35\n\n for (const xSign of [-1, 1]) {\n const brace = new THREE.BoxGeometry(braceW, braceH, braceLen)\n brace.translate(xSign * (width / 2 - legThickness / 2), braceY, 0)\n frameGeometries.push(brace)\n }\n\n // Cross-bracing: 좌우 다리 연결\n const braceLenX = width - legThickness\n for (const zSign of [-1, 1]) {\n const brace = new THREE.BoxGeometry(braceLenX, braceH, braceW)\n brace.translate(0, braceY, zSign * (height / 2 - railW / 2))\n frameGeometries.push(brace)\n }\n\n const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial)\n frameMesh.castShadow = true\n frameMesh.receiveShadow = true\n this.object3d.add(frameMesh)\n\n // --- Rollers or Belt ---\n if (conveyorType === 1) {\n this.buildBelt(width, height, depth, frameH, railW, value)\n } else {\n this.buildRollers(width, height, depth, frameH, railW, rollWidth as number, isVertical)\n }\n\n // --- Control box (motor housing at one end) ---\n this.buildControlBox(width, height, depth, frameH, railW, value)\n }\n\n private buildRollers(width: number, height: number, depth: number, frameH: number, railW: number, rollWidth: number, isVertical: boolean = false) {\n const rollerRadius = Math.max(rollWidth / 2, 1)\n const diameter = rollerRadius * 2\n const step = diameter + 1\n const rollerY = depth / 2 - frameH / 2\n\n const rollerGeometries: THREE.BufferGeometry[] = []\n\n if (isVertical) {\n // 롤러 축 = X축(가로), 이송 방향 = Z축(세로)\n const rollerLength = width - railW * 2 - 0.5\n const count = Math.max(1, Math.floor(height / step))\n const totalSpan = (count - 1) * step\n const startZ = -totalSpan / 2\n\n for (let i = 0; i < count; i++) {\n const z = startZ + i * step\n const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)\n roller.rotateZ(Math.PI / 2)\n roller.translate(0, rollerY, z)\n rollerGeometries.push(roller)\n }\n } else {\n // 롤러 축 = Z축(세로), 이송 방향 = X축(가로)\n const rollerLength = height - railW * 2 - 0.5\n const count = Math.max(1, Math.floor(width / step))\n const totalSpan = (count - 1) * step\n const startX = -totalSpan / 2\n\n for (let i = 0; i < count; i++) {\n const x = startX + i * step\n const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)\n roller.rotateX(Math.PI / 2)\n roller.translate(x, rollerY, 0)\n rollerGeometries.push(roller)\n }\n }\n\n if (rollerGeometries.length > 0) {\n const rollerMesh = new THREE.Mesh(\n BufferGeometryUtils.mergeGeometries(rollerGeometries),\n new THREE.MeshStandardMaterial({ color: ROLLER_COLOR, metalness: 0.9, roughness: 0.2 })\n )\n rollerMesh.castShadow = true\n this.object3d.add(rollerMesh)\n }\n }\n\n private buildBelt(width: number, height: number, depth: number, frameH: number, railW: number, value: number) {\n const drumRadius = frameH * 0.38\n const drumLength = height - railW * 2\n const beltY = depth / 2 - frameH / 2\n const fillColor = FILL_COLORS[value] ?? FILL_COLORS[0]\n\n const rollerMaterial = new THREE.MeshStandardMaterial({\n color: ROLLER_COLOR,\n metalness: 0.9,\n roughness: 0.2\n })\n\n // Two end drums\n const drumGeometries: THREE.BufferGeometry[] = []\n const drumInset = drumRadius * 1.5\n const leftDrumX = -width / 2 + drumInset\n const rightDrumX = width / 2 - drumInset\n\n for (const dx of [leftDrumX, rightDrumX]) {\n const drum = new THREE.CylinderGeometry(drumRadius, drumRadius, drumLength, 16)\n drum.rotateX(Math.PI / 2)\n drum.translate(dx, beltY, 0)\n drumGeometries.push(drum)\n }\n\n const drumMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(drumGeometries), rollerMaterial)\n drumMesh.castShadow = true\n this.object3d.add(drumMesh)\n\n // Belt surface: flat top + half-cylinder wraps around each drum end\n const beltThickness = drumRadius * 0.12\n const beltMaterial = new THREE.MeshStandardMaterial({\n color: fillColor,\n metalness: 0.0,\n roughness: 0.9\n })\n\n // Flat belt top surface spanning between drums\n const flatLen = rightDrumX - leftDrumX\n const flatGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength)\n const flatMesh = new THREE.Mesh(flatGeo, beltMaterial)\n flatMesh.position.set(0, beltY + drumRadius, 0)\n flatMesh.castShadow = true\n this.object3d.add(flatMesh)\n\n // Belt wrap around drums (half-torus shape using extruded half-circle)\n const wrapSegments = 12\n for (const [dx, angleStart] of [\n [leftDrumX, Math.PI / 2],\n [rightDrumX, -Math.PI / 2]\n ] as [number, number][]) {\n const wrapGeometries: THREE.BufferGeometry[] = []\n for (let i = 0; i < wrapSegments; i++) {\n const a0 = angleStart + (Math.PI * i) / wrapSegments\n const a1 = angleStart + (Math.PI * (i + 1)) / wrapSegments\n const r = drumRadius + beltThickness / 2\n\n const x0 = dx + Math.cos(a0) * r\n const y0 = beltY + Math.sin(a0) * r\n const x1 = dx + Math.cos(a1) * r\n const y1 = beltY + Math.sin(a1) * r\n\n const segLen = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) * 1.05\n const segAngle = Math.atan2(y1 - y0, x1 - x0)\n\n const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength)\n seg.rotateZ(segAngle)\n seg.translate((x0 + x1) / 2, (y0 + y1) / 2, 0)\n wrapGeometries.push(seg)\n }\n const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial)\n wrapMesh.castShadow = true\n this.object3d.add(wrapMesh)\n }\n\n // Flat belt bottom (return path, slightly below)\n const bottomGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)\n const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial)\n bottomMesh.position.set(0, beltY - drumRadius, 0)\n this.object3d.add(bottomMesh)\n }\n\n /** Control box (motor/controller housing) + status indicator light */\n private buildControlBox(\n width: number,\n height: number,\n depth: number,\n frameH: number,\n railW: number,\n value: number\n ) {\n const boxW = Math.max(width * 0.08, 4)\n const boxH = frameH * 0.7\n const boxD = Math.max(height * 0.2, 4)\n const railY = depth / 2 - frameH / 2\n\n // Box positioned at one end, attached to the side rail\n const boxMaterial = new THREE.MeshStandardMaterial({\n color: CONTROL_BOX_COLOR,\n metalness: 0.6,\n roughness: 0.4\n })\n\n const boxGeo = new THREE.BoxGeometry(boxW, boxH, boxD)\n const boxMesh = new THREE.Mesh(boxGeo, boxMaterial)\n boxMesh.position.set(\n -width / 2 + boxW / 2 + railW,\n railY - frameH / 2 - boxH / 2 + boxH * 0.15,\n -height / 2 + railW + boxD / 2\n )\n boxMesh.castShadow = true\n this.object3d.add(boxMesh)\n\n // Status indicator light on top of control box\n const lightR = Math.min(boxW, boxD) * 0.25\n const lightH = lightR * 1.2\n const emissiveColor = STATUS_EMISSIVE[value] ?? STATUS_EMISSIVE[0]\n const lightIntensity = value > 0 ? 1.5 : 0.2\n\n const lightMaterial = new THREE.MeshStandardMaterial({\n color: emissiveColor,\n emissive: emissiveColor,\n emissiveIntensity: lightIntensity,\n metalness: 0.0,\n roughness: 0.3\n })\n\n const lightGeo = new THREE.CylinderGeometry(lightR, lightR * 0.8, lightH, 12)\n const lightMesh = new THREE.Mesh(lightGeo, lightMaterial)\n lightMesh.position.set(\n boxMesh.position.x,\n boxMesh.position.y + boxH / 2 + lightH / 2,\n boxMesh.position.z\n )\n this.object3d.add(lightMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if ('value' in after || 'conveyorType' in after || 'rollWidth' in after || 'orientation' in after || 'width' in after || 'height' in after || 'depth' in after) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { RealObjectGroup } from '@hatiolab/things-scene';
2
+ export declare class ConveyorJoin3D extends RealObjectGroup {
3
+ get effectiveDepth(): number;
4
+ get position(): {
5
+ x: number;
6
+ y: any;
7
+ z: number;
8
+ };
9
+ build(): void;
10
+ private addBrace;
11
+ private buildArcRail;
12
+ private buildRollers;
13
+ private buildBelt;
14
+ private buildControlBox;
15
+ updateDimension(): void;
16
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
17
+ updateAlpha(): void;
18
+ }
@@ -0,0 +1,297 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Curved Conveyor (ConveyorJoin) 3D Model
5
+ *
6
+ * A curved conveyor section that changes direction of travel (e.g., 90° turn).
7
+ * Key feature: conical (tapered) rollers — wider at outer radius, narrower at
8
+ * inner radius — ensuring uniform belt speed across the curve width.
9
+ * Structure: curved side rails + conical rollers/belt + end caps + legs
10
+ * + cross-bracing + control box with status light.
11
+ */
12
+ import * as THREE from 'three';
13
+ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
14
+ import { RealObjectGroup } from '@hatiolab/things-scene';
15
+ const FILL_COLORS = {
16
+ 0: 0xcccccc, // IDLE
17
+ 1: 0xafd0f1, // RUN
18
+ 2: 0xafd0f1, // REVERSE
19
+ 3: 0xffba00, // WARN
20
+ 4: 0xe9746b // ERROR
21
+ };
22
+ const FRAME_COLOR = 0x888899;
23
+ const ROLLER_COLOR = 0xaaaabc;
24
+ const CONTROL_BOX_COLOR = 0x556677;
25
+ const STATUS_EMISSIVE = {
26
+ 0: 0x333333,
27
+ 1: 0x44aaff,
28
+ 2: 0x44aaff,
29
+ 3: 0xffaa00,
30
+ 4: 0xff3333
31
+ };
32
+ /**
33
+ * BoxGeometry의 X축(길이방향)을 XZ 평면의 (dx, dz) 방향으로 정렬하는 Y축 회전각.
34
+ * Three.js rotateY 컨벤션: +X → -Z 가 π/2.
35
+ */
36
+ function boxAlignAngle(dx, dz) {
37
+ return -Math.atan2(dz, dx);
38
+ }
39
+ /**
40
+ * CylinderGeometry가 rotateX(π/2) 후 Z축 방향이 되었을 때,
41
+ * Z축을 XZ 평면의 (dx, dz) 방향으로 정렬하는 Y축 회전각.
42
+ */
43
+ function cylinderAlignAngle(dx, dz) {
44
+ return Math.atan2(dx, dz);
45
+ }
46
+ /** 호 위의 점 (x, z) in XZ plane */
47
+ function arcPoint(rx, ry, angle) {
48
+ return { x: rx * Math.sin(angle), z: -ry * Math.cos(angle) };
49
+ }
50
+ export class ConveyorJoin3D extends RealObjectGroup {
51
+ get effectiveDepth() {
52
+ const { rx = 100, ratio = 50, depth } = this.component.state;
53
+ const outerR = Math.abs(rx);
54
+ const beltWidth = outerR - (outerR * Math.abs(ratio)) / 100;
55
+ return depth || beltWidth * 1.5;
56
+ }
57
+ get position() {
58
+ const { zPos = 0 } = this.component.state;
59
+ return {
60
+ x: this.cx,
61
+ y: zPos + this.effectiveDepth / 2,
62
+ z: this.cy
63
+ };
64
+ }
65
+ build() {
66
+ super.build();
67
+ const { rx: _rx = 100, ry: _ry = 100, ratio = 50, startAngle = 0, endAngle = Math.PI / 2, value = 0, conveyorType = 0, rollWidth = 10 } = this.component.state;
68
+ const depth = this.effectiveDepth;
69
+ const outerRx = Math.abs(_rx);
70
+ const outerRy = Math.abs(_ry);
71
+ const innerRx = (outerRx * Math.abs(ratio)) / 100;
72
+ const innerRy = (outerRy * Math.abs(ratio)) / 100;
73
+ const beltWidth = outerRx - innerRx;
74
+ // Conveyor3D와 동일: frameH = rollWidth 기반
75
+ const rollerDiameter = Math.max(rollWidth, 2);
76
+ const frameH = rollerDiameter;
77
+ const legH = Math.max(depth - frameH, 0);
78
+ const railW = Math.max(beltWidth * 0.06, 2);
79
+ const legThickness = Math.max(railW * 0.8, 2);
80
+ const railY = depth / 2 - frameH / 2;
81
+ const arcSpan = endAngle - startAngle;
82
+ const segments = Math.max(12, Math.floor((Math.abs(arcSpan) * outerRx) / 5));
83
+ const frameMaterial = new THREE.MeshStandardMaterial({
84
+ color: FRAME_COLOR,
85
+ metalness: 0.85,
86
+ roughness: 0.35
87
+ });
88
+ // --- Side rails (box segments along inner/outer arcs) ---
89
+ const frameGeometries = [];
90
+ this.buildArcRail(frameGeometries, outerRx, outerRy, railW, startAngle, endAngle, segments, frameH, railY);
91
+ this.buildArcRail(frameGeometries, innerRx, innerRy, railW, startAngle, endAngle, segments, frameH, railY);
92
+ // End caps (straight segments connecting inner/outer at start and end angles)
93
+ for (const angle of [startAngle, endAngle]) {
94
+ const o = arcPoint(outerRx, outerRy, angle);
95
+ const inn = arcPoint(innerRx, innerRy, angle);
96
+ const dx = o.x - inn.x;
97
+ const dz = o.z - inn.z;
98
+ const capLen = Math.sqrt(dx * dx + dz * dz);
99
+ const cap = new THREE.BoxGeometry(capLen, frameH, railW);
100
+ cap.rotateY(boxAlignAngle(dx, dz));
101
+ cap.translate((o.x + inn.x) / 2, railY, (o.z + inn.z) / 2);
102
+ frameGeometries.push(cap);
103
+ }
104
+ // 4 legs at corners
105
+ const legTopY = depth / 2 - frameH;
106
+ const legCenterY = legTopY - legH / 2;
107
+ const legPositions = [];
108
+ for (const angle of [startAngle, endAngle]) {
109
+ for (const [rx, ry] of [
110
+ [outerRx - railW, outerRy - railW],
111
+ [innerRx + railW, innerRy + railW]
112
+ ]) {
113
+ const p = arcPoint(rx, ry, angle);
114
+ const leg = new THREE.BoxGeometry(legThickness, legH, legThickness);
115
+ leg.translate(p.x, legCenterY, p.z);
116
+ frameGeometries.push(leg);
117
+ legPositions.push([p.x, p.z]);
118
+ }
119
+ }
120
+ // Cross-bracing
121
+ const braceSize = legThickness * 0.6;
122
+ const braceY = legTopY - legH * 0.35;
123
+ // Connect outer-to-inner at each end angle
124
+ for (let i = 0; i < legPositions.length; i += 2) {
125
+ const [x0, z0] = legPositions[i];
126
+ const [x1, z1] = legPositions[i + 1];
127
+ this.addBrace(frameGeometries, x0, z0, x1, z1, braceSize, braceY);
128
+ }
129
+ // Connect start-to-end along outer side and inner side
130
+ for (const side of [0, 1]) {
131
+ const [x0, z0] = legPositions[side];
132
+ const [x1, z1] = legPositions[side + 2];
133
+ this.addBrace(frameGeometries, x0, z0, x1, z1, braceSize, braceY);
134
+ }
135
+ const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial);
136
+ frameMesh.castShadow = true;
137
+ frameMesh.receiveShadow = true;
138
+ this.object3d.add(frameMesh);
139
+ // --- Rollers or Belt ---
140
+ if (conveyorType === 1) {
141
+ this.buildBelt(outerRx, outerRy, innerRx, innerRy, railW, startAngle, endAngle, segments, depth, frameH, value);
142
+ }
143
+ else {
144
+ this.buildRollers(outerRx, outerRy, innerRx, innerRy, railW, startAngle, endAngle, depth, frameH, rollWidth, beltWidth);
145
+ }
146
+ // --- Control box + status light ---
147
+ this.buildControlBox(outerRx, outerRy, innerRx, innerRy, startAngle, depth, frameH, railW, value);
148
+ }
149
+ addBrace(geometries, x0, z0, x1, z1, braceSize, braceY) {
150
+ const dx = x1 - x0;
151
+ const dz = z1 - z0;
152
+ const braceLen = Math.sqrt(dx * dx + dz * dz);
153
+ const brace = new THREE.BoxGeometry(braceLen, braceSize, braceSize);
154
+ brace.rotateY(boxAlignAngle(dx, dz));
155
+ brace.translate((x0 + x1) / 2, braceY, (z0 + z1) / 2);
156
+ geometries.push(brace);
157
+ }
158
+ buildArcRail(geometries, rx, ry, railW, startAngle, endAngle, segments, frameH, railY) {
159
+ const arcSpan = endAngle - startAngle;
160
+ for (let i = 0; i < segments; i++) {
161
+ const a0 = startAngle + (arcSpan * i) / segments;
162
+ const a1 = startAngle + (arcSpan * (i + 1)) / segments;
163
+ const p0 = arcPoint(rx, ry, a0);
164
+ const p1 = arcPoint(rx, ry, a1);
165
+ const dx = p1.x - p0.x;
166
+ const dz = p1.z - p0.z;
167
+ const segLen = Math.sqrt(dx * dx + dz * dz) * 1.03;
168
+ const seg = new THREE.BoxGeometry(segLen, frameH, railW);
169
+ seg.rotateY(boxAlignAngle(dx, dz));
170
+ seg.translate((p0.x + p1.x) / 2, railY, (p0.z + p1.z) / 2);
171
+ geometries.push(seg);
172
+ }
173
+ }
174
+ buildRollers(outerRx, outerRy, innerRx, innerRy, railW, startAngle, endAngle, depth, frameH, rollWidth, _beltWidth) {
175
+ const rollerRadius = Math.max(rollWidth / 2, 1);
176
+ const rollerY = depth / 2 - frameH / 2;
177
+ const midRx = (outerRx + innerRx) / 2;
178
+ const arcLength = midRx * Math.abs(endAngle - startAngle);
179
+ const diameter = rollerRadius * 2;
180
+ const step = diameter + 1;
181
+ const count = Math.max(1, Math.floor(arcLength / step));
182
+ const rollerGeometries = [];
183
+ for (let i = 1; i <= count; i++) {
184
+ const t = i / (count + 1);
185
+ const angle = startAngle + (endAngle - startAngle) * t;
186
+ const o = arcPoint(outerRx - railW, outerRy - railW, angle);
187
+ const inn = arcPoint(innerRx + railW, innerRy + railW, angle);
188
+ const dx = o.x - inn.x;
189
+ const dz = o.z - inn.z;
190
+ const rollerLength = Math.sqrt(dx * dx + dz * dz);
191
+ const midX = (o.x + inn.x) / 2;
192
+ const midZ = (o.z + inn.z) / 2;
193
+ // Conical roller: outer end wider, inner end narrower (capped at 1.5:1)
194
+ const outerDist = Math.sqrt(o.x * o.x + o.z * o.z);
195
+ const innerDist = Math.sqrt(inn.x * inn.x + inn.z * inn.z);
196
+ const rawRatio = innerDist > 0 ? outerDist / innerDist : 1;
197
+ const cappedRatio = Math.min(rawRatio, 1.5);
198
+ const outerRollerR = (rollerRadius * (2 * cappedRatio)) / (cappedRatio + 1);
199
+ const innerRollerR = (rollerRadius * 2) / (cappedRatio + 1);
200
+ // CylinderGeometry: radiusTop at +Y, radiusBottom at -Y
201
+ // After rotateX(π/2): +Y → -Z (top=inner), -Y → +Z (bottom=outer)
202
+ // cylinderAlignAngle maps +Z to radial direction (inner→outer)
203
+ const roller = new THREE.CylinderGeometry(outerRollerR, innerRollerR, rollerLength, 16);
204
+ roller.rotateX(Math.PI / 2);
205
+ roller.rotateY(cylinderAlignAngle(dx, dz));
206
+ roller.translate(midX, rollerY, midZ);
207
+ rollerGeometries.push(roller);
208
+ }
209
+ if (rollerGeometries.length > 0) {
210
+ const rollerMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(rollerGeometries), new THREE.MeshStandardMaterial({ color: ROLLER_COLOR, metalness: 0.9, roughness: 0.2 }));
211
+ rollerMesh.castShadow = true;
212
+ this.object3d.add(rollerMesh);
213
+ }
214
+ }
215
+ buildBelt(outerRx, outerRy, innerRx, innerRy, railW, startAngle, endAngle, segments, depth, frameH, value) {
216
+ const beltOx = outerRx - railW;
217
+ const beltOy = outerRy - railW;
218
+ const beltIx = innerRx + railW;
219
+ const beltIy = innerRy + railW;
220
+ const beltH = frameH * 0.15;
221
+ const fillColor = FILL_COLORS[value] ?? FILL_COLORS[0];
222
+ const shape = new THREE.Shape();
223
+ for (let i = 0; i <= segments; i++) {
224
+ const angle = startAngle + ((endAngle - startAngle) * i) / segments;
225
+ const sx = beltOx * Math.sin(angle);
226
+ const sy = beltOy * Math.cos(angle);
227
+ if (i === 0)
228
+ shape.moveTo(sx, sy);
229
+ else
230
+ shape.lineTo(sx, sy);
231
+ }
232
+ for (let i = segments; i >= 0; i--) {
233
+ const angle = startAngle + ((endAngle - startAngle) * i) / segments;
234
+ shape.lineTo(beltIx * Math.sin(angle), beltIy * Math.cos(angle));
235
+ }
236
+ shape.closePath();
237
+ const beltGeo = new THREE.ExtrudeGeometry(shape, { depth: beltH, bevelEnabled: false });
238
+ beltGeo.rotateX(-Math.PI / 2);
239
+ beltGeo.translate(0, depth / 2, 0);
240
+ const beltMesh = new THREE.Mesh(beltGeo, new THREE.MeshStandardMaterial({ color: fillColor, metalness: 0.0, roughness: 0.9 }));
241
+ beltMesh.castShadow = true;
242
+ this.object3d.add(beltMesh);
243
+ }
244
+ buildControlBox(outerRx, _outerRy, innerRx, _innerRy, startAngle, depth, frameH, railW, value) {
245
+ const beltWidth = outerRx - innerRx;
246
+ const boxW = Math.max(beltWidth * 0.12, 3);
247
+ const boxH = frameH * 0.6;
248
+ const boxD = Math.max(beltWidth * 0.15, 3);
249
+ const midR = outerRx - railW - boxD / 2;
250
+ const p = arcPoint(midR, midR, startAngle);
251
+ const railY = depth / 2 - frameH / 2;
252
+ const boxMaterial = new THREE.MeshStandardMaterial({
253
+ color: CONTROL_BOX_COLOR,
254
+ metalness: 0.6,
255
+ roughness: 0.4
256
+ });
257
+ const boxGeo = new THREE.BoxGeometry(boxW, boxH, boxD);
258
+ boxGeo.rotateY(-startAngle);
259
+ const boxMesh = new THREE.Mesh(boxGeo, boxMaterial);
260
+ boxMesh.position.set(p.x, railY - frameH / 2 - boxH / 2, p.z);
261
+ boxMesh.castShadow = true;
262
+ this.object3d.add(boxMesh);
263
+ // Status light
264
+ const lightR = Math.min(boxW, boxD) * 0.25;
265
+ const lightH = lightR * 1.2;
266
+ const emissiveColor = STATUS_EMISSIVE[value] ?? STATUS_EMISSIVE[0];
267
+ const lightMaterial = new THREE.MeshStandardMaterial({
268
+ color: emissiveColor,
269
+ emissive: emissiveColor,
270
+ emissiveIntensity: value > 0 ? 1.5 : 0.2,
271
+ metalness: 0.0,
272
+ roughness: 0.3
273
+ });
274
+ const lightGeo = new THREE.CylinderGeometry(lightR, lightR * 0.8, lightH, 12);
275
+ const lightMesh = new THREE.Mesh(lightGeo, lightMaterial);
276
+ lightMesh.position.set(p.x, boxMesh.position.y + boxH / 2 + lightH / 2, p.z);
277
+ this.object3d.add(lightMesh);
278
+ }
279
+ updateDimension() { }
280
+ onchange(after, before) {
281
+ if ('value' in after ||
282
+ 'conveyorType' in after ||
283
+ 'rollWidth' in after ||
284
+ 'rx' in after ||
285
+ 'ry' in after ||
286
+ 'ratio' in after ||
287
+ 'startAngle' in after ||
288
+ 'endAngle' in after ||
289
+ 'depth' in after) {
290
+ this.update();
291
+ return;
292
+ }
293
+ super.onchange(after, before);
294
+ }
295
+ updateAlpha() { }
296
+ }
297
+ //# sourceMappingURL=conveyor-join-3d.js.map