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

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 (75) hide show
  1. package/dist/agv.js +2 -2
  2. package/dist/agv.js.map +1 -1
  3. package/dist/carrier-manager.js +93 -0
  4. package/dist/carrier-manager.js.map +1 -0
  5. package/dist/carrier.js +51 -6
  6. package/dist/carrier.js.map +1 -1
  7. package/dist/conveyor-join.js +8 -2
  8. package/dist/conveyor-join.js.map +1 -1
  9. package/dist/conveyor.js +8 -0
  10. package/dist/conveyor.js.map +1 -1
  11. package/dist/crane.js.map +1 -1
  12. package/dist/factories/agv-3d.js +36 -8
  13. package/dist/factories/agv-3d.js.map +1 -1
  14. package/dist/factories/conveyor-3d.js +159 -69
  15. package/dist/factories/conveyor-3d.js.map +1 -1
  16. package/dist/factories/machine-3d.js +449 -62
  17. package/dist/factories/machine-3d.js.map +1 -1
  18. package/dist/factories/oht-3d.js +50 -24
  19. package/dist/factories/oht-3d.js.map +1 -1
  20. package/dist/factories/shuttle-3d.js +32 -6
  21. package/dist/factories/shuttle-3d.js.map +1 -1
  22. package/dist/index.js +50 -34
  23. package/dist/index.js.map +1 -1
  24. package/dist/mcs-carrier-holder.js +39 -22
  25. package/dist/mcs-carrier-holder.js.map +1 -1
  26. package/dist/mcs-vehicle.js +8 -3
  27. package/dist/mcs-vehicle.js.map +1 -1
  28. package/dist/node.js +1 -1
  29. package/dist/node.js.map +1 -1
  30. package/dist/oht.js.map +1 -1
  31. package/dist/scanner.js +30 -0
  32. package/dist/scanner.js.map +1 -0
  33. package/dist/scene/root-container-override.js +9 -0
  34. package/dist/scene/root-container-override.js.map +1 -1
  35. package/dist/stocker-capacity-bar.js +2 -2
  36. package/dist/stocker-capacity-bar.js.map +1 -1
  37. package/dist/templates/index.js +2 -0
  38. package/dist/templates/index.js.map +1 -1
  39. package/dist/templates/scanner.js +20 -0
  40. package/dist/templates/scanner.js.map +1 -0
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/utils/get-value-on-ranges.js +2 -4
  43. package/dist/utils/get-value-on-ranges.js.map +1 -1
  44. package/dist/zone-capacity-bar.js +2 -2
  45. package/dist/zone-capacity-bar.js.map +1 -1
  46. package/jest.config.js +10 -0
  47. package/package.json +4 -3
  48. package/src/agv.ts +2 -2
  49. package/src/carrier-manager.ts +117 -0
  50. package/src/carrier.ts +50 -4
  51. package/src/conveyor-join.ts +5 -2
  52. package/src/conveyor.ts +8 -0
  53. package/src/crane.ts +1 -1
  54. package/src/factories/agv-3d.ts +36 -8
  55. package/src/factories/conveyor-3d.ts +161 -71
  56. package/src/factories/machine-3d.ts +535 -56
  57. package/src/factories/oht-3d.ts +48 -24
  58. package/src/factories/shuttle-3d.ts +30 -6
  59. package/src/index.ts +56 -34
  60. package/src/mcs-carrier-holder.ts +38 -34
  61. package/src/mcs-vehicle.ts +9 -6
  62. package/src/node.ts +1 -1
  63. package/src/oht.ts +1 -1
  64. package/src/scanner.ts +29 -0
  65. package/src/scene/root-container-override.ts +12 -0
  66. package/src/stocker-capacity-bar.ts +2 -2
  67. package/src/templates/index.ts +2 -0
  68. package/src/templates/scanner.ts +20 -0
  69. package/src/utils/get-value-on-ranges.ts +2 -4
  70. package/src/zone-capacity-bar.ts +2 -2
  71. package/translations/en.json +2 -1
  72. package/translations/ja.json +2 -1
  73. package/translations/ko.json +2 -1
  74. package/translations/ms.json +2 -1
  75. package/translations/zh.json +2 -1
@@ -40,14 +40,14 @@ export class Conveyor3D extends RealObjectGroup {
40
40
  // '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
41
41
  try {
42
42
  const sc = comp.statusColor
43
- if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()
43
+ if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent') return new THREE.Color(sc).getHex()
44
44
  } catch {
45
45
  /* statusColor 미구현 시 무시 */
46
46
  }
47
47
 
48
48
  // 2) state.fillStyle fallback
49
49
  const { fillStyle } = this.component.state
50
- if (fillStyle && typeof fillStyle === 'string') {
50
+ if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
51
51
  try {
52
52
  return new THREE.Color(fillStyle).getHex()
53
53
  } catch {
@@ -61,16 +61,31 @@ export class Conveyor3D extends RealObjectGroup {
61
61
  build() {
62
62
  super.build()
63
63
 
64
- const { width, height, conveyorType } = this.component.state
64
+ const { width: rawWidth, height: rawHeight, conveyorType, orientation } = this.component.state
65
65
  const depth = this.effectiveDepth
66
- if (!width || !height) return
66
+ if (!rawWidth || !rawHeight) return
67
+
68
+ // orientation: 'horizontal' = 롤러가 Z축(세로), 'vertical' = 롤러가 X축(가로)
69
+ // default = 긴 방향에 수직 (긴 축을 따라 이송)
70
+ const isVertical = orientation === 'vertical' ? true
71
+ : orientation === 'horizontal' ? false
72
+ : rawWidth < rawHeight // default: 세로가 길면 롤러는 가로(X축)
73
+ // isVertical=true → 롤러 축이 X(가로), 이송 방향이 Z(세로)
74
+ // isVertical=false → 롤러 축이 Z(세로), 이송 방향이 X(가로)
75
+ const width = rawWidth
76
+ const height = rawHeight
67
77
 
68
78
  // 2D와 동일 로직으로 rollWidth 산출
69
79
  const rollWidth = Math.max(10, Math.min(width, height) / 2)
70
80
  const rollerDiameter = Math.max(rollWidth, 2)
71
81
  const frameH = Math.min(rollerDiameter, depth)
72
82
  const legH = Math.max(depth - frameH, 0)
73
- const railW = Math.max(height * 0.06, 2)
83
+
84
+ // isVertical일 때 이송 방향이 Z축: 레일은 Z축을 따라, 벨트폭은 width
85
+ // !isVertical일 때 이송 방향이 X축: 레일은 X축을 따라, 벨트폭은 height
86
+ const beltWidth = isVertical ? width : height
87
+ const railLen = isVertical ? height : width
88
+ const railW = Math.max(beltWidth * 0.06, 2)
74
89
  const legThickness = Math.max(railW * 0.8, 2)
75
90
 
76
91
  const frameMaterial = new THREE.MeshStandardMaterial({
@@ -84,11 +99,21 @@ export class Conveyor3D extends RealObjectGroup {
84
99
  const frameGeometries: THREE.BufferGeometry[] = []
85
100
  const railY = depth - frameH / 2
86
101
 
87
- // Side rails (along X)
88
- for (const zSign of [-1, 1]) {
89
- const rail = new THREE.BoxGeometry(width, frameH, railW)
90
- rail.translate(0, railY, zSign * (height / 2 - railW / 2))
91
- frameGeometries.push(rail)
102
+ // Side rails — 이송 방향을 따라, 벨트 양측에 배치
103
+ if (isVertical) {
104
+ // 레일이 Z축(세로)을 따라, ±X에 배치
105
+ for (const xSign of [-1, 1]) {
106
+ const rail = new THREE.BoxGeometry(railW, frameH, height)
107
+ rail.translate(xSign * (width / 2 - railW / 2), railY, 0)
108
+ frameGeometries.push(rail)
109
+ }
110
+ } else {
111
+ // 레일이 X축(가로)을 따라, ±Z에 배치
112
+ for (const zSign of [-1, 1]) {
113
+ const rail = new THREE.BoxGeometry(width, frameH, railW)
114
+ rail.translate(0, railY, zSign * (height / 2 - railW / 2))
115
+ frameGeometries.push(rail)
116
+ }
92
117
  }
93
118
 
94
119
  // 4 legs at corners
@@ -96,34 +121,51 @@ export class Conveyor3D extends RealObjectGroup {
96
121
  const legTopY = depth - frameH
97
122
  const legCenterY = legTopY - legH / 2
98
123
 
124
+ // 다리 위치: 레일 안쪽에 정렬
125
+ const legXPos = isVertical ? (width / 2 - railW / 2) : (width / 2 - legThickness / 2)
126
+ const legZPos = isVertical ? (height / 2 - legThickness / 2) : (height / 2 - railW / 2)
127
+
99
128
  for (const xSign of [-1, 1]) {
100
129
  for (const zSign of [-1, 1]) {
101
- const lx = xSign * (width / 2 - legThickness / 2)
102
- const lz = zSign * (height / 2 - railW / 2)
103
130
  const leg = new THREE.BoxGeometry(legThickness, legH, legThickness)
104
- leg.translate(lx, legCenterY, lz)
131
+ leg.translate(xSign * legXPos, legCenterY, zSign * legZPos)
105
132
  frameGeometries.push(leg)
106
133
  }
107
134
  }
108
135
 
109
- // Cross-bracing: 앞뒤 다리 연결 (Z 방향)
136
+ // Cross-bracing
110
137
  const braceH = legThickness * 0.6
111
138
  const braceW = legThickness * 0.6
112
- const braceLen = height - railW
113
139
  const braceY = legTopY - legH * 0.35
114
140
 
115
- for (const xSign of [-1, 1]) {
116
- const brace = new THREE.BoxGeometry(braceW, braceH, braceLen)
117
- brace.translate(xSign * (width / 2 - legThickness / 2), braceY, 0)
118
- frameGeometries.push(brace)
141
+ // 이송 방향 브레이스 (레일과 평행)
142
+ if (isVertical) {
143
+ for (const xSign of [-1, 1]) {
144
+ const brace = new THREE.BoxGeometry(braceW, braceH, height - legThickness)
145
+ brace.translate(xSign * legXPos, braceY, 0)
146
+ frameGeometries.push(brace)
147
+ }
148
+ } else {
149
+ for (const zSign of [-1, 1]) {
150
+ const brace = new THREE.BoxGeometry(width - legThickness, braceH, braceW)
151
+ brace.translate(0, braceY, zSign * legZPos)
152
+ frameGeometries.push(brace)
153
+ }
119
154
  }
120
155
 
121
- // Cross-bracing: 좌우 다리 연결 (X 방향)
122
- const braceLenX = width - legThickness
123
- for (const zSign of [-1, 1]) {
124
- const brace = new THREE.BoxGeometry(braceLenX, braceH, braceW)
125
- brace.translate(0, braceY, zSign * (height / 2 - railW / 2))
126
- frameGeometries.push(brace)
156
+ // 벨트폭 방향 브레이스 (레일과 수직)
157
+ if (isVertical) {
158
+ for (const zSign of [-1, 1]) {
159
+ const brace = new THREE.BoxGeometry(width - railW, braceH, braceW)
160
+ brace.translate(0, braceY, zSign * legZPos)
161
+ frameGeometries.push(brace)
162
+ }
163
+ } else {
164
+ for (const xSign of [-1, 1]) {
165
+ const brace = new THREE.BoxGeometry(braceW, braceH, height - railW)
166
+ brace.translate(xSign * legXPos, braceY, 0)
167
+ frameGeometries.push(brace)
168
+ }
127
169
  }
128
170
  }
129
171
 
@@ -134,11 +176,11 @@ export class Conveyor3D extends RealObjectGroup {
134
176
 
135
177
  // --- Rollers, Belt, or Rails ---
136
178
  if (conveyorType === 'rail') {
137
- this.buildRails(width, height, depth, frameH, railW)
179
+ this.buildRails(width, height, depth, frameH, railW, isVertical)
138
180
  } else if (conveyorType === 'belt') {
139
- this.buildBelt(width, height, depth, frameH, railW)
181
+ this.buildBelt(width, height, depth, frameH, railW, isVertical)
140
182
  } else {
141
- this.buildRollers(width, height, depth, frameH, railW, rollWidth)
183
+ this.buildRollers(width, height, depth, frameH, railW, rollWidth, isVertical)
142
184
  }
143
185
  }
144
186
 
@@ -148,28 +190,45 @@ export class Conveyor3D extends RealObjectGroup {
148
190
  depth: number,
149
191
  frameH: number,
150
192
  railW: number,
151
- rollWidth: number
193
+ rollWidth: number,
194
+ isVertical: boolean = false
152
195
  ) {
153
196
  const rollerRadius = Math.max(rollWidth / 8, 1)
154
- const rollerLength = height - railW * 2 - 0.5
155
197
  const diameter = rollerRadius * 2
156
- // 롤러 간 최소 간격 (z-fighting 방지용 미세 갭만)
157
198
  const step = diameter * 1.05
158
- // 롤러 상단 = 이송 표면 (y=depth)
159
199
  const rollerY = depth - rollerRadius
160
200
  const color = this.resolveColor()
161
201
 
162
202
  const rollerGeometries: THREE.BufferGeometry[] = []
163
- const count = Math.max(1, Math.floor(width / step))
164
- const totalSpan = (count - 1) * step
165
- const startX = -totalSpan / 2
166
-
167
- for (let i = 0; i < count; i++) {
168
- const x = startX + i * step
169
- const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
170
- roller.rotateX(Math.PI / 2)
171
- roller.translate(x, rollerY, 0)
172
- rollerGeometries.push(roller)
203
+
204
+ if (isVertical) {
205
+ // 롤러 축 = X축(가로), 이송 방향 = Z축(세로)
206
+ const rollerLength = width - railW * 2 - 0.5
207
+ const count = Math.max(1, Math.floor(height / step))
208
+ const totalSpan = (count - 1) * step
209
+ const startZ = -totalSpan / 2
210
+
211
+ for (let i = 0; i < count; i++) {
212
+ const z = startZ + i * step
213
+ const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
214
+ roller.rotateZ(Math.PI / 2)
215
+ roller.translate(0, rollerY, z)
216
+ rollerGeometries.push(roller)
217
+ }
218
+ } else {
219
+ // 롤러 축 = Z축(세로), 이송 방향 = X축(가로) — 기존 동작
220
+ const rollerLength = height - railW * 2 - 0.5
221
+ const count = Math.max(1, Math.floor(width / step))
222
+ const totalSpan = (count - 1) * step
223
+ const startX = -totalSpan / 2
224
+
225
+ for (let i = 0; i < count; i++) {
226
+ const x = startX + i * step
227
+ const roller = new THREE.CylinderGeometry(rollerRadius, rollerRadius, rollerLength, 16)
228
+ roller.rotateX(Math.PI / 2)
229
+ roller.translate(x, rollerY, 0)
230
+ rollerGeometries.push(roller)
231
+ }
173
232
  }
174
233
 
175
234
  if (rollerGeometries.length > 0) {
@@ -182,22 +241,31 @@ export class Conveyor3D extends RealObjectGroup {
182
241
  }
183
242
  }
184
243
 
185
- private buildBelt(width: number, height: number, depth: number, frameH: number, railW: number) {
244
+ private buildBelt(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
186
245
  const drumRadius = frameH * 0.38
187
- const drumLength = height - railW * 2
188
246
  const beltY = depth - drumRadius
189
247
  const color = this.resolveColor()
190
248
 
249
+ // 이송 방향 길이와 벨트폭
250
+ const transportLen = isVertical ? height : width
251
+ const beltWidth = isVertical ? width : height
252
+ const drumLength = beltWidth - railW * 2
253
+
191
254
  // Two end drums
192
255
  const drumGeometries: THREE.BufferGeometry[] = []
193
256
  const drumInset = drumRadius * 1.5
194
- const leftDrumX = -width / 2 + drumInset
195
- const rightDrumX = width / 2 - drumInset
257
+ const drum1 = -transportLen / 2 + drumInset
258
+ const drum2 = transportLen / 2 - drumInset
196
259
 
197
- for (const dx of [leftDrumX, rightDrumX]) {
260
+ for (const dp of [drum1, drum2]) {
198
261
  const drum = new THREE.CylinderGeometry(drumRadius, drumRadius, drumLength, 16)
199
- drum.rotateX(Math.PI / 2)
200
- drum.translate(dx, beltY, 0)
262
+ if (isVertical) {
263
+ drum.rotateZ(Math.PI / 2)
264
+ drum.translate(0, beltY, dp)
265
+ } else {
266
+ drum.rotateX(Math.PI / 2)
267
+ drum.translate(dp, beltY, 0)
268
+ }
201
269
  drumGeometries.push(drum)
202
270
  }
203
271
 
@@ -217,8 +285,10 @@ export class Conveyor3D extends RealObjectGroup {
217
285
  })
218
286
 
219
287
  // Flat belt top surface spanning between drums
220
- const flatLen = rightDrumX - leftDrumX
221
- const flatGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength)
288
+ const flatLen = drum2 - drum1
289
+ const flatGeo = isVertical
290
+ ? new THREE.BoxGeometry(drumLength, beltThickness, flatLen)
291
+ : new THREE.BoxGeometry(flatLen, beltThickness, drumLength)
222
292
  const flatMesh = new THREE.Mesh(flatGeo, beltMaterial)
223
293
  flatMesh.position.set(0, beltY + drumRadius, 0)
224
294
  flatMesh.castShadow = true
@@ -226,9 +296,9 @@ export class Conveyor3D extends RealObjectGroup {
226
296
 
227
297
  // Belt wrap around drums (segmented half-cylinder)
228
298
  const wrapSegments = 12
229
- for (const [dx, angleStart] of [
230
- [leftDrumX, Math.PI / 2],
231
- [rightDrumX, -Math.PI / 2],
299
+ for (const [dp, angleStart] of [
300
+ [drum1, Math.PI / 2],
301
+ [drum2, -Math.PI / 2],
232
302
  ] as [number, number][]) {
233
303
  const wrapGeometries: THREE.BufferGeometry[] = []
234
304
  for (let i = 0; i < wrapSegments; i++) {
@@ -236,18 +306,25 @@ export class Conveyor3D extends RealObjectGroup {
236
306
  const a1 = angleStart + (Math.PI * (i + 1)) / wrapSegments
237
307
  const r = drumRadius + beltThickness / 2
238
308
 
239
- const x0 = dx + Math.cos(a0) * r
309
+ const p0 = dp + Math.cos(a0) * r
240
310
  const y0 = beltY + Math.sin(a0) * r
241
- const x1 = dx + Math.cos(a1) * r
311
+ const p1 = dp + Math.cos(a1) * r
242
312
  const y1 = beltY + Math.sin(a1) * r
243
313
 
244
- const segLen = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) * 1.05
245
- const segAngle = Math.atan2(y1 - y0, x1 - x0)
246
-
247
- const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength)
248
- seg.rotateZ(segAngle)
249
- seg.translate((x0 + x1) / 2, (y0 + y1) / 2, 0)
250
- wrapGeometries.push(seg)
314
+ const segLen = Math.sqrt((p1 - p0) ** 2 + (y1 - y0) ** 2) * 1.05
315
+ const segAngle = Math.atan2(y1 - y0, p1 - p0)
316
+
317
+ if (isVertical) {
318
+ const seg = new THREE.BoxGeometry(drumLength, beltThickness, segLen)
319
+ seg.rotateX(-segAngle)
320
+ seg.translate(0, (y0 + y1) / 2, (p0 + p1) / 2)
321
+ wrapGeometries.push(seg)
322
+ } else {
323
+ const seg = new THREE.BoxGeometry(segLen, beltThickness, drumLength)
324
+ seg.rotateZ(segAngle)
325
+ seg.translate((p0 + p1) / 2, (y0 + y1) / 2, 0)
326
+ wrapGeometries.push(seg)
327
+ }
251
328
  }
252
329
  const wrapMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(wrapGeometries), beltMaterial)
253
330
  wrapMesh.castShadow = true
@@ -255,17 +332,21 @@ export class Conveyor3D extends RealObjectGroup {
255
332
  }
256
333
 
257
334
  // Flat belt bottom (return path)
258
- const bottomGeo = new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)
335
+ const bottomGeo = isVertical
336
+ ? new THREE.BoxGeometry(drumLength * 0.85, beltThickness, flatLen)
337
+ : new THREE.BoxGeometry(flatLen, beltThickness, drumLength * 0.85)
259
338
  const bottomMesh = new THREE.Mesh(bottomGeo, beltMaterial)
260
339
  bottomMesh.position.set(0, beltY - drumRadius, 0)
261
340
  this.object3d.add(bottomMesh)
262
341
  }
263
342
 
264
- private buildRails(width: number, height: number, depth: number, frameH: number, railW: number) {
343
+ private buildRails(width: number, height: number, depth: number, frameH: number, railW: number, isVertical: boolean = false) {
265
344
  const color = this.resolveColor()
266
345
  const railHeight = frameH * 0.3
267
346
  const railY = depth - railHeight / 2
268
- const innerWidth = height - railW * 2
347
+ const beltWidth = isVertical ? width : height
348
+ const transportLen = isVertical ? height : width
349
+ const innerWidth = beltWidth - railW * 2
269
350
  // 두 레일 간격: 내부 폭의 60%
270
351
  const railSpacing = innerWidth * 0.6
271
352
 
@@ -277,11 +358,19 @@ export class Conveyor3D extends RealObjectGroup {
277
358
 
278
359
  const railGeometries: THREE.BufferGeometry[] = []
279
360
 
280
- // Two parallel rails running along X axis
281
- for (const zSign of [-1, 1]) {
282
- const rail = new THREE.BoxGeometry(width - railW * 2, railHeight, railW * 0.8)
283
- rail.translate(0, railY, zSign * (railSpacing / 2))
284
- railGeometries.push(rail)
361
+ // Two parallel rails running along transport direction
362
+ if (isVertical) {
363
+ for (const xSign of [-1, 1]) {
364
+ const rail = new THREE.BoxGeometry(railW * 0.8, railHeight, transportLen - railW * 2)
365
+ rail.translate(xSign * (railSpacing / 2), railY, 0)
366
+ railGeometries.push(rail)
367
+ }
368
+ } else {
369
+ for (const zSign of [-1, 1]) {
370
+ const rail = new THREE.BoxGeometry(transportLen - railW * 2, railHeight, railW * 0.8)
371
+ rail.translate(0, railY, zSign * (railSpacing / 2))
372
+ railGeometries.push(rail)
373
+ }
285
374
  }
286
375
 
287
376
  const railMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(railGeometries), railMaterial)
@@ -295,6 +384,7 @@ export class Conveyor3D extends RealObjectGroup {
295
384
  onchange(after: Record<string, unknown>, before: Record<string, unknown>) {
296
385
  if (
297
386
  'conveyorType' in after ||
387
+ 'orientation' in after ||
298
388
  'width' in after ||
299
389
  'height' in after ||
300
390
  'depth' in after ||