@fmsim/machine 2.0.0-beta.99 → 2.0.1

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 (73) hide show
  1. package/dist/carrier-manager.js +9 -1
  2. package/dist/carrier-manager.js.map +1 -1
  3. package/dist/carrier.js +69 -52
  4. package/dist/carrier.js.map +1 -1
  5. package/dist/crane.js +15 -6
  6. package/dist/crane.js.map +1 -1
  7. package/dist/factories/agv-3d.js +9 -27
  8. package/dist/factories/agv-3d.js.map +1 -1
  9. package/dist/factories/conveyor-3d.js +11 -19
  10. package/dist/factories/conveyor-3d.js.map +1 -1
  11. package/dist/factories/crane-3d.js +8 -17
  12. package/dist/factories/crane-3d.js.map +1 -1
  13. package/dist/factories/crane-rail-3d.js +8 -12
  14. package/dist/factories/crane-rail-3d.js.map +1 -1
  15. package/dist/factories/machine-3d.js +100 -63
  16. package/dist/factories/machine-3d.js.map +1 -1
  17. package/dist/factories/oht-3d.js +9 -27
  18. package/dist/factories/oht-3d.js.map +1 -1
  19. package/dist/factories/shuttle-3d.js +11 -27
  20. package/dist/factories/shuttle-3d.js.map +1 -1
  21. package/dist/features/capacity-gauge-mixin.js +520 -0
  22. package/dist/features/capacity-gauge-mixin.js.map +1 -0
  23. package/dist/features/mcs-status-default.js +13 -0
  24. package/dist/features/mcs-status-default.js.map +1 -1
  25. package/dist/features/parent-object-mixin.js +5 -1
  26. package/dist/features/parent-object-mixin.js.map +1 -1
  27. package/dist/mcs-machine.js +4 -23
  28. package/dist/mcs-machine.js.map +1 -1
  29. package/dist/mcs-unit.js +2 -5
  30. package/dist/mcs-unit.js.map +1 -1
  31. package/dist/mcs-vehicle.js +3 -1
  32. package/dist/mcs-vehicle.js.map +1 -1
  33. package/dist/scene/root-container-override.js +5 -2
  34. package/dist/scene/root-container-override.js.map +1 -1
  35. package/dist/stocker-capacity-bar.js +11 -372
  36. package/dist/stocker-capacity-bar.js.map +1 -1
  37. package/dist/stocker.js +29 -1
  38. package/dist/stocker.js.map +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/dist/utils/crane-position.js +23 -14
  41. package/dist/utils/crane-position.js.map +1 -1
  42. package/dist/utils/stocker-gauge.js +48 -10
  43. package/dist/utils/stocker-gauge.js.map +1 -1
  44. package/dist/zone-capacity-bar.js +13 -213
  45. package/dist/zone-capacity-bar.js.map +1 -1
  46. package/package.json +4 -4
  47. package/src/carrier-manager.ts +9 -1
  48. package/src/carrier.ts +73 -52
  49. package/src/crane.ts +14 -5
  50. package/src/factories/agv-3d.ts +10 -26
  51. package/src/factories/conveyor-3d.ts +12 -19
  52. package/src/factories/crane-3d.ts +9 -17
  53. package/src/factories/crane-rail-3d.ts +9 -13
  54. package/src/factories/machine-3d.ts +125 -66
  55. package/src/factories/oht-3d.ts +10 -26
  56. package/src/factories/shuttle-3d.ts +12 -26
  57. package/src/features/capacity-gauge-mixin.ts +642 -0
  58. package/src/features/mcs-status-default.ts +13 -0
  59. package/src/features/parent-object-mixin.ts +5 -1
  60. package/src/mcs-machine.ts +4 -23
  61. package/src/mcs-unit.ts +2 -5
  62. package/src/mcs-vehicle.ts +3 -1
  63. package/src/scene/root-container-override.ts +5 -2
  64. package/src/stocker-capacity-bar.ts +15 -476
  65. package/src/stocker.ts +29 -1
  66. package/src/utils/crane-position.ts +46 -14
  67. package/src/utils/stocker-gauge.ts +63 -10
  68. package/src/zone-capacity-bar.ts +17 -282
  69. package/translations/en.json +4 -1
  70. package/translations/ja.json +4 -1
  71. package/translations/ko.json +4 -1
  72. package/translations/ms.json +4 -1
  73. package/translations/zh.json +4 -1
@@ -9,11 +9,11 @@
9
9
  */
10
10
  import * as THREE from 'three';
11
11
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
12
- import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene';
13
- import { LEGEND_CAPACITY } from '../features/mcs-status-default';
12
+ import { RealObjectGroup, registerRealObjectFactory, disposeObject3D, MaterialBank } from '@hatiolab/things-scene';
13
+ import { LEGEND_CAPACITY, resolveCarrierLegend } from '../features/mcs-status-default';
14
14
  import { getValueOnRanges } from '../utils/get-value-on-ranges';
15
15
  import { fitGaugeScale } from '../utils/fit-gauge-scale';
16
- import { carrierSegmentsFromData, abnormalSegmentsFromData, CARRIER_COLORS, ABNORMAL_COLORS } from '../utils/stocker-gauge';
16
+ import { carrierSegmentsFromData, abnormalSegmentsFromData, carrierColorsFromLegend, ABNORMAL_COLORS } from '../utils/stocker-gauge';
17
17
  import { Z_FMSIM } from './z-priority';
18
18
  /**
19
19
  * 포트 기본 높이 (절대값).
@@ -232,6 +232,8 @@ class MCSRealObject3D extends RealObjectGroup {
232
232
  ? this.buildTriangleGeometry(depth)
233
233
  : this.buildBoxGeometry(w, depth, h);
234
234
  const isPlane = shape === 'plane';
235
+ // Standard body 는 MaterialBank 공유(색 변형) — 인스턴스 dedup + faint 안전(코어가 공유 base 의
236
+ // 변형만 스왑, 원본 base 불변). plane 은 unlit MeshBasicMaterial 이라 bank(Standard 전용) 비대상 → raw.
235
237
  const material = isPlane
236
238
  ? new THREE.MeshBasicMaterial({
237
239
  color,
@@ -239,11 +241,13 @@ class MCSRealObject3D extends RealObjectGroup {
239
241
  transparent: opacity < 1,
240
242
  side: THREE.DoubleSide,
241
243
  })
242
- : new THREE.MeshStandardMaterial(Object.assign({ color,
243
- metalness,
244
+ : MaterialBank.material('mcs|body', Object.assign({ metalness,
244
245
  roughness,
245
- opacity, transparent: opacity < 1, side: opacity < 1 ? THREE.DoubleSide : THREE.FrontSide }, (this._config.emissiveIntensity ? { emissive: color, emissiveIntensity: this._config.emissiveIntensity } : {})));
246
- // z-fighting 방지: 바닥면(floor grid)보다 항상 앞에 렌더링
246
+ opacity, side: opacity < 1 ? THREE.DoubleSide : THREE.FrontSide }, (this._config.emissiveIntensity
247
+ ? { emissive: color, emissiveIntensity: this._config.emissiveIntensity }
248
+ : {})), color);
249
+ // z-fighting 방지: 바닥면(floor grid)보다 앞. bank body 는 공유 base 라 polygonOffset 은 모든 mcs
250
+ // body 공통 상수 — 반환 material 에 세팅(같은 값이라 멱등, 인스턴스별 발산 없음).
247
251
  material.polygonOffset = true;
248
252
  material.polygonOffsetFactor = -1;
249
253
  material.polygonOffsetUnit = -1;
@@ -275,6 +279,10 @@ class MCSRealObject3D extends RealObjectGroup {
275
279
  this._ownMesh.castShadow = !isPlane;
276
280
  this._ownMesh.receiveShadow = true;
277
281
  this.components3D.add(this._ownMesh);
282
+ // mesh 는 super.build() *이후* 생성되므로, 코어의 build-시점 faint 적용이 이 mesh 에 못 닿는다.
283
+ // mesh 생성 직후 명시 적용 — 코어 applyMaterialOpacity 가 content-alpha × faint 를 일괄 처리.
284
+ // view 모드에선 factor=1(=state.alpha 없음) → canonical(config.opacity) 그대로, 고객 화면 불변.
285
+ this.updateAlpha();
278
286
  }
279
287
  onchange(after, before) {
280
288
  if ('width' in after ||
@@ -284,7 +292,8 @@ class MCSRealObject3D extends RealObjectGroup {
284
292
  'strokeStyle' in after ||
285
293
  'id' in after ||
286
294
  'EMPTYTYPE' in after ||
287
- 'CARRIERSTATUS' in after) {
295
+ 'CARRIERSTATUS' in after ||
296
+ 'showText' in after) {
288
297
  // 연속 setState에 의한 다중 rebuild 방지: 한 프레임에 한 번만 실행
289
298
  if (!this._updatePending) {
290
299
  this._updatePending = true;
@@ -301,7 +310,7 @@ class MCSRealObject3D extends RealObjectGroup {
301
310
  * 상판 텍스처: 컴포넌트의 2D render()를 캔버스에 그려서 텍스처로 사용
302
311
  */
303
312
  buildTopTexture(w, h) {
304
- var _a;
313
+ var _a, _b;
305
314
  const scale = 4;
306
315
  const cw = this.nextPow2(w * scale);
307
316
  const ch = this.nextPow2(h * scale);
@@ -318,8 +327,9 @@ class MCSRealObject3D extends RealObjectGroup {
318
327
  // 바탕: 캐리어 상태 색상 (사용 영역만)
319
328
  ctx.fillStyle = statusColor;
320
329
  ctx.fillRect(0, 0, usedW, usedH);
321
- // 텍스트 (사용 영역 중앙)
322
- if (text) {
330
+ // 텍스트 (사용 영역 중앙) — showText(= 현재 홀더의 showCarrierText) off 면 미표시. 2D 와 동일.
331
+ const showText = !!((_b = this.component.state) === null || _b === void 0 ? void 0 : _b.showText);
332
+ if (text && showText) {
323
333
  const fontSize = Math.round(Math.min(usedW, usedH) * 0.7);
324
334
  ctx.font = `bold ${fontSize}px Arial`;
325
335
  ctx.textAlign = 'center';
@@ -369,24 +379,6 @@ class MCSRealObject3D extends RealObjectGroup {
369
379
  }
370
380
  // RealObjectGroup 기본 동작 비활성화
371
381
  updateDimension() { }
372
- updateAlpha() {
373
- const state = this.component.state;
374
- // alpha가 명시적으로 설정되어 있으면 그 값을 사용, 없으면 config 기본값 적용
375
- const effective = ('alpha' in state) ? state.alpha : this._config.opacity;
376
- this.object3d.traverse((o) => {
377
- if (!o.material)
378
- return;
379
- const materials = Array.isArray(o.material) ? o.material : [o.material];
380
- for (const m of materials) {
381
- if (!m.visible)
382
- continue; // openTop invisible face 무시
383
- if (m.map)
384
- continue; // 텍스처 material은 자체 transparent 유지
385
- m.opacity = effective;
386
- m.transparent = effective < 1;
387
- }
388
- });
389
- }
390
382
  }
391
383
  // ── 헬퍼 ────────────────────────────────────────────────
392
384
  /**
@@ -461,11 +453,9 @@ class OHTLineRealObject3D extends RealObjectGroup {
461
453
  const flangeW = beltW * 0.8; // 플랜지 폭
462
454
  const webH = beamH - flangeH * 2; // 웹 높이
463
455
  const webW = Math.max(flangeW * 0.25, 1); // 웹 두께
464
- const mat = new THREE.MeshStandardMaterial({
465
- color: OHT_LINE_RAIL_COLOR,
466
- metalness: 0.5,
467
- roughness: 0.4,
468
- });
456
+ // 공유 base(MaterialBank) — OHTLine rail 은 정적 색이라 전 인스턴스 dedup. faint 는 코어가
457
+ // 공유 base 의 변형을 스왑(원본 불변). dispose 는 bank-safe(update 에서 material.dispose 안 함).
458
+ const mat = MaterialBank.material('oht-line|rail', { metalness: 0.5, roughness: 0.4 }, OHT_LINE_RAIL_COLOR);
469
459
  const geoms = [];
470
460
  // 상부 플랜지
471
461
  const topFlange = isVertical
@@ -488,12 +478,14 @@ class OHTLineRealObject3D extends RealObjectGroup {
488
478
  this._mesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(geoms), mat);
489
479
  this._mesh.castShadow = true;
490
480
  this.object3d.add(this._mesh);
481
+ // mesh 가 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).
482
+ this.updateAlpha();
491
483
  }
492
484
  update() {
493
485
  if (this._mesh) {
494
486
  this.object3d.remove(this._mesh);
495
487
  this._mesh.geometry.dispose();
496
- this._mesh.material.dispose();
488
+ // material 은 MaterialBank 공유(앱 수명) — 여기서 dispose 하면 다른 인스턴스가 깨진다.
497
489
  this._mesh = undefined;
498
490
  }
499
491
  this.build();
@@ -508,8 +500,8 @@ class OHTLineRealObject3D extends RealObjectGroup {
508
500
  }
509
501
  super.onchange(after, before);
510
502
  }
503
+ // updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).
511
504
  updateDimension() { }
512
- updateAlpha() { }
513
505
  }
514
506
  registerRealObjectFactory('OHTLine', (component) => new OHTLineRealObject3D(component));
515
507
  // STOCKER: 벽면에 용량 게이지 바를 포함하는 커스텀 3D 오브젝트
@@ -523,7 +515,7 @@ class StockerRealObject3D extends MCSRealObject3D {
523
515
  this.buildGauge();
524
516
  }
525
517
  buildGauge() {
526
- var _a;
518
+ var _a, _b;
527
519
  const comp = this.component;
528
520
  const data = comp.data || {};
529
521
  const gaugeMode = ((_a = comp.root) === null || _a === void 0 ? void 0 : _a.stockerGaugeMode) || 'capacity';
@@ -559,9 +551,12 @@ class StockerRealObject3D extends MCSRealObject3D {
559
551
  ctx.strokeStyle = gauge.borderColor;
560
552
  ctx.lineWidth = 12;
561
553
  ctx.strokeRect(6, 6, canvasW - 12, canvasH - 12);
562
- // 세그먼트 채움 (왼→오)
554
+ // 세그먼트 채움 (왼→오) + carrier/abnormal 은 세그먼트별 숫자(%) 표기 (2D 와 동일)
563
555
  const innerW = canvasW - pad * 2;
564
556
  const innerH = canvasH - pad * 2;
557
+ // 컴포넌트 폰트 설정(fontFamily/bold/italic) 을 그대로, 크기만 텍스처 scale 배 (2D 와 상대 크기 일치).
558
+ const fontSize = (Number((_b = this.component.state) === null || _b === void 0 ? void 0 : _b.fontSize) || 15) * scale;
559
+ const font = this.gaugeFont(fontSize);
565
560
  let x = pad;
566
561
  for (const seg of gauge.segments) {
567
562
  const segW = Math.max(innerW * seg.ratio, seg.ratio > 0 ? 1 : 0);
@@ -569,14 +564,19 @@ class StockerRealObject3D extends MCSRealObject3D {
569
564
  continue;
570
565
  ctx.fillStyle = seg.color;
571
566
  ctx.fillRect(x, pad, segW, innerH);
567
+ if (gauge.perSegment) {
568
+ this.drawGaugeSegmentNumber(ctx, seg.ratio, x, pad, segW, innerH, font, fontSize);
569
+ }
572
570
  x += segW;
573
571
  }
574
- // 텍스트
575
- ctx.font = `bold ${10 * scale}px sans-serif`;
576
- ctx.textAlign = 'center';
577
- ctx.textBaseline = 'middle';
578
- ctx.fillStyle = '#333333';
579
- ctx.fillText(gauge.label, canvasW / 2, canvasH / 2);
572
+ // capacity: 전체 중앙에 usage% 한 개 (2D 와 동일). carrier/abnormal 은 위 세그먼트별 숫자 사용.
573
+ if (gauge.label) {
574
+ ctx.font = font;
575
+ ctx.textAlign = 'center';
576
+ ctx.textBaseline = 'middle';
577
+ ctx.fillStyle = '#333333';
578
+ ctx.fillText(gauge.label, canvasW / 2, canvasH / 2);
579
+ }
580
580
  // 텍스처 → Plane
581
581
  const texture = new THREE.CanvasTexture(canvas);
582
582
  texture.colorSpace = THREE.SRGBColorSpace;
@@ -601,31 +601,39 @@ class StockerRealObject3D extends MCSRealObject3D {
601
601
  var _a;
602
602
  switch (mode) {
603
603
  case 'carrier': {
604
- // 세그먼트 비율/색상은 2D 와 공유되는 단일 소스에서 계산 (필드/색상 drift 방지)
605
- const [f, e, ee] = carrierSegmentsFromData(data);
604
+ // 세그먼트 비율/색상은 2D 와 공유되는 단일 소스에서 계산 (필드/색상 drift 방지).
605
+ // 색상은 carrier 컴포넌트와 동일한 legend(테마)에서 취득.
606
+ const colors = carrierColorsFromLegend(resolveCarrierLegend(this.component));
607
+ const [f, e, ee] = carrierSegmentsFromData(data, colors);
606
608
  const total = f.count + e.count + ee.count;
607
- const segments = total > 0
608
- ? [f, e, ee].map(s => ({ ratio: s.ratio, color: s.color }))
609
- : [{ ratio: 1, color: '#dddddd' }];
610
- return {
611
- segments,
612
- borderColor: CARRIER_COLORS.full,
613
- label: total > 0 ? `F${f.count} E${e.count} EE${ee.count}` : '',
614
- };
609
+ // 접두사 없는 세그먼트별 숫자(%)로 표기 (capacity·2D 와 동일). label(중앙 한 개) 미사용.
610
+ return total > 0
611
+ ? {
612
+ segments: [f, e, ee].map(s => ({ ratio: s.ratio, color: s.color })),
613
+ borderColor: colors.full,
614
+ label: null,
615
+ perSegment: true,
616
+ }
617
+ : { segments: [{ ratio: 1, color: '#dddddd' }], borderColor: colors.full, label: null, perSegment: false };
615
618
  }
616
619
  case 'abnormal': {
617
620
  const [n, a] = abnormalSegmentsFromData(data);
618
621
  const total = n.count + a.count;
619
- const segments = total > 0
620
- ? [n, a].map(s => ({ ratio: s.ratio, color: s.color }))
621
- : [{ ratio: 1, color: '#dddddd' }];
622
- return {
623
- segments,
624
- borderColor: a.count > 0 ? ABNORMAL_COLORS.abnormal : ABNORMAL_COLORS.normal,
625
- label: total > 0 ? `N${n.count} A${a.count}` : '',
626
- };
622
+ return total > 0
623
+ ? {
624
+ segments: [n, a].map(s => ({ ratio: s.ratio, color: s.color })),
625
+ borderColor: a.count > 0 ? ABNORMAL_COLORS.abnormal : ABNORMAL_COLORS.normal,
626
+ label: null,
627
+ perSegment: true,
628
+ }
629
+ : {
630
+ segments: [{ ratio: 1, color: '#dddddd' }],
631
+ borderColor: ABNORMAL_COLORS.normal,
632
+ label: null,
633
+ perSegment: false,
634
+ };
627
635
  }
628
- default: { // capacity
636
+ default: { // capacity — 전체 중앙에 usage% 한 개
629
637
  const cur = Number(data.CURRENTCAPACITY) || 0;
630
638
  const max = Number(data.MAXCAPACITY) || 100;
631
639
  const fullup = data.FULLUP === 'T';
@@ -636,10 +644,39 @@ class StockerRealObject3D extends MCSRealObject3D {
636
644
  segments: [{ ratio: usage / 100, color }],
637
645
  borderColor: color,
638
646
  label: `${usage}%`,
647
+ perSegment: false,
639
648
  };
640
649
  }
641
650
  }
642
651
  }
652
+ /** 컴포넌트 폰트 설정(fontFamily/bold/italic)으로 CSS font 문자열 조합. size 는 텍스처 스케일 반영값. */
653
+ gaugeFont(sizePx) {
654
+ const st = this.component.state || {};
655
+ const parts = [];
656
+ if (st.bold)
657
+ parts.push('bold');
658
+ if (st.italic)
659
+ parts.push('italic');
660
+ parts.push(`${sizePx}px`);
661
+ parts.push(st.fontFamily || 'sans-serif');
662
+ return parts.join(' ');
663
+ }
664
+ /** 세그먼트 영역 중심에 숫자(%)만 표기. 좁으면 값% → 값 → 생략 (2D _drawSegmentLabel 과 동일 정책). */
665
+ drawGaugeSegmentNumber(ctx, ratio, x, y, w, h, font, fontSize) {
666
+ ctx.font = font;
667
+ ctx.textAlign = 'center';
668
+ ctx.textBaseline = 'middle';
669
+ ctx.fillStyle = '#333333';
670
+ const value = `${Math.round(ratio * 100)}`;
671
+ const cx = x + w / 2;
672
+ const cy = y + h / 2;
673
+ if (fontSize <= h && ctx.measureText(`${value}%`).width <= w) {
674
+ ctx.fillText(`${value}%`, cx, cy);
675
+ }
676
+ else if (fontSize <= h && ctx.measureText(value).width <= w) {
677
+ ctx.fillText(value, cx, cy);
678
+ }
679
+ }
643
680
  update() {
644
681
  this.clearGauge();
645
682
  super.update();