@fmsim/machine 2.0.0-beta.52 → 2.0.0-beta.53

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fmsim/machine",
3
3
  "description": "Layout View를 구성하는 설비(machine)들과 유닛을 구현한 모듈입니다.",
4
4
  "author": "heartyoh",
5
- "version": "2.0.0-beta.52",
5
+ "version": "2.0.0-beta.53",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
8
8
  "things-scene": true,
@@ -55,5 +55,5 @@
55
55
  "prettier --write"
56
56
  ]
57
57
  },
58
- "gitHead": "a670b735fe5d071811d688afb948bf2d79409312"
58
+ "gitHead": "32f43b7ad9ab0db3e0378c29ec43cbebf22eaecc"
59
59
  }
@@ -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
- * - 바닥 원점 (y=0~depth)
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
 
@@ -118,7 +114,8 @@ export class AGV3D extends RealObjectGroup {
118
114
  plat.receiveShadow = true
119
115
 
120
116
  this._ownMeshes = [bumper, body, plat]
121
- for (const m of this._ownMeshes) this.object3d.add(m)
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.object3d.remove(m)
126
+ this.components3D.remove(m)
130
127
  disposeObject3D(m)
131
128
  }
132
129
  this._ownMeshes = []
@@ -27,12 +27,6 @@ export class Conveyor3D extends RealObjectGroup {
27
27
  return depth || TRANSPORT_SURFACE_HEIGHT
28
28
  }
29
29
 
30
- // group origin = 바닥 (y=0). MCSRealObject3D와 동일 컨벤션.
31
- // 기하는 build()에서 y=0(바닥)~y=depth(상단) 범위로 배치.
32
- protected get syncZPosOffset(): number {
33
- return 0
34
- }
35
-
36
30
  private resolveColor(): number {
37
31
  const comp = this.component as any
38
32
 
@@ -172,7 +166,7 @@ export class Conveyor3D extends RealObjectGroup {
172
166
  const frameMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(frameGeometries), frameMaterial)
173
167
  frameMesh.castShadow = true
174
168
  frameMesh.receiveShadow = true
175
- this.object3d.add(frameMesh)
169
+ this.components3D.add(frameMesh)
176
170
 
177
171
  // --- Rollers, Belt, or Rails ---
178
172
  if (conveyorType === 'rail') {
@@ -237,7 +231,7 @@ export class Conveyor3D extends RealObjectGroup {
237
231
  new THREE.MeshStandardMaterial({ color, metalness: 0.9, roughness: 0.2 })
238
232
  )
239
233
  rollerMesh.castShadow = true
240
- this.object3d.add(rollerMesh)
234
+ this.components3D.add(rollerMesh)
241
235
  }
242
236
  }
243
237
 
@@ -274,7 +268,7 @@ export class Conveyor3D extends RealObjectGroup {
274
268
  new THREE.MeshStandardMaterial({ color: ROLLER_COLOR, metalness: 0.9, roughness: 0.2 })
275
269
  )
276
270
  drumMesh.castShadow = true
277
- this.object3d.add(drumMesh)
271
+ this.components3D.add(drumMesh)
278
272
 
279
273
  // Belt surface
280
274
  const beltThickness = drumRadius * 0.12
@@ -292,7 +286,7 @@ export class Conveyor3D extends RealObjectGroup {
292
286
  const flatMesh = new THREE.Mesh(flatGeo, beltMaterial)
293
287
  flatMesh.position.set(0, beltY + drumRadius, 0)
294
288
  flatMesh.castShadow = true
295
- this.object3d.add(flatMesh)
289
+ this.components3D.add(flatMesh)
296
290
 
297
291
  // Belt wrap around drums (segmented half-cylinder)
298
292
  const wrapSegments = 12
@@ -328,7 +322,7 @@ export class Conveyor3D extends RealObjectGroup {
328
322
  }
329
323
  const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial)
330
324
  wrapMesh.castShadow = true
331
- this.object3d.add(wrapMesh)
325
+ this.components3D.add(wrapMesh)
332
326
  }
333
327
 
334
328
  // Flat belt bottom (return path)
@@ -337,7 +331,7 @@ export class Conveyor3D extends RealObjectGroup {
337
331
  : new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)
338
332
  const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial)
339
333
  bottomMesh.position.set(0, beltY - drumRadius, 0)
340
- this.object3d.add(bottomMesh)
334
+ this.components3D.add(bottomMesh)
341
335
  }
342
336
 
343
337
  private buildRails(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
@@ -375,7 +369,7 @@ export class Conveyor3D extends RealObjectGroup {
375
369
 
376
370
  const railMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(railGeometries), railMaterial)
377
371
  railMesh.castShadow = true
378
- this.object3d.add(railMesh)
372
+ this.components3D.add(railMesh)
379
373
  }
380
374
 
381
375
  updateDimension() {}
@@ -27,10 +27,6 @@ export class Crane3D extends RealObjectGroup {
27
27
  return depth || 5 * TRANSPORT_SURFACE_HEIGHT - 2
28
28
  }
29
29
 
30
- protected get syncZPosOffset(): number {
31
- return 0
32
- }
33
-
34
30
  private resolveColor(): number {
35
31
  const comp = this.component as any
36
32
 
@@ -62,6 +58,8 @@ export class Crane3D extends RealObjectGroup {
62
58
  const d = this.effectiveDepth
63
59
  if (!w || !h) return
64
60
 
61
+ const inner = this.components3D
62
+
65
63
  // 높이 배분: 하단 클램프 4%, 마스트 92%, 상단 클램프 4%
66
64
  const clampH = d * 0.04
67
65
  const mastH = d * 0.92
@@ -81,7 +79,7 @@ export class Crane3D extends RealObjectGroup {
81
79
  const bottomClamp = new THREE.Mesh(bottomClampGeo, clampMat)
82
80
  bottomClamp.position.y = clampH / 2
83
81
  bottomClamp.castShadow = true
84
- this.object3d.add(bottomClamp)
82
+ inner.add(bottomClamp)
85
83
 
86
84
  // ── 2. 수직 마스트 (좌우 기둥 + 상·하 가로 연결 바) ──
87
85
  const mastBaseY = clampH
@@ -115,7 +113,7 @@ export class Crane3D extends RealObjectGroup {
115
113
 
116
114
  const mastMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(mastGeometries), mastMat)
117
115
  mastMesh.castShadow = true
118
- this.object3d.add(mastMesh)
116
+ inner.add(mastMesh)
119
117
 
120
118
  // ── 3. 캐리지 포크 (마스트 40% 높이, 양쪽으로 뻗는 포크 암) ──
121
119
  const forkY = mastBaseY + mastH * 0.4
@@ -151,14 +149,14 @@ export class Crane3D extends RealObjectGroup {
151
149
 
152
150
  const forkMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(forkGeometries), forkMat)
153
151
  forkMesh.castShadow = true
154
- this.object3d.add(forkMesh)
152
+ inner.add(forkMesh)
155
153
 
156
154
  // ── 4. 상단 클램프 (천장 레일에 물리는 가이드부) ──
157
155
  const topClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)
158
156
  const topClamp = new THREE.Mesh(topClampGeo, clampMat)
159
157
  topClamp.position.y = mastBaseY + mastH + clampH / 2
160
158
  topClamp.castShadow = true
161
- this.object3d.add(topClamp)
159
+ inner.add(topClamp)
162
160
  }
163
161
 
164
162
  updateDimension() {}
@@ -28,10 +28,6 @@ export class CraneRail3D extends RealObjectGroup {
28
28
  return depth || DEFAULT_RAIL_DEPTH
29
29
  }
30
30
 
31
- protected get syncZPosOffset(): number {
32
- return 0
33
- }
34
-
35
31
  build() {
36
32
  super.build()
37
33
 
@@ -41,6 +37,8 @@ export class CraneRail3D extends RealObjectGroup {
41
37
  const d = this.effectiveDepth
42
38
  if (!w || !h) return
43
39
 
40
+ const inner = this.components3D
41
+
44
42
  const railH = Math.max(d * 0.02, 2) // 레일 높이 (얇은 트랙)
45
43
  const railW = Math.max(h * 0.06, 2) // 레일 폭
46
44
  const supportThick = Math.max(Math.min(w, h) * 0.05, 2)
@@ -67,7 +65,7 @@ export class CraneRail3D extends RealObjectGroup {
67
65
  const bottomMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(bottomGeometries), railMat)
68
66
  bottomMesh.castShadow = true
69
67
  bottomMesh.receiveShadow = true
70
- this.object3d.add(bottomMesh)
68
+ inner.add(bottomMesh)
71
69
 
72
70
  // ── 천장 가이드 레일 (2개 평행 트랙) ──
73
71
  const topGeometries: THREE.BufferGeometry[] = []
@@ -78,7 +76,7 @@ export class CraneRail3D extends RealObjectGroup {
78
76
  }
79
77
  const topMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topGeometries), railMat)
80
78
  topMesh.castShadow = true
81
- this.object3d.add(topMesh)
79
+ inner.add(topMesh)
82
80
 
83
81
  // ── 양 끝단 수직 지지대 (바닥~천장 연결) ──
84
82
  const supportH = d - railH * 2
@@ -96,7 +94,7 @@ export class CraneRail3D extends RealObjectGroup {
96
94
  }
97
95
  const supportMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(supportGeometries), supportMat)
98
96
  supportMesh.castShadow = true
99
- this.object3d.add(supportMesh)
97
+ inner.add(supportMesh)
100
98
  }
101
99
 
102
100
  updateDimension() {}
@@ -91,29 +91,35 @@ class MCSRealObject3D extends RealObjectGroup {
91
91
  }
92
92
 
93
93
  protected get syncZPosOffset(): number {
94
+ // syncZPosOffset = placement + geometric (center origin = effectiveDepth/2)
95
+ // 중첩 보정(parent shift 상쇄)은 base RealObject.cz가 parent.geometricOffsetY를
96
+ // 빼는 방식으로 처리하므로 여기선 placement + 본인 geometric만 반환
97
+ const halfD = this.effectiveDepth / 2
94
98
  // zPosOverride: 고정 높이 (OHTLine 등)
95
99
  if (this._config.zPosOverride != null) {
96
- return this._config.zPosOverride
100
+ return this._config.zPosOverride + halfD
97
101
  }
98
102
  // Port: 이송 표면 높이에 배치
99
103
  if (this._config.defaultZPosFromParent) {
100
- return TRANSPORT_SURFACE_HEIGHT
104
+ return TRANSPORT_SURFACE_HEIGHT + halfD
101
105
  }
102
106
  // Carrier: 부모 유형에 따라 배치 위치 결정
103
107
  if (this._config.sizeRatio && this._config.sizeRatio < 1) {
104
108
  const parentType = this.component.parent?.state?.type
105
- // OHT: 그리퍼 아래에 매달림 (캐리어 상단이 OHT 하단에 붙음)
109
+ // OHT: 그리퍼 아래에 매달림 — placement = -effectiveDepth(carrier top이 OHT bottom)
106
110
  if (parentType === 'OHT') {
107
- return -this.effectiveDepth
111
+ return -this.effectiveDepth + halfD
108
112
  }
109
113
  if (parentType === 'Port' || parentType === 'Shelf' || parentType === 'Shuttle') {
110
- return 0
114
+ return halfD
111
115
  }
112
- return TRANSPORT_SURFACE_HEIGHT
116
+ return TRANSPORT_SURFACE_HEIGHT + halfD
113
117
  }
114
- return 0
118
+ return halfD
115
119
  }
116
120
 
121
+ // geometric 부분(center origin: depth/2)은 base 기본값 사용 — override 불필요
122
+
117
123
  // 컴포넌트의 상태 색상(statusColor) 또는 fillStyle → Three.js 색상 변환
118
124
  protected resolveColor(): number {
119
125
  const comp = this.component as any
@@ -285,11 +291,14 @@ class MCSRealObject3D extends RealObjectGroup {
285
291
  }
286
292
 
287
293
  this._ownMesh = new THREE.Mesh(geometry, meshMaterial)
288
- // sizeRatio < 1이면 부모 중심에 배치 (rawDepth/2), plane은 살짝 띄움
289
- this._ownMesh.position.y = isPlane ? 1 : ratio < 1 ? rawDepth / 2 : depth / 2
294
+ // ownGroup 기준(bottom-up): mesh 바닥을 y=0에 맞추고 ownGroup이 -d/2로 shift해
295
+ // center-origin object3d에 정렬됨.
296
+ // box/hexagon (center-local geometry): y=rawDepth/2 (바닥을 0에 맞춤)
297
+ // plane: y=1 (바닥 살짝 띄움)
298
+ this._ownMesh.position.y = isPlane ? 1 : rawDepth / 2
290
299
  this._ownMesh.castShadow = !isPlane
291
300
  this._ownMesh.receiveShadow = true
292
- this.object3d.add(this._ownMesh)
301
+ this.components3D.add(this._ownMesh)
293
302
  }
294
303
 
295
304
  // ── Change handling ──
@@ -384,7 +393,7 @@ class MCSRealObject3D extends RealObjectGroup {
384
393
  update() {
385
394
  // 자신의 mesh만 제거 (자식 컴포넌트 3D 오브젝트 보존)
386
395
  if (this._ownMesh) {
387
- this.object3d.remove(this._ownMesh)
396
+ this.components3D.remove(this._ownMesh)
388
397
  disposeObject3D(this._ownMesh)
389
398
  this._ownMesh = undefined
390
399
  }
@@ -642,8 +651,9 @@ class StockerRealObject3D extends MCSRealObject3D {
642
651
  mat.polygonOffsetUnit = -2
643
652
 
644
653
  this.gaugeMesh = new THREE.Mesh(geo, mat)
654
+ // ownGroup 기준(bottom-up): 볼륨 상단 = +depth. 그 위로 0.5 띄움.
645
655
  this.gaugeMesh.position.set(0, depth + 0.5, 0)
646
- this.object3d.add(this.gaugeMesh)
656
+ this.components3D.add(this.gaugeMesh)
647
657
  }
648
658
 
649
659
  private resolveGauge(data: any, mode: string) {
@@ -718,7 +728,7 @@ class StockerRealObject3D extends MCSRealObject3D {
718
728
 
719
729
  private clearGauge() {
720
730
  if (this.gaugeMesh) {
721
- this.object3d.remove(this.gaugeMesh)
731
+ this.components3D.remove(this.gaugeMesh)
722
732
  this.gaugeMesh.geometry?.dispose()
723
733
  const mat = this.gaugeMesh.material as THREE.MeshBasicMaterial
724
734
  mat?.map?.dispose()
@@ -30,9 +30,9 @@ export class OHT3D extends RealObjectGroup {
30
30
  return depth || TRANSPORT_SURFACE_HEIGHT
31
31
  }
32
32
 
33
- /** 천장 레일 높이에 배치 */
33
+ /** 천장 레일 높이에 배치 (center origin 기준: 레일 높이 + 볼륨 중심 반깊이) */
34
34
  protected get syncZPosOffset(): number {
35
- return OHT_RAIL_HEIGHT
35
+ return OHT_RAIL_HEIGHT + this.effectiveDepth / 2
36
36
  }
37
37
 
38
38
  private resolveColor(): number {
@@ -155,14 +155,14 @@ export class OHT3D extends RealObjectGroup {
155
155
  }
156
156
 
157
157
  this._ownMeshes = [gripperMesh, armMesh, bodyMesh, trolleyMesh, ...wheelMeshes]
158
- for (const m of this._ownMeshes) this.object3d.add(m)
158
+ for (const m of this._ownMeshes) this.components3D.add(m)
159
159
  }
160
160
 
161
161
  updateDimension() {}
162
162
 
163
163
  update() {
164
164
  for (const m of this._ownMeshes) {
165
- this.object3d.remove(m)
165
+ this.components3D.remove(m)
166
166
  disposeObject3D(m)
167
167
  }
168
168
  this._ownMeshes = []
@@ -27,10 +27,6 @@ export class Shuttle3D extends RealObjectGroup {
27
27
  return depth || TRANSPORT_SURFACE_HEIGHT
28
28
  }
29
29
 
30
- protected get syncZPosOffset(): number {
31
- return 0
32
- }
33
-
34
30
  private resolveColor(): number {
35
31
  const comp = this.component as any
36
32
 
@@ -142,14 +138,14 @@ export class Shuttle3D extends RealObjectGroup {
142
138
  forkMesh.castShadow = true
143
139
 
144
140
  this._ownMeshes = [baseMesh, mastMesh, forkMesh]
145
- for (const m of this._ownMeshes) this.object3d.add(m)
141
+ for (const m of this._ownMeshes) this.components3D.add(m)
146
142
  }
147
143
 
148
144
  updateDimension() {}
149
145
 
150
146
  update() {
151
147
  for (const m of this._ownMeshes) {
152
- this.object3d.remove(m)
148
+ this.components3D.remove(m)
153
149
  disposeObject3D(m)
154
150
  }
155
151
  this._ownMeshes = []