@fmsim/machine 2.0.0-beta.10 → 2.0.0-beta.101
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 +17 -36
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +179 -100
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/crane-3d.js +56 -29
- package/dist/factories/crane-3d.js.map +1 -1
- package/dist/factories/crane-rail-3d.js +14 -18
- package/dist/factories/crane-rail-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +533 -197
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +29 -47
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/factories/shuttle-3d.js +13 -32
- 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 +20 -0
- package/dist/mcs-machine.js.map +1 -1
- package/dist/mcs-unit.js +17 -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 +19 -36
- package/src/factories/conveyor-3d.ts +183 -103
- package/src/factories/crane-3d.ts +62 -29
- package/src/factories/crane-rail-3d.ts +17 -20
- package/src/factories/machine-3d.ts +631 -205
- package/src/factories/oht-3d.ts +30 -46
- package/src/factories/shuttle-3d.ts +14 -32
- 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 +23 -0
- package/src/mcs-unit.ts +21 -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,12 +10,12 @@
|
|
|
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'
|
|
17
17
|
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'
|
|
18
|
-
import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene'
|
|
18
|
+
import { RealObjectGroup, registerRealObjectFactory, disposeObject3D, MaterialBank } from '@hatiolab/things-scene'
|
|
19
19
|
import type { Component } from '@hatiolab/things-scene'
|
|
20
20
|
import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'
|
|
21
21
|
|
|
@@ -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 {
|
|
@@ -80,11 +76,8 @@ export class AGV3D extends RealObjectGroup {
|
|
|
80
76
|
const bumperPad = Math.min(w, h) * 0.03
|
|
81
77
|
const bumperCornerR = Math.min(w + bumperPad * 2, h + bumperPad * 2, bumperH) * 0.2
|
|
82
78
|
const bumperGeo = new RoundedBoxGeometry(w + bumperPad * 2, bumperH, h + bumperPad * 2, 4, bumperCornerR)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
roughness: 0.8,
|
|
86
|
-
metalness: 0.1,
|
|
87
|
-
})
|
|
79
|
+
// MaterialBank 공유 — dedup + faint 안전(코어 변형 스왑, disposeObject3D 가 __bankOwned 스킵).
|
|
80
|
+
const bumperMat = MaterialBank.material('agv|bumper', { roughness: 0.8, metalness: 0.1 }, BUMPER_COLOR)
|
|
88
81
|
const bumper = new THREE.Mesh(bumperGeo, bumperMat)
|
|
89
82
|
bumper.position.y = bumperH / 2
|
|
90
83
|
bumper.castShadow = true
|
|
@@ -93,11 +86,7 @@ export class AGV3D extends RealObjectGroup {
|
|
|
93
86
|
// ── 본체 (둥근 모서리, statusColor 적용) ──
|
|
94
87
|
const bodyColor = this.resolveColor()
|
|
95
88
|
const bodyGeo = new RoundedBoxGeometry(w, bodyH, h, 4, cornerR)
|
|
96
|
-
const bodyMat =
|
|
97
|
-
color: bodyColor,
|
|
98
|
-
roughness: 0.3,
|
|
99
|
-
metalness: 0.5,
|
|
100
|
-
})
|
|
89
|
+
const bodyMat = MaterialBank.material('agv|body', { roughness: 0.3, metalness: 0.5 }, bodyColor)
|
|
101
90
|
const body = new THREE.Mesh(bodyGeo, bodyMat)
|
|
102
91
|
body.position.y = bumperH + bodyH / 2
|
|
103
92
|
body.castShadow = true
|
|
@@ -107,18 +96,18 @@ export class AGV3D extends RealObjectGroup {
|
|
|
107
96
|
const platInset = Math.min(w, h) * 0.04
|
|
108
97
|
const platCornerR = Math.min(w - platInset * 2, h - platInset * 2, platH) * 0.15
|
|
109
98
|
const platGeo = new RoundedBoxGeometry(w - platInset * 2, platH, h - platInset * 2, 4, platCornerR)
|
|
110
|
-
const platMat =
|
|
111
|
-
color: PLATFORM_COLOR,
|
|
112
|
-
roughness: 0.5,
|
|
113
|
-
metalness: 0.15,
|
|
114
|
-
})
|
|
99
|
+
const platMat = MaterialBank.material('agv|platform', { roughness: 0.5, metalness: 0.15 }, PLATFORM_COLOR)
|
|
115
100
|
const plat = new THREE.Mesh(platGeo, platMat)
|
|
116
101
|
plat.position.y = bumperH + bodyH + gapH + platH / 2
|
|
117
102
|
plat.castShadow = true
|
|
118
103
|
plat.receiveShadow = true
|
|
119
104
|
|
|
120
105
|
this._ownMeshes = [bumper, body, plat]
|
|
121
|
-
|
|
106
|
+
// ownGroup이 center-origin ↔ bottom-up 좌표계 변환을 자동 처리
|
|
107
|
+
for (const m of this._ownMeshes) this.components3D.add(m)
|
|
108
|
+
|
|
109
|
+
// mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).
|
|
110
|
+
this.updateAlpha()
|
|
122
111
|
}
|
|
123
112
|
|
|
124
113
|
updateDimension() {}
|
|
@@ -126,7 +115,7 @@ export class AGV3D extends RealObjectGroup {
|
|
|
126
115
|
update() {
|
|
127
116
|
// 자신의 mesh만 제거 (Carrier 등 자식 컴포넌트 3D 보존)
|
|
128
117
|
for (const m of this._ownMeshes) {
|
|
129
|
-
this.
|
|
118
|
+
this.components3D.remove(m)
|
|
130
119
|
disposeObject3D(m)
|
|
131
120
|
}
|
|
132
121
|
this._ownMeshes = []
|
|
@@ -137,23 +126,17 @@ export class AGV3D extends RealObjectGroup {
|
|
|
137
126
|
this.updateHidden()
|
|
138
127
|
}
|
|
139
128
|
|
|
140
|
-
updateAlpha()
|
|
141
|
-
|
|
142
|
-
for (const m of this._ownMeshes) {
|
|
143
|
-
const materials = Array.isArray(m.material) ? m.material : [m.material]
|
|
144
|
-
for (const mat of materials) {
|
|
145
|
-
;(mat as THREE.MeshStandardMaterial).opacity = alpha
|
|
146
|
-
;(mat as THREE.MeshStandardMaterial).transparent = alpha < 1
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
129
|
+
// updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴.
|
|
130
|
+
// canonical(bank base opacity 1) × state.alpha × faint. view 모드 불변, 편집 모드 faint 추가.
|
|
150
131
|
|
|
151
132
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
152
133
|
if ('width' in after || 'height' in after || 'depth' in after ||
|
|
153
|
-
'fillStyle' in after || 'strokeStyle' in after || 'id' in after
|
|
134
|
+
'fillStyle' in after || 'strokeStyle' in after || 'id' in after ||
|
|
135
|
+
'EMPTYTYPE' in after || 'CARRIERSTATUS' in after) {
|
|
154
136
|
this.update()
|
|
155
137
|
return
|
|
156
138
|
}
|
|
139
|
+
|
|
157
140
|
super.onchange(after, before)
|
|
158
141
|
}
|
|
159
142
|
}
|
|
@@ -13,26 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
import * as THREE from 'three'
|
|
15
15
|
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
16
|
-
import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene'
|
|
16
|
+
import { RealObjectGroup, registerRealObjectFactory, MaterialBank } 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,34 +58,56 @@ 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
|
-
|
|
77
|
-
|
|
78
|
-
metalness: 0.85,
|
|
79
|
-
roughness: 0.35,
|
|
80
|
-
})
|
|
88
|
+
// MaterialBank 공유(dedup). 자체 update() 없이 base 생명주기(guarded disposeObject3D) 상속 → 안전.
|
|
89
|
+
const frameMaterial = MaterialBank.material('conveyor|frame', { metalness: 0.85, roughness: 0.35 }, FRAME_COLOR)
|
|
81
90
|
|
|
82
91
|
// --- Frame: side rails + legs + cross-bracing ---
|
|
83
92
|
// Y 좌표: y=0(바닥) ~ y=depth(상단). 레일 상단이 depth에 위치.
|
|
84
93
|
const frameGeometries: THREE.BufferGeometry[] = []
|
|
85
94
|
const railY = depth - frameH / 2
|
|
86
95
|
|
|
87
|
-
// Side rails
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
// Side rails — 이송 방향을 따라, 벨트 양측에 배치
|
|
97
|
+
if (isVertical) {
|
|
98
|
+
// 레일이 Z축(세로)을 따라, ±X에 배치
|
|
99
|
+
for (const xSign of [-1, 1]) {
|
|
100
|
+
const rail = new THREE.BoxGeometry(railW, frameH, height)
|
|
101
|
+
rail.translate(xSign * (width / 2 - railW / 2), railY, 0)
|
|
102
|
+
frameGeometries.push(rail)
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
// 레일이 X축(가로)을 따라, ±Z에 배치
|
|
106
|
+
for (const zSign of [-1, 1]) {
|
|
107
|
+
const rail = new THREE.BoxGeometry(width, frameH, railW)
|
|
108
|
+
rail.translate(0, railY, zSign * (height / 2 - railW / 2))
|
|
109
|
+
frameGeometries.push(rail)
|
|
110
|
+
}
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
// 4 legs at corners
|
|
@@ -96,50 +115,70 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
96
115
|
const legTopY = depth - frameH
|
|
97
116
|
const legCenterY = legTopY - legH / 2
|
|
98
117
|
|
|
118
|
+
// 다리 위치: 레일 안쪽에 정렬
|
|
119
|
+
const legXPos = isVertical ? (width / 2 - railW / 2) : (width / 2 - legThickness / 2)
|
|
120
|
+
const legZPos = isVertical ? (height / 2 - legThickness / 2) : (height / 2 - railW / 2)
|
|
121
|
+
|
|
99
122
|
for (const xSign of [-1, 1]) {
|
|
100
123
|
for (const zSign of [-1, 1]) {
|
|
101
|
-
const lx = xSign * (width / 2 - legThickness / 2)
|
|
102
|
-
const lz = zSign * (height / 2 - railW / 2)
|
|
103
124
|
const leg = new THREE.BoxGeometry(legThickness, legH, legThickness)
|
|
104
|
-
leg.translate(
|
|
125
|
+
leg.translate(xSign * legXPos, legCenterY, zSign * legZPos)
|
|
105
126
|
frameGeometries.push(leg)
|
|
106
127
|
}
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
// Cross-bracing
|
|
130
|
+
// Cross-bracing
|
|
110
131
|
const braceH = legThickness * 0.6
|
|
111
132
|
const braceW = legThickness * 0.6
|
|
112
|
-
const braceLen = height - railW
|
|
113
133
|
const braceY = legTopY - legH * 0.35
|
|
114
134
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
// 이송 방향 브레이스 (레일과 평행)
|
|
136
|
+
if (isVertical) {
|
|
137
|
+
for (const xSign of [-1, 1]) {
|
|
138
|
+
const brace = new THREE.BoxGeometry(braceW, braceH, height - legThickness)
|
|
139
|
+
brace.translate(xSign * legXPos, braceY, 0)
|
|
140
|
+
frameGeometries.push(brace)
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
for (const zSign of [-1, 1]) {
|
|
144
|
+
const brace = new THREE.BoxGeometry(width - legThickness, braceH, braceW)
|
|
145
|
+
brace.translate(0, braceY, zSign * legZPos)
|
|
146
|
+
frameGeometries.push(brace)
|
|
147
|
+
}
|
|
119
148
|
}
|
|
120
149
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
// 벨트폭 방향 브레이스 (레일과 수직)
|
|
151
|
+
if (isVertical) {
|
|
152
|
+
for (const zSign of [-1, 1]) {
|
|
153
|
+
const brace = new THREE.BoxGeometry(width - railW, braceH, braceW)
|
|
154
|
+
brace.translate(0, braceY, zSign * legZPos)
|
|
155
|
+
frameGeometries.push(brace)
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
for (const xSign of [-1, 1]) {
|
|
159
|
+
const brace = new THREE.BoxGeometry(braceW, braceH, height - railW)
|
|
160
|
+
brace.translate(xSign * legXPos, braceY, 0)
|
|
161
|
+
frameGeometries.push(brace)
|
|
162
|
+
}
|
|
127
163
|
}
|
|
128
164
|
}
|
|
129
165
|
|
|
130
166
|
const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial)
|
|
131
167
|
frameMesh.castShadow = true
|
|
132
168
|
frameMesh.receiveShadow = true
|
|
133
|
-
this.
|
|
169
|
+
this.components3D.add(frameMesh)
|
|
134
170
|
|
|
135
171
|
// --- Rollers, Belt, or Rails ---
|
|
136
172
|
if (conveyorType === 'rail') {
|
|
137
|
-
this.buildRails(width, height, depth, frameH, railW)
|
|
173
|
+
this.buildRails(width, height, depth, frameH, railW, isVertical)
|
|
138
174
|
} else if (conveyorType === 'belt') {
|
|
139
|
-
this.buildBelt(width, height, depth, frameH, railW)
|
|
175
|
+
this.buildBelt(width, height, depth, frameH, railW, isVertical)
|
|
140
176
|
} else {
|
|
141
|
-
this.buildRollers(width, height, depth, frameH, railW, rollWidth)
|
|
177
|
+
this.buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical)
|
|
142
178
|
}
|
|
179
|
+
|
|
180
|
+
// mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).
|
|
181
|
+
this.updateAlpha()
|
|
143
182
|
}
|
|
144
183
|
|
|
145
184
|
private buildRollers(
|
|
@@ -148,87 +187,111 @@ 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) {
|
|
176
232
|
const rollerMesh = new THREE.Mesh(
|
|
177
233
|
BufferGeometryUtils.mergeGeometries(rollerGeometries),
|
|
178
|
-
|
|
234
|
+
MaterialBank.material('conveyor|roller', { metalness: 0.9, roughness: 0.2 }, color)
|
|
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
|
|
|
204
269
|
const drumMesh = new THREE.Mesh(
|
|
205
270
|
BufferGeometryUtils.mergeGeometries(drumGeometries),
|
|
206
|
-
|
|
271
|
+
MaterialBank.material('conveyor|drum', { metalness: 0.9, roughness: 0.2 }, ROLLER_COLOR)
|
|
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
|
|
213
|
-
const beltMaterial =
|
|
214
|
-
color: color,
|
|
215
|
-
metalness: 0.0,
|
|
216
|
-
roughness: 0.9,
|
|
217
|
-
})
|
|
278
|
+
const beltMaterial = MaterialBank.material('conveyor|belt', { metalness: 0.0, roughness: 0.9 }, color)
|
|
218
279
|
|
|
219
280
|
// Flat belt top surface spanning between drums
|
|
220
|
-
const flatLen =
|
|
221
|
-
const flatGeo =
|
|
281
|
+
const flatLen = drum2 - drum1
|
|
282
|
+
const flatGeo = isVertical
|
|
283
|
+
? new THREE.BoxGeometry(drumLength, beltThickness, flatLen)
|
|
284
|
+
: new THREE.BoxGeometry(flatLen, beltThickness, drumLength)
|
|
222
285
|
const flatMesh = new THREE.Mesh(flatGeo, beltMaterial)
|
|
223
286
|
flatMesh.position.set(0, beltY + drumRadius, 0)
|
|
224
287
|
flatMesh.castShadow = true
|
|
225
|
-
this.
|
|
288
|
+
this.components3D.add(flatMesh)
|
|
226
289
|
|
|
227
290
|
// Belt wrap around drums (segmented half-cylinder)
|
|
228
291
|
const wrapSegments = 12
|
|
229
|
-
for (const [
|
|
230
|
-
[
|
|
231
|
-
[
|
|
292
|
+
for (const [dp, angleStart] of [
|
|
293
|
+
[drum1, Math.PI / 2],
|
|
294
|
+
[drum2, -Math.PI / 2],
|
|
232
295
|
] as [number, number][]) {
|
|
233
296
|
const wrapGeometries: THREE.BufferGeometry[] = []
|
|
234
297
|
for (let i = 0; i < wrapSegments; i++) {
|
|
@@ -236,65 +299,82 @@ export class Conveyor3D extends RealObjectGroup {
|
|
|
236
299
|
const a1 = angleStart + (Math.PI * (i + 1)) / wrapSegments
|
|
237
300
|
const r = drumRadius + beltThickness / 2
|
|
238
301
|
|
|
239
|
-
const
|
|
302
|
+
const p0 = dp + Math.cos(a0) * r
|
|
240
303
|
const y0 = beltY + Math.sin(a0) * r
|
|
241
|
-
const
|
|
304
|
+
const p1 = dp + Math.cos(a1) * r
|
|
242
305
|
const y1 = beltY + Math.sin(a1) * r
|
|
243
306
|
|
|
244
|
-
const segLen = Math.sqrt((
|
|
245
|
-
const segAngle = Math.atan2(y1 - y0,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
307
|
+
const segLen = Math.sqrt((p1 - p0) ** 2 + (y1 - y0) ** 2) * 1.05
|
|
308
|
+
const segAngle = Math.atan2(y1 - y0, p1 - p0)
|
|
309
|
+
|
|
310
|
+
if (isVertical) {
|
|
311
|
+
const seg = new THREE.BoxGeometry(drumLength, beltThickness, segLen)
|
|
312
|
+
seg.rotateX(-segAngle)
|
|
313
|
+
seg.translate(0, (y0 + y1) / 2, (p0 + p1) / 2)
|
|
314
|
+
wrapGeometries.push(seg)
|
|
315
|
+
} else {
|
|
316
|
+
const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength)
|
|
317
|
+
seg.rotateZ(segAngle)
|
|
318
|
+
seg.translate((p0 + p1) / 2, (y0 + y1) / 2, 0)
|
|
319
|
+
wrapGeometries.push(seg)
|
|
320
|
+
}
|
|
251
321
|
}
|
|
252
322
|
const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial)
|
|
253
323
|
wrapMesh.castShadow = true
|
|
254
|
-
this.
|
|
324
|
+
this.components3D.add(wrapMesh)
|
|
255
325
|
}
|
|
256
326
|
|
|
257
327
|
// Flat belt bottom (return path)
|
|
258
|
-
const bottomGeo =
|
|
328
|
+
const bottomGeo = isVertical
|
|
329
|
+
? new THREE.BoxGeometry(drumLength * 0.85, beltThickness, flatLen)
|
|
330
|
+
: new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)
|
|
259
331
|
const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial)
|
|
260
332
|
bottomMesh.position.set(0, beltY - drumRadius, 0)
|
|
261
|
-
this.
|
|
333
|
+
this.components3D.add(bottomMesh)
|
|
262
334
|
}
|
|
263
335
|
|
|
264
|
-
private buildRails(width: number, height: number, depth: number, frameH: number, railW: number) {
|
|
336
|
+
private buildRails(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
|
|
265
337
|
const color = this.resolveColor()
|
|
266
338
|
const railHeight = frameH * 0.3
|
|
267
339
|
const railY = depth - railHeight / 2
|
|
268
|
-
const
|
|
340
|
+
const beltWidth = isVertical ? width : height
|
|
341
|
+
const transportLen = isVertical ? height : width
|
|
342
|
+
const innerWidth = beltWidth - railW * 2
|
|
269
343
|
// 두 레일 간격: 내부 폭의 60%
|
|
270
344
|
const railSpacing = innerWidth * 0.6
|
|
271
345
|
|
|
272
|
-
const railMaterial =
|
|
273
|
-
color,
|
|
274
|
-
metalness: 0.7,
|
|
275
|
-
roughness: 0.3,
|
|
276
|
-
})
|
|
346
|
+
const railMaterial = MaterialBank.material('conveyor|rail', { metalness: 0.7, roughness: 0.3 }, color)
|
|
277
347
|
|
|
278
348
|
const railGeometries: THREE.BufferGeometry[] = []
|
|
279
349
|
|
|
280
|
-
// Two parallel rails running along
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
350
|
+
// Two parallel rails running along transport direction
|
|
351
|
+
if (isVertical) {
|
|
352
|
+
for (const xSign of [-1, 1]) {
|
|
353
|
+
const rail = new THREE.BoxGeometry(railW * 0.8, railHeight, transportLen - railW * 2)
|
|
354
|
+
rail.translate(xSign * (railSpacing / 2), railY, 0)
|
|
355
|
+
railGeometries.push(rail)
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
for (const zSign of [-1, 1]) {
|
|
359
|
+
const rail = new THREE.BoxGeometry(transportLen - railW * 2, railHeight, railW * 0.8)
|
|
360
|
+
rail.translate(0, railY, zSign * (railSpacing / 2))
|
|
361
|
+
railGeometries.push(rail)
|
|
362
|
+
}
|
|
285
363
|
}
|
|
286
364
|
|
|
287
365
|
const railMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(railGeometries), railMaterial)
|
|
288
366
|
railMesh.castShadow = true
|
|
289
|
-
this.
|
|
367
|
+
this.components3D.add(railMesh)
|
|
290
368
|
}
|
|
291
369
|
|
|
292
370
|
updateDimension() {}
|
|
293
|
-
updateAlpha()
|
|
371
|
+
// updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).
|
|
372
|
+
// view 모드는 factor=1 → build-시 opacity 그대로(불변), 편집 모드만 faint 추가.
|
|
294
373
|
|
|
295
374
|
onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
|
|
296
375
|
if (
|
|
297
376
|
'conveyorType' in after ||
|
|
377
|
+
'orientation' in after ||
|
|
298
378
|
'width' in after ||
|
|
299
379
|
'height' in after ||
|
|
300
380
|
'depth' in after ||
|