@fmsim/machine 2.0.0-beta.10 → 2.0.0-beta.100
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/dist/agv.js +6 -2
- package/dist/agv.js.map +1 -1
- package/dist/carrier-manager.js +183 -0
- package/dist/carrier-manager.js.map +1 -0
- package/dist/carrier.js +99 -9
- package/dist/carrier.js.map +1 -1
- package/dist/conveyor-join.js +8 -2
- package/dist/conveyor-join.js.map +1 -1
- package/dist/conveyor.js +8 -0
- package/dist/conveyor.js.map +1 -1
- package/dist/crane.js +144 -1
- package/dist/crane.js.map +1 -1
- package/dist/factories/agv-3d.js +8 -9
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +168 -81
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/crane-3d.js +48 -12
- package/dist/factories/crane-3d.js.map +1 -1
- package/dist/factories/crane-rail-3d.js +6 -6
- package/dist/factories/crane-rail-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +527 -181
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +20 -20
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/factories/shuttle-3d.js +2 -5
- package/dist/factories/shuttle-3d.js.map +1 -1
- package/dist/factories/z-priority-defaults.js +33 -0
- package/dist/factories/z-priority-defaults.js.map +1 -0
- package/dist/factories/z-priority.js +54 -0
- package/dist/factories/z-priority.js.map +1 -0
- package/dist/features/conveyor-mixin.js.map +1 -1
- package/dist/features/mcs-status-default.js +15 -1
- package/dist/features/mcs-status-default.js.map +1 -1
- package/dist/features/mcs-status-mixin.js +22 -0
- package/dist/features/mcs-status-mixin.js.map +1 -1
- package/dist/features/mcs-zone-mixin.js.map +1 -1
- package/dist/features/parent-object-mixin.js +15 -7
- package/dist/features/parent-object-mixin.js.map +1 -1
- package/dist/floating-container.js +5 -0
- package/dist/floating-container.js.map +1 -1
- package/dist/index.js +52 -34
- package/dist/index.js.map +1 -1
- package/dist/mcs-carrier-holder.js +56 -22
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/mcs-machine.js +39 -0
- package/dist/mcs-machine.js.map +1 -1
- package/dist/mcs-unit.js +20 -0
- package/dist/mcs-unit.js.map +1 -1
- package/dist/mcs-vehicle.js +8 -3
- package/dist/mcs-vehicle.js.map +1 -1
- package/dist/node.js +10 -3
- package/dist/node.js.map +1 -1
- package/dist/oht.js +4 -0
- package/dist/oht.js.map +1 -1
- package/dist/port-flow.js +4 -0
- package/dist/port-flow.js.map +1 -1
- package/dist/port.js +4 -0
- package/dist/port.js.map +1 -1
- package/dist/scanner.js +30 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scene/root-container-override.js +160 -96
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/shelf.js +4 -0
- package/dist/shelf.js.map +1 -1
- package/dist/static-container.js +5 -0
- package/dist/static-container.js.map +1 -1
- package/dist/stocker-abnormal-bar.js +5 -1
- package/dist/stocker-abnormal-bar.js.map +1 -1
- package/dist/stocker-capacity-bar.js +253 -6
- package/dist/stocker-capacity-bar.js.map +1 -1
- package/dist/stocker-carrier-bar.js +5 -1
- package/dist/stocker-carrier-bar.js.map +1 -1
- package/dist/stocker.js +6 -0
- package/dist/stocker.js.map +1 -1
- package/dist/templates/backdrop.js +30 -0
- package/dist/templates/backdrop.js.map +1 -0
- package/dist/templates/decal.js +21 -0
- package/dist/templates/decal.js.map +1 -0
- package/dist/templates/index.js +7 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/scanner.js +20 -0
- package/dist/templates/scanner.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/crane-position.js +83 -0
- package/dist/utils/crane-position.js.map +1 -0
- package/dist/utils/data-value.js +21 -0
- package/dist/utils/data-value.js.map +1 -0
- package/dist/utils/fit-gauge-scale.js +28 -0
- package/dist/utils/fit-gauge-scale.js.map +1 -0
- package/dist/utils/get-value-on-ranges.js +2 -4
- package/dist/utils/get-value-on-ranges.js.map +1 -1
- package/dist/utils/stocker-gauge.js +78 -0
- package/dist/utils/stocker-gauge.js.map +1 -0
- package/dist/zone-capacity-bar.js +3 -3
- package/dist/zone-capacity-bar.js.map +1 -1
- package/icons/backdrop.png +0 -0
- package/icons/decal.png +0 -0
- package/jest.config.js +10 -0
- package/package.json +4 -3
- package/src/agv.ts +7 -2
- package/src/carrier-manager.ts +223 -0
- package/src/carrier.ts +103 -10
- package/src/conveyor-join.ts +5 -2
- package/src/conveyor.ts +8 -0
- package/src/crane.ts +181 -1
- package/src/factories/agv-3d.ts +9 -10
- package/src/factories/conveyor-3d.ts +171 -84
- package/src/factories/crane-3d.ts +53 -12
- package/src/factories/crane-rail-3d.ts +8 -7
- package/src/factories/machine-3d.ts +616 -191
- package/src/factories/oht-3d.ts +20 -20
- package/src/factories/shuttle-3d.ts +2 -6
- package/src/factories/z-priority-defaults.ts +38 -0
- package/src/factories/z-priority.ts +60 -0
- package/src/features/conveyor-mixin.ts +1 -1
- package/src/features/mcs-status-default.ts +15 -1
- package/src/features/mcs-status-mixin.ts +26 -4
- package/src/features/mcs-zone-mixin.ts +1 -1
- package/src/features/parent-object-mixin.ts +17 -8
- package/src/floating-container.ts +5 -0
- package/src/index.ts +58 -34
- package/src/mcs-carrier-holder.ts +57 -34
- package/src/mcs-machine.ts +42 -0
- package/src/mcs-unit.ts +24 -1
- package/src/mcs-vehicle.ts +9 -6
- package/src/node.ts +9 -3
- package/src/oht.ts +6 -1
- package/src/port-flow.ts +5 -0
- package/src/port.ts +5 -0
- package/src/scanner.ts +29 -0
- package/src/scene/root-container-override.ts +154 -103
- package/src/shelf.ts +5 -0
- package/src/static-container.ts +5 -0
- package/src/stocker-abnormal-bar.ts +6 -1
- package/src/stocker-capacity-bar.ts +298 -7
- package/src/stocker-carrier-bar.ts +6 -1
- package/src/stocker.ts +7 -0
- package/src/templates/backdrop.ts +30 -0
- package/src/templates/decal.ts +21 -0
- package/src/templates/index.ts +7 -1
- package/src/templates/scanner.ts +20 -0
- package/src/utils/crane-position.ts +105 -0
- package/src/utils/data-value.ts +19 -0
- package/src/utils/fit-gauge-scale.ts +35 -0
- package/src/utils/get-value-on-ranges.ts +2 -4
- package/src/utils/stocker-gauge.ts +111 -0
- package/src/zone-capacity-bar.ts +3 -3
- package/translations/en.json +26 -1
- package/translations/ja.json +26 -1
- package/translations/ko.json +26 -1
- package/translations/ms.json +26 -1
- package/translations/zh.json +26 -1
package/src/factories/agv-3d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Based on operato-scene Vehicle3D, adapted for fmsim:
|
|
11
11
|
* - extends RealObjectGroup (Conveyor3D와 동일 패턴)
|
|
12
12
|
* - statusColor from MCSStatusMixin
|
|
13
|
-
* -
|
|
13
|
+
* - 볼륨 중심 원점 (things-scene 기본 컨벤션) - mesh 로컬 [-d/2, +d/2]
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import * as THREE from 'three'
|
|
@@ -30,10 +30,6 @@ export class AGV3D extends RealObjectGroup {
|
|
|
30
30
|
return depth || TRANSPORT_SURFACE_HEIGHT
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
protected get syncZPosOffset(): number {
|
|
34
|
-
return 0
|
|
35
|
-
}
|
|
36
|
-
|
|
37
33
|
private resolveColor(): number {
|
|
38
34
|
const comp = this.component as any
|
|
39
35
|
|
|
@@ -41,14 +37,14 @@ export class AGV3D extends RealObjectGroup {
|
|
|
41
37
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
42
38
|
try {
|
|
43
39
|
const sc = comp.statusColor
|
|
44
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
|
|
40
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
|
|
45
41
|
} catch {
|
|
46
42
|
/* statusColor 미구현 시 무시 */
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
// 2) state.fillStyle fallback
|
|
50
46
|
const { fillStyle } = this.component.state
|
|
51
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
47
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
52
48
|
try {
|
|
53
49
|
return new THREE.Color(fillStyle).getHex()
|
|
54
50
|
} catch {
|
|
@@ -118,7 +114,8 @@ export class AGV3D extends RealObjectGroup {
|
|
|
118
114
|
plat.receiveShadow = true
|
|
119
115
|
|
|
120
116
|
this._ownMeshes = [bumper, body, plat]
|
|
121
|
-
|
|
117
|
+
// ownGroup이 center-origin ↔ bottom-up 좌표계 변환을 자동 처리
|
|
118
|
+
for (const m of this._ownMeshes) this.components3D.add(m)
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
updateDimension() {}
|
|
@@ -126,7 +123,7 @@ export class AGV3D extends RealObjectGroup {
|
|
|
126
123
|
update() {
|
|
127
124
|
// 자신의 mesh만 제거 (Carrier 등 자식 컴포넌트 3D 보존)
|
|
128
125
|
for (const m of this._ownMeshes) {
|
|
129
|
-
this.
|
|
126
|
+
this.components3D.remove(m)
|
|
130
127
|
disposeObject3D(m)
|
|
131
128
|
}
|
|
132
129
|
this._ownMeshes = []
|
|
@@ -150,10 +147,12 @@ export class AGV3D extends RealObjectGroup {
|
|
|
150
147
|
|
|
151
148
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
152
149
|
if ('width' in after || 'height' in after || 'depth' in after ||
|
|
153
|
-
'fillStyle' in after || 'strokeStyle' in after || 'id' in after
|
|
150
|
+
'fillStyle' in after || 'strokeStyle' in after || 'id' in after ||
|
|
151
|
+
'EMPTYTYPE' in after || 'CARRIERSTATUS' in after) {
|
|
154
152
|
this.update()
|
|
155
153
|
return
|
|
156
154
|
}
|
|
155
|
+
|
|
157
156
|
super.onchange(after, before)
|
|
158
157
|
}
|
|
159
158
|
}
|
|
@@ -16,23 +16,20 @@ import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUti
|
|
|
16
16
|
import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene'
|
|
17
17
|
import type { Component } from '@hatiolab/things-scene'
|
|
18
18
|
import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'
|
|
19
|
+
import { Z_FMSIM } from './z-priority'
|
|
19
20
|
|
|
20
21
|
const FRAME_COLOR = 0x888899
|
|
21
22
|
const ROLLER_COLOR = 0xaaaabc
|
|
22
23
|
const DEFAULT_COLOR = 0x999999
|
|
23
24
|
|
|
24
25
|
export class Conveyor3D extends RealObjectGroup {
|
|
26
|
+
get zPriority(): number { return Z_FMSIM.CONVEYOR }
|
|
27
|
+
|
|
25
28
|
get effectiveDepth(): number {
|
|
26
29
|
const { depth } = this.component.state
|
|
27
30
|
return depth || TRANSPORT_SURFACE_HEIGHT
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
// group origin = 바닥 (y=0). MCSRealObject3D와 동일 컨벤션.
|
|
31
|
-
// 기하는 build()에서 y=0(바닥)~y=depth(상단) 범위로 배치.
|
|
32
|
-
protected get syncZPosOffset(): number {
|
|
33
|
-
return 0
|
|
34
|
-
}
|
|
35
|
-
|
|
36
33
|
private resolveColor(): number {
|
|
37
34
|
const comp = this.component as any
|
|
38
35
|
|
|
@@ -40,14 +37,14 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
40
37
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
41
38
|
try {
|
|
42
39
|
const sc = comp.statusColor
|
|
43
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
|
|
40
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
|
|
44
41
|
} catch {
|
|
45
42
|
/* statusColor 미구현 시 무시 */
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
// 2) state.fillStyle fallback
|
|
49
46
|
const { fillStyle } = this.component.state
|
|
50
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
47
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
51
48
|
try {
|
|
52
49
|
return new THREE.Color(fillStyle).getHex()
|
|
53
50
|
} catch {
|
|
@@ -61,16 +58,31 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
61
58
|
build() {
|
|
62
59
|
super.build()
|
|
63
60
|
|
|
64
|
-
const { width, height, conveyorType } = this.component.state
|
|
61
|
+
const { width: rawWidth, height: rawHeight, conveyorType, orientation } = this.component.state
|
|
65
62
|
const depth = this.effectiveDepth
|
|
66
|
-
if (!
|
|
63
|
+
if (!rawWidth || !rawHeight) return
|
|
64
|
+
|
|
65
|
+
// orientation: 'horizontal' = 롤러가 Z축(세로), 'vertical' = 롤러가 X축(가로)
|
|
66
|
+
// default = 긴 방향에 수직 (긴 축을 따라 이송)
|
|
67
|
+
const isVertical = orientation === 'vertical' ? true
|
|
68
|
+
: orientation === 'horizontal' ? false
|
|
69
|
+
: rawWidth < rawHeight // default: 세로가 길면 롤러는 가로(X축)
|
|
70
|
+
// isVertical=true → 롤러 축이 X(가로), 이송 방향이 Z(세로)
|
|
71
|
+
// isVertical=false → 롤러 축이 Z(세로), 이송 방향이 X(가로)
|
|
72
|
+
const width = rawWidth
|
|
73
|
+
const height = rawHeight
|
|
67
74
|
|
|
68
75
|
// 2D와 동일 로직으로 rollWidth 산출
|
|
69
76
|
const rollWidth = Math.max(10, Math.min(width, height) / 2)
|
|
70
77
|
const rollerDiameter = Math.max(rollWidth, 2)
|
|
71
78
|
const frameH = Math.min(rollerDiameter, depth)
|
|
72
79
|
const legH = Math.max(depth - frameH, 0)
|
|
73
|
-
|
|
80
|
+
|
|
81
|
+
// isVertical일 때 이송 방향이 Z축: 레일은 Z축을 따라, 벨트폭은 width
|
|
82
|
+
// !isVertical일 때 이송 방향이 X축: 레일은 X축을 따라, 벨트폭은 height
|
|
83
|
+
const beltWidth = isVertical ? width : height
|
|
84
|
+
const railLen = isVertical ? height : width
|
|
85
|
+
const railW = Math.max(beltWidth * 0.06, 2)
|
|
74
86
|
const legThickness = Math.max(railW * 0.8, 2)
|
|
75
87
|
|
|
76
88
|
const frameMaterial = new THREE.MeshStandardMaterial({
|
|
@@ -84,11 +96,21 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
84
96
|
const frameGeometries: THREE.BufferGeometry[] = []
|
|
85
97
|
const railY = depth - frameH / 2
|
|
86
98
|
|
|
87
|
-
// Side rails
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
// Side rails — 이송 방향을 따라, 벨트 양측에 배치
|
|
100
|
+
if (isVertical) {
|
|
101
|
+
// 레일이 Z축(세로)을 따라, ±X에 배치
|
|
102
|
+
for (const xSign of [-1, 1]) {
|
|
103
|
+
const rail = new THREE.BoxGeometry(railW, frameH, height)
|
|
104
|
+
rail.translate(xSign * (width / 2 - railW / 2), railY, 0)
|
|
105
|
+
frameGeometries.push(rail)
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// 레일이 X축(가로)을 따라, ±Z에 배치
|
|
109
|
+
for (const zSign of [-1, 1]) {
|
|
110
|
+
const rail = new THREE.BoxGeometry(width, frameH, railW)
|
|
111
|
+
rail.translate(0, railY, zSign * (height / 2 - railW / 2))
|
|
112
|
+
frameGeometries.push(rail)
|
|
113
|
+
}
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
// 4 legs at corners
|
|
@@ -96,49 +118,66 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
96
118
|
const legTopY = depth - frameH
|
|
97
119
|
const legCenterY = legTopY - legH / 2
|
|
98
120
|
|
|
121
|
+
// 다리 위치: 레일 안쪽에 정렬
|
|
122
|
+
const legXPos = isVertical ? (width / 2 - railW / 2) : (width / 2 - legThickness / 2)
|
|
123
|
+
const legZPos = isVertical ? (height / 2 - legThickness / 2) : (height / 2 - railW / 2)
|
|
124
|
+
|
|
99
125
|
for (const xSign of [-1, 1]) {
|
|
100
126
|
for (const zSign of [-1, 1]) {
|
|
101
|
-
const lx = xSign * (width / 2 - legThickness / 2)
|
|
102
|
-
const lz = zSign * (height / 2 - railW / 2)
|
|
103
127
|
const leg = new THREE.BoxGeometry(legThickness, legH, legThickness)
|
|
104
|
-
leg.translate(
|
|
128
|
+
leg.translate(xSign * legXPos, legCenterY, zSign * legZPos)
|
|
105
129
|
frameGeometries.push(leg)
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
// Cross-bracing
|
|
133
|
+
// Cross-bracing
|
|
110
134
|
const braceH = legThickness * 0.6
|
|
111
135
|
const braceW = legThickness * 0.6
|
|
112
|
-
const braceLen = height - railW
|
|
113
136
|
const braceY = legTopY - legH * 0.35
|
|
114
137
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
// 이송 방향 브레이스 (레일과 평행)
|
|
139
|
+
if (isVertical) {
|
|
140
|
+
for (const xSign of [-1, 1]) {
|
|
141
|
+
const brace = new THREE.BoxGeometry(braceW, braceH, height - legThickness)
|
|
142
|
+
brace.translate(xSign * legXPos, braceY, 0)
|
|
143
|
+
frameGeometries.push(brace)
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
for (const zSign of [-1, 1]) {
|
|
147
|
+
const brace = new THREE.BoxGeometry(width - legThickness, braceH, braceW)
|
|
148
|
+
brace.translate(0, braceY, zSign * legZPos)
|
|
149
|
+
frameGeometries.push(brace)
|
|
150
|
+
}
|
|
119
151
|
}
|
|
120
152
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
// 벨트폭 방향 브레이스 (레일과 수직)
|
|
154
|
+
if (isVertical) {
|
|
155
|
+
for (const zSign of [-1, 1]) {
|
|
156
|
+
const brace = new THREE.BoxGeometry(width - railW, braceH, braceW)
|
|
157
|
+
brace.translate(0, braceY, zSign * legZPos)
|
|
158
|
+
frameGeometries.push(brace)
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
for (const xSign of [-1, 1]) {
|
|
162
|
+
const brace = new THREE.BoxGeometry(braceW, braceH, height - railW)
|
|
163
|
+
brace.translate(xSign * legXPos, braceY, 0)
|
|
164
|
+
frameGeometries.push(brace)
|
|
165
|
+
}
|
|
127
166
|
}
|
|
128
167
|
}
|
|
129
168
|
|
|
130
169
|
const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial)
|
|
131
170
|
frameMesh.castShadow = true
|
|
132
171
|
frameMesh.receiveShadow = true
|
|
133
|
-
this.
|
|
172
|
+
this.components3D.add(frameMesh)
|
|
134
173
|
|
|
135
174
|
// --- Rollers, Belt, or Rails ---
|
|
136
175
|
if (conveyorType === 'rail') {
|
|
137
|
-
this.buildRails(width, height, depth, frameH, railW)
|
|
176
|
+
this.buildRails(width, height, depth, frameH, railW, isVertical)
|
|
138
177
|
} else if (conveyorType === 'belt') {
|
|
139
|
-
this.buildBelt(width, height, depth, frameH, railW)
|
|
178
|
+
this.buildBelt(width, height, depth, frameH, railW, isVertical)
|
|
140
179
|
} else {
|
|
141
|
-
this.buildRollers(width, height, depth, frameH, railW, rollWidth)
|
|
180
|
+
this.buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical)
|
|
142
181
|
}
|
|
143
182
|
}
|
|
144
183
|
|
|
@@ -148,28 +187,45 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
148
187
|
depth: number,
|
|
149
188
|
frameH: number,
|
|
150
189
|
railW: number,
|
|
151
|
-
rollWidth: number
|
|
190
|
+
rollWidth: number,
|
|
191
|
+
isVertical: boolean = false
|
|
152
192
|
) {
|
|
153
193
|
const rollerRadius = Math.max(rollWidth / 8, 1)
|
|
154
|
-
const rollerLength = height - railW * 2 - 0.5
|
|
155
194
|
const diameter = rollerRadius * 2
|
|
156
|
-
// 롤러 간 최소 간격 (z-fighting 방지용 미세 갭만)
|
|
157
195
|
const step = diameter * 1.05
|
|
158
|
-
// 롤러 상단 = 이송 표면 (y=depth)
|
|
159
196
|
const rollerY = depth - rollerRadius
|
|
160
197
|
const color = this.resolveColor()
|
|
161
198
|
|
|
162
199
|
const rollerGeometries: THREE.BufferGeometry[] = []
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
|
|
201
|
+
if (isVertical) {
|
|
202
|
+
// 롤러 축 = X축(가로), 이송 방향 = Z축(세로)
|
|
203
|
+
const rollerLength = width - railW * 2 - 0.5
|
|
204
|
+
const count = Math.max(1, Math.floor(height / step))
|
|
205
|
+
const totalSpan = (count - 1) * step
|
|
206
|
+
const startZ = -totalSpan / 2
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < count; i++) {
|
|
209
|
+
const z = startZ + i * step
|
|
210
|
+
const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
|
|
211
|
+
roller.rotateZ(Math.PI / 2)
|
|
212
|
+
roller.translate(0, rollerY, z)
|
|
213
|
+
rollerGeometries.push(roller)
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// 롤러 축 = Z축(세로), 이송 방향 = X축(가로) — 기존 동작
|
|
217
|
+
const rollerLength = height - railW * 2 - 0.5
|
|
218
|
+
const count = Math.max(1, Math.floor(width / step))
|
|
219
|
+
const totalSpan = (count - 1) * step
|
|
220
|
+
const startX = -totalSpan / 2
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < count; i++) {
|
|
223
|
+
const x = startX + i * step
|
|
224
|
+
const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
|
|
225
|
+
roller.rotateX(Math.PI / 2)
|
|
226
|
+
roller.translate(x, rollerY, 0)
|
|
227
|
+
rollerGeometries.push(roller)
|
|
228
|
+
}
|
|
173
229
|
}
|
|
174
230
|
|
|
175
231
|
if (rollerGeometries.length > 0) {
|
|
@@ -178,26 +234,35 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
178
234
|
new THREE.MeshStandardMaterial({ color, metalness: 0.9, roughness: 0.2 })
|
|
179
235
|
)
|
|
180
236
|
rollerMesh.castShadow = true
|
|
181
|
-
this.
|
|
237
|
+
this.components3D.add(rollerMesh)
|
|
182
238
|
}
|
|
183
239
|
}
|
|
184
240
|
|
|
185
|
-
private buildBelt(width: number, height: number, depth: number, frameH: number, railW: number) {
|
|
241
|
+
private buildBelt(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
|
|
186
242
|
const drumRadius = frameH * 0.38
|
|
187
|
-
const drumLength = height - railW * 2
|
|
188
243
|
const beltY = depth - drumRadius
|
|
189
244
|
const color = this.resolveColor()
|
|
190
245
|
|
|
246
|
+
// 이송 방향 길이와 벨트폭
|
|
247
|
+
const transportLen = isVertical ? height : width
|
|
248
|
+
const beltWidth = isVertical ? width : height
|
|
249
|
+
const drumLength = beltWidth - railW * 2
|
|
250
|
+
|
|
191
251
|
// Two end drums
|
|
192
252
|
const drumGeometries: THREE.BufferGeometry[] = []
|
|
193
253
|
const drumInset = drumRadius * 1.5
|
|
194
|
-
const
|
|
195
|
-
const
|
|
254
|
+
const drum1 = -transportLen / 2 + drumInset
|
|
255
|
+
const drum2 = transportLen / 2 - drumInset
|
|
196
256
|
|
|
197
|
-
for (const
|
|
257
|
+
for (const dp of [drum1, drum2]) {
|
|
198
258
|
const drum = new THREE.CylinderGeometry(drumRadius, drumRadius, drumLength, 16)
|
|
199
|
-
|
|
200
|
-
|
|
259
|
+
if (isVertical) {
|
|
260
|
+
drum.rotateZ(Math.PI / 2)
|
|
261
|
+
drum.translate(0, beltY, dp)
|
|
262
|
+
} else {
|
|
263
|
+
drum.rotateX(Math.PI / 2)
|
|
264
|
+
drum.translate(dp, beltY, 0)
|
|
265
|
+
}
|
|
201
266
|
drumGeometries.push(drum)
|
|
202
267
|
}
|
|
203
268
|
|
|
@@ -206,7 +271,7 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
206
271
|
new THREE.MeshStandardMaterial({ color: ROLLER_COLOR, metalness: 0.9, roughness: 0.2 })
|
|
207
272
|
)
|
|
208
273
|
drumMesh.castShadow = true
|
|
209
|
-
this.
|
|
274
|
+
this.components3D.add(drumMesh)
|
|
210
275
|
|
|
211
276
|
// Belt surface
|
|
212
277
|
const beltThickness = drumRadius * 0.12
|
|
@@ -217,18 +282,20 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
217
282
|
})
|
|
218
283
|
|
|
219
284
|
// Flat belt top surface spanning between drums
|
|
220
|
-
const flatLen =
|
|
221
|
-
const flatGeo =
|
|
285
|
+
const flatLen = drum2 - drum1
|
|
286
|
+
const flatGeo = isVertical
|
|
287
|
+
? new THREE.BoxGeometry(drumLength, beltThickness, flatLen)
|
|
288
|
+
: new THREE.BoxGeometry(flatLen, beltThickness, drumLength)
|
|
222
289
|
const flatMesh = new THREE.Mesh(flatGeo, beltMaterial)
|
|
223
290
|
flatMesh.position.set(0, beltY + drumRadius, 0)
|
|
224
291
|
flatMesh.castShadow = true
|
|
225
|
-
this.
|
|
292
|
+
this.components3D.add(flatMesh)
|
|
226
293
|
|
|
227
294
|
// Belt wrap around drums (segmented half-cylinder)
|
|
228
295
|
const wrapSegments = 12
|
|
229
|
-
for (const [
|
|
230
|
-
[
|
|
231
|
-
[
|
|
296
|
+
for (const [dp, angleStart] of [
|
|
297
|
+
[drum1, Math.PI / 2],
|
|
298
|
+
[drum2, -Math.PI / 2],
|
|
232
299
|
] as [number, number][]) {
|
|
233
300
|
const wrapGeometries: THREE.BufferGeometry[] = []
|
|
234
301
|
for (let i = 0; i < wrapSegments; i++) {
|
|
@@ -236,36 +303,47 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
236
303
|
const a1 = angleStart + (Math.PI * (i + 1)) / wrapSegments
|
|
237
304
|
const r = drumRadius + beltThickness / 2
|
|
238
305
|
|
|
239
|
-
const
|
|
306
|
+
const p0 = dp + Math.cos(a0) * r
|
|
240
307
|
const y0 = beltY + Math.sin(a0) * r
|
|
241
|
-
const
|
|
308
|
+
const p1 = dp + Math.cos(a1) * r
|
|
242
309
|
const y1 = beltY + Math.sin(a1) * r
|
|
243
310
|
|
|
244
|
-
const segLen = Math.sqrt((
|
|
245
|
-
const segAngle = Math.atan2(y1 - y0,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
311
|
+
const segLen = Math.sqrt((p1 - p0) ** 2 + (y1 - y0) ** 2) * 1.05
|
|
312
|
+
const segAngle = Math.atan2(y1 - y0, p1 - p0)
|
|
313
|
+
|
|
314
|
+
if (isVertical) {
|
|
315
|
+
const seg = new THREE.BoxGeometry(drumLength, beltThickness, segLen)
|
|
316
|
+
seg.rotateX(-segAngle)
|
|
317
|
+
seg.translate(0, (y0 + y1) / 2, (p0 + p1) / 2)
|
|
318
|
+
wrapGeometries.push(seg)
|
|
319
|
+
} else {
|
|
320
|
+
const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength)
|
|
321
|
+
seg.rotateZ(segAngle)
|
|
322
|
+
seg.translate((p0 + p1) / 2, (y0 + y1) / 2, 0)
|
|
323
|
+
wrapGeometries.push(seg)
|
|
324
|
+
}
|
|
251
325
|
}
|
|
252
326
|
const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial)
|
|
253
327
|
wrapMesh.castShadow = true
|
|
254
|
-
this.
|
|
328
|
+
this.components3D.add(wrapMesh)
|
|
255
329
|
}
|
|
256
330
|
|
|
257
331
|
// Flat belt bottom (return path)
|
|
258
|
-
const bottomGeo =
|
|
332
|
+
const bottomGeo = isVertical
|
|
333
|
+
? new THREE.BoxGeometry(drumLength * 0.85, beltThickness, flatLen)
|
|
334
|
+
: new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)
|
|
259
335
|
const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial)
|
|
260
336
|
bottomMesh.position.set(0, beltY - drumRadius, 0)
|
|
261
|
-
this.
|
|
337
|
+
this.components3D.add(bottomMesh)
|
|
262
338
|
}
|
|
263
339
|
|
|
264
|
-
private buildRails(width: number, height: number, depth: number, frameH: number, railW: number) {
|
|
340
|
+
private buildRails(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
|
|
265
341
|
const color = this.resolveColor()
|
|
266
342
|
const railHeight = frameH * 0.3
|
|
267
343
|
const railY = depth - railHeight / 2
|
|
268
|
-
const
|
|
344
|
+
const beltWidth = isVertical ? width : height
|
|
345
|
+
const transportLen = isVertical ? height : width
|
|
346
|
+
const innerWidth = beltWidth - railW * 2
|
|
269
347
|
// 두 레일 간격: 내부 폭의 60%
|
|
270
348
|
const railSpacing = innerWidth * 0.6
|
|
271
349
|
|
|
@@ -277,16 +355,24 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
277
355
|
|
|
278
356
|
const railGeometries: THREE.BufferGeometry[] = []
|
|
279
357
|
|
|
280
|
-
// Two parallel rails running along
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
358
|
+
// Two parallel rails running along transport direction
|
|
359
|
+
if (isVertical) {
|
|
360
|
+
for (const xSign of [-1, 1]) {
|
|
361
|
+
const rail = new THREE.BoxGeometry(railW * 0.8, railHeight, transportLen - railW * 2)
|
|
362
|
+
rail.translate(xSign * (railSpacing / 2), railY, 0)
|
|
363
|
+
railGeometries.push(rail)
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
for (const zSign of [-1, 1]) {
|
|
367
|
+
const rail = new THREE.BoxGeometry(transportLen - railW * 2, railHeight, railW * 0.8)
|
|
368
|
+
rail.translate(0, railY, zSign * (railSpacing / 2))
|
|
369
|
+
railGeometries.push(rail)
|
|
370
|
+
}
|
|
285
371
|
}
|
|
286
372
|
|
|
287
373
|
const railMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(railGeometries), railMaterial)
|
|
288
374
|
railMesh.castShadow = true
|
|
289
|
-
this.
|
|
375
|
+
this.components3D.add(railMesh)
|
|
290
376
|
}
|
|
291
377
|
|
|
292
378
|
updateDimension() {}
|
|
@@ -295,6 +381,7 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
295
381
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
296
382
|
if (
|
|
297
383
|
'conveyorType' in after ||
|
|
384
|
+
'orientation' in after ||
|
|
298
385
|
'width' in after ||
|
|
299
386
|
'height' in after ||
|
|
300
387
|
'depth' in after ||
|
|
@@ -22,13 +22,27 @@ const FORK_COLOR = 0x999999
|
|
|
22
22
|
const DEFAULT_BODY_COLOR = 0xcc8844
|
|
23
23
|
|
|
24
24
|
export class Crane3D extends RealObjectGroup {
|
|
25
|
+
/** 캐리지 포크 group — Crane.state.forkZ (0~100) 가 변하면 position.y 만 lerp.
|
|
26
|
+
* build() 시 inner 에 add 되고, *_mastBaseY 와 mastH 를 함께 보관* 해서
|
|
27
|
+
* setForkZ 가 비율 계산에 사용. */
|
|
28
|
+
private forkGroup?: THREE.Group
|
|
29
|
+
private forkMastBaseY = 0
|
|
30
|
+
private forkMastH = 0
|
|
31
|
+
|
|
25
32
|
get effectiveDepth(): number {
|
|
26
33
|
const { depth } = this.component.state
|
|
27
34
|
return depth || 5 * TRANSPORT_SURFACE_HEIGHT - 2
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
/**
|
|
38
|
+
* 0~100 정규화 z (= state.forkZ) 를 mast 높이 비율로 변환해서 포크 group 의
|
|
39
|
+
* three-up y 위치 갱신. build() 가 안 끝났으면 noop — build 가 forkZ 를 다시
|
|
40
|
+
* 반영하면서 끝난다.
|
|
41
|
+
*/
|
|
42
|
+
applyForkZ(z: number): void {
|
|
43
|
+
if (!this.forkGroup) return
|
|
44
|
+
const clamped = Math.max(0, Math.min(100, Number(z) || 0))
|
|
45
|
+
this.forkGroup.position.y = this.forkMastBaseY + (clamped / 100) * this.forkMastH
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
private resolveColor(): number {
|
|
@@ -62,6 +76,8 @@ export class Crane3D extends RealObjectGroup {
|
|
|
62
76
|
const d = this.effectiveDepth
|
|
63
77
|
if (!w || !h) return
|
|
64
78
|
|
|
79
|
+
const inner = this.components3D
|
|
80
|
+
|
|
65
81
|
// 높이 배분: 하단 클램프 4%, 마스트 92%, 상단 클램프 4%
|
|
66
82
|
const clampH = d * 0.04
|
|
67
83
|
const mastH = d * 0.92
|
|
@@ -81,7 +97,7 @@ export class Crane3D extends RealObjectGroup {
|
|
|
81
97
|
const bottomClamp = new THREE.Mesh(bottomClampGeo, clampMat)
|
|
82
98
|
bottomClamp.position.y = clampH / 2
|
|
83
99
|
bottomClamp.castShadow = true
|
|
84
|
-
|
|
100
|
+
inner.add(bottomClamp)
|
|
85
101
|
|
|
86
102
|
// ── 2. 수직 마스트 (좌우 기둥 + 상·하 가로 연결 바) ──
|
|
87
103
|
const mastBaseY = clampH
|
|
@@ -115,10 +131,12 @@ export class Crane3D extends RealObjectGroup {
|
|
|
115
131
|
|
|
116
132
|
const mastMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(mastGeometries), mastMat)
|
|
117
133
|
mastMesh.castShadow = true
|
|
118
|
-
|
|
134
|
+
inner.add(mastMesh)
|
|
119
135
|
|
|
120
|
-
// ── 3. 캐리지 포크 (
|
|
121
|
-
|
|
136
|
+
// ── 3. 캐리지 포크 (별도 group — state.forkZ 변경 시 group.position.y lerp) ──
|
|
137
|
+
// 포크 geometry 는 *_mastBaseY 기준 local 0 에 둠* — 즉 group.position.y = 0
|
|
138
|
+
// 일 때 mast 바닥 자리에. forkZ 0~100 에 따라 group.position.y 만 변경하면
|
|
139
|
+
// mast 따라 위/아래 이동. mesh 재구축 불필요.
|
|
122
140
|
const forkMat = new THREE.MeshStandardMaterial({
|
|
123
141
|
color: FORK_COLOR,
|
|
124
142
|
roughness: 0.4,
|
|
@@ -127,15 +145,13 @@ export class Crane3D extends RealObjectGroup {
|
|
|
127
145
|
|
|
128
146
|
const forkGeometries: THREE.BufferGeometry[] = []
|
|
129
147
|
|
|
130
|
-
// 캐리지 중심 플레이트 (마스트에 부착, 양쪽 포크의 베이스)
|
|
131
148
|
const plateW = w * 0.9
|
|
132
149
|
const plateH = d * 0.08
|
|
133
150
|
const plateD = pillarThick * 0.8
|
|
134
151
|
const centerPlate = new THREE.BoxGeometry(plateW, plateH, plateD)
|
|
135
|
-
centerPlate.translate(0,
|
|
152
|
+
centerPlate.translate(0, plateH / 2, 0) // local 0 = mast 바닥
|
|
136
153
|
forkGeometries.push(centerPlate)
|
|
137
154
|
|
|
138
|
-
// 양쪽 포크 암 (좌/우 선반 방향으로 뻗음)
|
|
139
155
|
const armW = pillarThick * 0.6
|
|
140
156
|
const armH = plateH * 0.4
|
|
141
157
|
const armD = h * 0.6
|
|
@@ -144,31 +160,56 @@ export class Crane3D extends RealObjectGroup {
|
|
|
144
160
|
for (const zSign of [-1, 1]) {
|
|
145
161
|
for (const xSign of [-1, 1]) {
|
|
146
162
|
const arm = new THREE.BoxGeometry(armW, armH, armD)
|
|
147
|
-
arm.translate(xSign * (armSpacing / 2),
|
|
163
|
+
arm.translate(xSign * (armSpacing / 2), armH / 2, zSign * (plateD / 2 + armD / 2))
|
|
148
164
|
forkGeometries.push(arm)
|
|
149
165
|
}
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
const forkMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(forkGeometries), forkMat)
|
|
153
169
|
forkMesh.castShadow = true
|
|
154
|
-
|
|
170
|
+
|
|
171
|
+
const forkGroup = new THREE.Group()
|
|
172
|
+
forkGroup.add(forkMesh)
|
|
173
|
+
inner.add(forkGroup)
|
|
174
|
+
this.forkGroup = forkGroup
|
|
175
|
+
this.forkMastBaseY = mastBaseY
|
|
176
|
+
// forkZ=100 시점에 mast 의 *_위쪽 한계*. 포크 자체 두께 (plateH) 만큼은 마스트
|
|
177
|
+
// 안에 머무르도록 limit 보정 — 그렇지 않으면 forkZ=100 일 때 포크가 상단
|
|
178
|
+
// 클램프와 충돌. mastH * 0.92 로 약간 보수적 (= 마스트 본체 92% 까지만 이동).
|
|
179
|
+
this.forkMastH = mastH * 0.92
|
|
180
|
+
|
|
181
|
+
// 초기 forkZ 반영 — state.forkZ 가 이미 set 되어 있으면 그 위치에 배치
|
|
182
|
+
const initialForkZ = Number((this.component.state as any).forkZ)
|
|
183
|
+
this.applyForkZ(Number.isFinite(initialForkZ) ? initialForkZ : 0)
|
|
155
184
|
|
|
156
185
|
// ── 4. 상단 클램프 (천장 레일에 물리는 가이드부) ──
|
|
157
186
|
const topClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)
|
|
158
187
|
const topClamp = new THREE.Mesh(topClampGeo, clampMat)
|
|
159
188
|
topClamp.position.y = mastBaseY + mastH + clampH / 2
|
|
160
189
|
topClamp.castShadow = true
|
|
161
|
-
|
|
190
|
+
inner.add(topClamp)
|
|
162
191
|
}
|
|
163
192
|
|
|
164
193
|
updateDimension() {}
|
|
165
194
|
updateAlpha() {}
|
|
166
195
|
|
|
196
|
+
/** build() 직전 호출. forkGroup 참조를 깨끗이 비워둔다 — 새 build 가 다시 set. */
|
|
197
|
+
clear() {
|
|
198
|
+
this.forkGroup = undefined
|
|
199
|
+
super.clear()
|
|
200
|
+
}
|
|
201
|
+
|
|
167
202
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
168
203
|
if ('width' in after || 'height' in after || 'depth' in after || 'fillStyle' in after || 'strokeStyle' in after) {
|
|
169
204
|
this.update()
|
|
170
205
|
return
|
|
171
206
|
}
|
|
207
|
+
// forkZ 는 mesh 재구축 없이 group.position.y 만 갱신. 같은 onchange 에서
|
|
208
|
+
// left/top 도 함께 변경될 수 있으므로 super 도 반드시 호출해 본체 transform
|
|
209
|
+
// 갱신 path (state.left/top → obj3d.position.x/z) 가 동작하게 함.
|
|
210
|
+
if ('forkZ' in after) {
|
|
211
|
+
this.applyForkZ(Number(after.forkZ) || 0)
|
|
212
|
+
}
|
|
172
213
|
super.onchange(after, before)
|
|
173
214
|
}
|
|
174
215
|
}
|