@fmsim/machine 2.0.1 → 3.0.0-beta.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 (95) hide show
  1. package/dist/agv.js +2 -3
  2. package/dist/agv.js.map +1 -1
  3. package/dist/carrier.js +53 -43
  4. package/dist/carrier.js.map +1 -1
  5. package/dist/crane.js +37 -2
  6. package/dist/crane.js.map +1 -1
  7. package/dist/editors/index.js +2 -0
  8. package/dist/editors/index.js.map +1 -1
  9. package/dist/editors/load-layout-host.js +96 -0
  10. package/dist/editors/load-layout-host.js.map +1 -0
  11. package/dist/editors/ox-load-layout-panel.js +124 -0
  12. package/dist/editors/ox-load-layout-panel.js.map +1 -0
  13. package/dist/factories/crane-3d.js +77 -18
  14. package/dist/factories/crane-3d.js.map +1 -1
  15. package/dist/factories/machine-3d.js +29 -52
  16. package/dist/factories/machine-3d.js.map +1 -1
  17. package/dist/features/capacity-gauge-mixin.js +2 -3
  18. package/dist/features/capacity-gauge-mixin.js.map +1 -1
  19. package/dist/features/load-layout-holder.js +77 -0
  20. package/dist/features/load-layout-holder.js.map +1 -0
  21. package/dist/features/mcs-status-default.js +30 -7
  22. package/dist/features/mcs-status-default.js.map +1 -1
  23. package/dist/features/mcs-status-mixin.js +3 -3
  24. package/dist/features/mcs-status-mixin.js.map +1 -1
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcs-carrier-holder.js +287 -82
  28. package/dist/mcs-carrier-holder.js.map +1 -1
  29. package/dist/oht.js +4 -0
  30. package/dist/oht.js.map +1 -1
  31. package/dist/port.js +6 -1
  32. package/dist/port.js.map +1 -1
  33. package/dist/scene/root-container-override.js +5 -11
  34. package/dist/scene/root-container-override.js.map +1 -1
  35. package/dist/shelf.js +4 -0
  36. package/dist/shelf.js.map +1 -1
  37. package/dist/shuttle.js +4 -0
  38. package/dist/shuttle.js.map +1 -1
  39. package/dist/stocker-carrier-bar.js +4 -4
  40. package/dist/stocker-carrier-bar.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/utils/carrier-type-code.js +10 -0
  43. package/dist/utils/carrier-type-code.js.map +1 -0
  44. package/dist/utils/crane-forks.js +5 -0
  45. package/dist/utils/crane-forks.js.map +1 -0
  46. package/dist/utils/layout-issue-text.js +16 -0
  47. package/dist/utils/layout-issue-text.js.map +1 -0
  48. package/dist/utils/layout-issue.js +16 -0
  49. package/dist/utils/layout-issue.js.map +1 -0
  50. package/dist/utils/load-address.js +217 -0
  51. package/dist/utils/load-address.js.map +1 -0
  52. package/dist/utils/stocker-gauge.js +3 -2
  53. package/dist/utils/stocker-gauge.js.map +1 -1
  54. package/jest.config.js +9 -1
  55. package/package.json +9 -6
  56. package/src/agv.ts +2 -3
  57. package/src/carrier.ts +50 -41
  58. package/src/crane.ts +39 -2
  59. package/src/editors/index.ts +2 -0
  60. package/src/editors/load-layout-host.ts +121 -0
  61. package/src/editors/ox-load-layout-panel.ts +129 -0
  62. package/src/factories/crane-3d.ts +74 -20
  63. package/src/factories/machine-3d.ts +26 -49
  64. package/src/features/capacity-gauge-mixin.ts +2 -3
  65. package/src/features/load-layout-holder.ts +83 -0
  66. package/src/features/mcs-status-default.ts +29 -10
  67. package/src/features/mcs-status-mixin.ts +3 -7
  68. package/src/index.ts +7 -0
  69. package/src/mcs-carrier-holder.ts +319 -78
  70. package/src/oht.ts +5 -0
  71. package/src/port.ts +7 -1
  72. package/src/scene/root-container-override.ts +4 -14
  73. package/src/shelf.ts +5 -0
  74. package/src/shuttle.ts +5 -0
  75. package/src/stocker-carrier-bar.ts +4 -4
  76. package/src/utils/carrier-type-code.ts +9 -0
  77. package/src/utils/crane-forks.ts +4 -0
  78. package/src/utils/layout-issue-text.ts +18 -0
  79. package/src/utils/layout-issue.ts +41 -0
  80. package/src/utils/load-address.ts +270 -0
  81. package/src/utils/stocker-gauge.ts +3 -3
  82. package/translations/en.json +42 -1
  83. package/translations/ja.json +42 -1
  84. package/translations/ko.json +59 -18
  85. package/translations/ms.json +42 -1
  86. package/translations/zh.json +42 -1
  87. package/dist/carrier-manager.js +0 -191
  88. package/dist/carrier-manager.js.map +0 -1
  89. package/dist/features/animation-default.js +0 -6
  90. package/dist/features/animation-default.js.map +0 -1
  91. package/dist/utils/get-value-on-ranges.js +0 -21
  92. package/dist/utils/get-value-on-ranges.js.map +0 -1
  93. package/src/carrier-manager.ts +0 -231
  94. package/src/features/animation-default.ts +0 -13
  95. package/src/utils/get-value-on-ranges.ts +0 -23
package/dist/agv.js CHANGED
@@ -2,8 +2,7 @@ import { __decorate } from "tslib";
2
2
  import { sceneComponent } from '@hatiolab/things-scene';
3
3
  import { themesColorRange } from '@fmsim/api';
4
4
  import MCSVehicle from './mcs-vehicle';
5
- import { getValueOnRanges } from './utils/get-value-on-ranges';
6
- import { BATTERY_RATE_LEGEND } from './features/mcs-status-default';
5
+ import { BATTERY_RATE_LEGEND, legendColor, legendRangeEntryOf } from './features/mcs-status-default';
7
6
  const NATURE = {
8
7
  mutable: false,
9
8
  resizable: true,
@@ -46,7 +45,7 @@ let AGV = class AGV extends MCSVehicle {
46
45
  }
47
46
  }
48
47
  getBatteryColor(rate) {
49
- return getValueOnRanges(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback());
48
+ return legendColor(legendRangeEntryOf(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback()));
50
49
  }
51
50
  render(ctx) {
52
51
  var { left, top, width, height } = this.bounds;
package/dist/agv.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'\nimport { themesColorRange } from '@fmsim/api'\nimport MCSVehicle from './mcs-vehicle'\nimport { getValueOnRanges } from './utils/get-value-on-ranges'\nimport { BATTERY_RATE_LEGEND } from './features/mcs-status-default'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n ...MCSVehicle.properties,\n {\n type: 'select',\n label: 'battery-rate-legend-name',\n name: 'batteryRateLegendName',\n property: {\n options: themesColorRange\n }\n }\n ]\n}\n\nconst GAP = 2\n\n@sceneComponent('AGV')\nexport default class AGV extends MCSVehicle {\n static get nature() {\n return NATURE\n }\n\n get auxColor() {\n return 'black'\n }\n\n /** AGV 의 parentObject 는 AGVLine. */\n get parentObjectTypes(): string[] {\n return ['AGVLine']\n }\n\n getLegendFallback() {\n return (this.root as any).agvLegendTheme || super.getLegendFallback()\n }\n\n getBatteryLegendFallback() {\n return (this.root as any).batteryRateLegendTheme || BATTERY_RATE_LEGEND\n }\n\n getBatteryLegendTheme() {\n const { batteryRateLegendName } = this.state\n\n if (batteryRateLegendName) {\n return (this.root as any)?.style?.[batteryRateLegendName]\n }\n }\n\n getBatteryColor(rate: number) {\n return getValueOnRanges(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback())\n }\n\n render(ctx: CanvasRenderingContext2D) {\n var { left, top, width, height } = this.bounds\n\n ctx.translate(left, top)\n ctx.beginPath()\n\n // 작은 크기에서도 보이도록 최소 선 두께 설정\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 100\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n ctx.strokeStyle = this.auxColor\n ctx.fillStyle = this.statusColor!\n ctx.lineWidth = lineWidth\n ctx.lineCap = 'round'\n ctx.setLineDash([])\n\n // 기본 사각형 그리기\n ctx.rect(1, 0, width - 2, height)\n ctx.rect(GAP + 1, GAP, width - (GAP + 1) * 2, height - GAP * 2)\n\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n\n // 바퀴 그리기\n ctx.strokeStyle = 'black'\n\n ctx.moveTo(0, (height * 1) / 3)\n ctx.lineTo(0, (height * 2) / 3)\n ctx.moveTo(width, (height * 1) / 3)\n ctx.lineTo(width, (height * 2) / 3)\n\n ctx.stroke()\n\n ctx.translate(-left, -top)\n }\n\n // 사선 패턴을 그리는 함수\n drawHatchPattern(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {\n // 6개의 빗금을 그리기 위한 간격 계산\n const spacing = (width + height) / 6\n\n // 선 두께 계산 (크기에 비례)\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 20 // 130에서 50으로 변경하여 더 두껍게\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n ctx.beginPath()\n ctx.strokeStyle = 'black'\n ctx.lineWidth = lineWidth\n\n // 배터리 박스 내부로 클리핑 설정\n ctx.save()\n ctx.rect(x, y, width, height)\n ctx.clip()\n\n // 6개의 사선 패턴 그리기\n for (let i = 0; i < 6; i++) {\n const startX = x + i * spacing\n ctx.moveTo(startX, y)\n ctx.lineTo(startX - height, y + height)\n }\n\n ctx.stroke()\n // 클리핑 해제\n ctx.restore()\n }\n\n postrender(ctx: CanvasRenderingContext2D): void {\n super.postrender(ctx)\n\n const BATTERYRATE = this.data?.BATTERYRATE\n\n if (typeof BATTERYRATE === 'number') {\n const { left, top, width, height } = this.bounds\n ctx.translate(left, top)\n\n // 배터리 크기 및 위치 설정 (최소 크기 보장)\n const minBatteryHeight = 2\n const minBatteryWidth = 4\n const preferredHeight = height / 5\n const preferredWidth = (width * 3) / 4\n\n const batteryHeight = Math.max(minBatteryHeight, preferredHeight)\n const batteryWidth = Math.max(minBatteryWidth, preferredWidth)\n const batteryX = (width - batteryWidth) / 2\n const batteryY = (height - batteryHeight) * 1.1\n\n // 배터리 테두리 그리기 (크기에 비례한 선 두께)\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 100\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n // 배터리 배경을 흰색으로 채우기\n ctx.fillStyle = 'white'\n ctx.fillRect(batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 전체 영역에 사선 패턴 그리기\n this.drawHatchPattern(ctx, batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 잔량 표시\n const fillWidth = (batteryWidth * BATTERYRATE) / 100\n\n // 배터리 잔량에 따른 색상 설정 - 테마 적용\n ctx.fillStyle = this.getBatteryColor(BATTERYRATE)\n\n // 배터리 잔량 영역 채우기\n ctx.fillRect(batteryX, batteryY, fillWidth, batteryHeight)\n\n // 배터리 테두리 다시 그리기\n ctx.beginPath()\n ctx.strokeStyle = 'black'\n ctx.lineWidth = lineWidth\n ctx.strokeRect(batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 잔량 텍스트 표시\n const fontSize = batteryHeight * 0.8\n\n // 실제 렌더링될 텍스트 크기 계산\n const transform = ctx.getTransform()\n const scaleX = Math.sqrt(transform.m11 * transform.m11 + transform.m21 * transform.m21)\n const actualFontSize = (fontSize * (scaleX || 1)) / window.devicePixelRatio\n\n // 실제 텍스트 크기가 10px 이상일 때만 텍스트 표시\n if (actualFontSize >= 10) {\n ctx.font = `${fontSize}px Roboto, Arial, sans-serif`\n ctx.fillStyle = 'black'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n\n // 배터리 잔량 텍스트 (정수 + % 포함)\n const { BATTERYRATETEXT } = this.data || {}\n const text = BATTERYRATETEXT || `${Math.round(BATTERYRATE)}%`\n\n // 텍스트 크기 측정\n const textMetrics = ctx.measureText(text)\n const textWidth = textMetrics.width * 1.1 // 여백을 위해 10% 추가\n const textHeight = fontSize * 1.1 // 여백을 위해 10% 추가\n\n // 배터리 중심점 계산\n const batteryCenterX = batteryX + batteryWidth / 2\n const batteryCenterY = batteryY + batteryHeight / 2\n\n // 현재 변환 상태 저장\n ctx.save()\n\n // 배터리 중심점으로 이동\n ctx.translate(batteryCenterX, batteryCenterY)\n\n // 순수한 회전 각도 계산 (라디안)\n const normalizedM11 = transform.m11 / scaleX\n const normalizedM21 = transform.m21 / scaleX\n let angle = Math.atan2(normalizedM21, normalizedM11)\n\n // 회전 보정 (같은 방향으로 회전)\n ctx.rotate(angle)\n\n // 텍스트 배경 위치 계산 (중심점 기준)\n const textBgX = -textWidth / 2\n const textBgY = -textHeight / 2\n\n // 텍스트 배경 그리기\n ctx.fillStyle = 'white'\n ctx.fillRect(textBgX, textBgY, textWidth, textHeight)\n\n // 텍스트 그리기\n ctx.fillStyle = 'black'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, 0, textHeight * 0.1) // 텍스트를 약간 아래로 이동\n\n // 변환 상태 복원\n ctx.restore()\n }\n\n ctx.translate(-left, -top)\n }\n }\n}\n"]}
1
+ {"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["import { ComponentNature, sceneComponent } from '@hatiolab/things-scene'\nimport { themesColorRange } from '@fmsim/api'\nimport MCSVehicle from './mcs-vehicle'\nimport { BATTERY_RATE_LEGEND, legendColor, legendRangeEntryOf } from './features/mcs-status-default'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n ...MCSVehicle.properties,\n {\n type: 'select',\n label: 'battery-rate-legend-name',\n name: 'batteryRateLegendName',\n property: {\n options: themesColorRange\n }\n }\n ]\n}\n\nconst GAP = 2\n\n@sceneComponent('AGV')\nexport default class AGV extends MCSVehicle {\n static get nature() {\n return NATURE\n }\n\n get auxColor() {\n return 'black'\n }\n\n /** AGV 의 parentObject 는 AGVLine. */\n get parentObjectTypes(): string[] {\n return ['AGVLine']\n }\n\n getLegendFallback() {\n return (this.root as any).agvLegendTheme || super.getLegendFallback()\n }\n\n getBatteryLegendFallback() {\n return (this.root as any).batteryRateLegendTheme || BATTERY_RATE_LEGEND\n }\n\n getBatteryLegendTheme() {\n const { batteryRateLegendName } = this.state\n\n if (batteryRateLegendName) {\n return (this.root as any)?.style?.[batteryRateLegendName]\n }\n }\n\n getBatteryColor(rate: number) {\n return legendColor(legendRangeEntryOf(rate, this.getBatteryLegendTheme() || this.getBatteryLegendFallback()))\n }\n\n render(ctx: CanvasRenderingContext2D) {\n var { left, top, width, height } = this.bounds\n\n ctx.translate(left, top)\n ctx.beginPath()\n\n // 작은 크기에서도 보이도록 최소 선 두께 설정\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 100\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n ctx.strokeStyle = this.auxColor\n ctx.fillStyle = this.statusColor!\n ctx.lineWidth = lineWidth\n ctx.lineCap = 'round'\n ctx.setLineDash([])\n\n // 기본 사각형 그리기\n ctx.rect(1, 0, width - 2, height)\n ctx.rect(GAP + 1, GAP, width - (GAP + 1) * 2, height - GAP * 2)\n\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n\n // 바퀴 그리기\n ctx.strokeStyle = 'black'\n\n ctx.moveTo(0, (height * 1) / 3)\n ctx.lineTo(0, (height * 2) / 3)\n ctx.moveTo(width, (height * 1) / 3)\n ctx.lineTo(width, (height * 2) / 3)\n\n ctx.stroke()\n\n ctx.translate(-left, -top)\n }\n\n // 사선 패턴을 그리는 함수\n drawHatchPattern(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number) {\n // 6개의 빗금을 그리기 위한 간격 계산\n const spacing = (width + height) / 6\n\n // 선 두께 계산 (크기에 비례)\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 20 // 130에서 50으로 변경하여 더 두껍게\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n ctx.beginPath()\n ctx.strokeStyle = 'black'\n ctx.lineWidth = lineWidth\n\n // 배터리 박스 내부로 클리핑 설정\n ctx.save()\n ctx.rect(x, y, width, height)\n ctx.clip()\n\n // 6개의 사선 패턴 그리기\n for (let i = 0; i < 6; i++) {\n const startX = x + i * spacing\n ctx.moveTo(startX, y)\n ctx.lineTo(startX - height, y + height)\n }\n\n ctx.stroke()\n // 클리핑 해제\n ctx.restore()\n }\n\n postrender(ctx: CanvasRenderingContext2D): void {\n super.postrender(ctx)\n\n const BATTERYRATE = this.data?.BATTERYRATE\n\n if (typeof BATTERYRATE === 'number') {\n const { left, top, width, height } = this.bounds\n ctx.translate(left, top)\n\n // 배터리 크기 및 위치 설정 (최소 크기 보장)\n const minBatteryHeight = 2\n const minBatteryWidth = 4\n const preferredHeight = height / 5\n const preferredWidth = (width * 3) / 4\n\n const batteryHeight = Math.max(minBatteryHeight, preferredHeight)\n const batteryWidth = Math.max(minBatteryWidth, preferredWidth)\n const batteryX = (width - batteryWidth) / 2\n const batteryY = (height - batteryHeight) * 1.1\n\n // 배터리 테두리 그리기 (크기에 비례한 선 두께)\n const minLineWidth = 0.5\n const scaleFactor = Math.min(width, height) / 100\n const lineWidth = Math.max(minLineWidth, scaleFactor)\n\n // 배터리 배경을 흰색으로 채우기\n ctx.fillStyle = 'white'\n ctx.fillRect(batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 전체 영역에 사선 패턴 그리기\n this.drawHatchPattern(ctx, batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 잔량 표시\n const fillWidth = (batteryWidth * BATTERYRATE) / 100\n\n // 배터리 잔량에 따른 색상 설정 - 테마 적용\n ctx.fillStyle = this.getBatteryColor(BATTERYRATE)\n\n // 배터리 잔량 영역 채우기\n ctx.fillRect(batteryX, batteryY, fillWidth, batteryHeight)\n\n // 배터리 테두리 다시 그리기\n ctx.beginPath()\n ctx.strokeStyle = 'black'\n ctx.lineWidth = lineWidth\n ctx.strokeRect(batteryX, batteryY, batteryWidth, batteryHeight)\n\n // 배터리 잔량 텍스트 표시\n const fontSize = batteryHeight * 0.8\n\n // 실제 렌더링될 텍스트 크기 계산\n const transform = ctx.getTransform()\n const scaleX = Math.sqrt(transform.m11 * transform.m11 + transform.m21 * transform.m21)\n const actualFontSize = (fontSize * (scaleX || 1)) / window.devicePixelRatio\n\n // 실제 텍스트 크기가 10px 이상일 때만 텍스트 표시\n if (actualFontSize >= 10) {\n ctx.font = `${fontSize}px Roboto, Arial, sans-serif`\n ctx.fillStyle = 'black'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n\n // 배터리 잔량 텍스트 (정수 + % 포함)\n const { BATTERYRATETEXT } = this.data || {}\n const text = BATTERYRATETEXT || `${Math.round(BATTERYRATE)}%`\n\n // 텍스트 크기 측정\n const textMetrics = ctx.measureText(text)\n const textWidth = textMetrics.width * 1.1 // 여백을 위해 10% 추가\n const textHeight = fontSize * 1.1 // 여백을 위해 10% 추가\n\n // 배터리 중심점 계산\n const batteryCenterX = batteryX + batteryWidth / 2\n const batteryCenterY = batteryY + batteryHeight / 2\n\n // 현재 변환 상태 저장\n ctx.save()\n\n // 배터리 중심점으로 이동\n ctx.translate(batteryCenterX, batteryCenterY)\n\n // 순수한 회전 각도 계산 (라디안)\n const normalizedM11 = transform.m11 / scaleX\n const normalizedM21 = transform.m21 / scaleX\n let angle = Math.atan2(normalizedM21, normalizedM11)\n\n // 회전 보정 (같은 방향으로 회전)\n ctx.rotate(angle)\n\n // 텍스트 배경 위치 계산 (중심점 기준)\n const textBgX = -textWidth / 2\n const textBgY = -textHeight / 2\n\n // 텍스트 배경 그리기\n ctx.fillStyle = 'white'\n ctx.fillRect(textBgX, textBgY, textWidth, textHeight)\n\n // 텍스트 그리기\n ctx.fillStyle = 'black'\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n ctx.fillText(text, 0, textHeight * 0.1) // 텍스트를 약간 아래로 이동\n\n // 변환 상태 복원\n ctx.restore()\n }\n\n ctx.translate(-left, -top)\n }\n }\n}\n"]}
package/dist/carrier.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { __decorate } from "tslib";
2
2
  import JSON5 from 'json5';
3
3
  import { getPopupData } from '@fmsim/api';
4
- import { Shape, sceneComponent } from '@hatiolab/things-scene';
5
- import { ANIMATION_DEFAULT } from './features/animation-default.js';
4
+ import { Item, TRANSFER_SLOT_KEY, figureLegendSourcesChanged, registerFigureCapability, sceneComponent } from '@hatiolab/things-scene';
5
+ import { ANIMATION_DEFAULT, CARRIER_FIGURE } from '@fmsim/figure-capabilities';
6
6
  import { ParentObjectMixin } from './features/parent-object-mixin.js';
7
7
  import { resolveCarrierLegend } from './features/mcs-status-default.js';
8
8
  import { MCSStatusMixin } from './features/mcs-status-mixin.js';
9
+ import { normalizeTypeCode } from './utils/carrier-type-code';
9
10
  const NATURE = {
10
11
  mutable: false,
11
12
  resizable: false,
@@ -24,10 +25,23 @@ const NATURE = {
24
25
  * branch in `render()`. `render()` (the original code).
25
26
  */
26
27
  const STATUS_TEXT_COUNTER_ROTATE = true;
27
- let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
28
+ /**
29
+ * 캐리어는 아이템이다 (공통 아이템 계약, ADR-0167 6). 컴포넌트 타입은 Carrier 그대로이고, 아이템 타입은 CARRIERTYPE 이다.
30
+ * 3D 는 그 타입이 이름한 피규어(carrier-types)가 그리고, 그리는 피규어가 없으면 fmsim 의 캐리어 상자다. 2D 는 그대로.
31
+ */
32
+ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Item)) {
28
33
  static get nature() {
29
34
  return NATURE;
30
35
  }
36
+ /** 아이템 타입 코드: CARRIERTYPE 을 라이브 데이터 비교 규칙대로 (fmsim 문서 01, 6.8 — trim·대문자) */
37
+ get itemTypeCode() {
38
+ var _a, _b;
39
+ return normalizeTypeCode((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.CARRIERTYPE) !== null && _b !== void 0 ? _b : this.state.CARRIERTYPE);
40
+ }
41
+ /** 3D 가 아이템의 것(피규어)인지: 타입의 피규어가 있을 때만. 없으면 'Carrier' 로 등록된 fmsim 상자가 지어진다 */
42
+ get drawnAsItem() {
43
+ return !!this.look;
44
+ }
31
45
  get decorators() {
32
46
  return ['decotag'];
33
47
  }
@@ -45,31 +59,23 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
45
59
  ];
46
60
  }
47
61
  set path(path) { }
48
- added(parent) {
49
- var _a, _b, _c;
50
- super.added(parent);
51
- // 새 부모에 맞춰 크기 및 상태 갱신 — width/height 만. depth 는 *절대로
52
- // 건드리지 않는다*. machine-3d.ts 의 CARRIER_CONFIG + MCSRealObject3D.
53
- // effectiveDepth 가 depthRatio(0.5) × min(w,h) 로 자동 결정한다.
54
- // 여기서 state.depth 를 명시 set 하면 그 resolution chain 의 step 1 에
55
- // 걸려 본래 두께가 덮어써진다 (= 사용자 호소 "두께가 줄어들었다" 의 원인).
56
- if (parent) {
57
- const { width, height } = parent.state || {};
58
- if (width && height) {
59
- this.set({ width, height });
60
- }
61
- }
62
+ /**
63
+ * 홀더가 받았다 (공통 아이템 계약 3.5 의 handover 끝). 2D 는 홀더 평면을 덮으므로 width/height 를 새 홀더에 맞춘다.
64
+ * depth 는 건드리지 않는다 — 3D 크기는 홀더가 보이는 치수(itemSeatOf().dims)로 정한다.
65
+ */
66
+ held(holder) {
67
+ var _a, _b;
68
+ const { width, height } = (holder === null || holder === void 0 ? void 0 : holder.state) || {};
69
+ if (width && height)
70
+ this.set({ width, height });
62
71
  this.syncStateFromData();
63
72
  /* 부모가 정해진 지금 다시 해석한다 — 생성 시(data 선할당) 판정에는 parent 가 없어
64
- * 홀더의 carrierAnimationName 이 반영되지 않았다. reparent 로 홀더가 바뀐 경우도 여기서
73
+ * 홀더의 carrierAnimationName 이 반영되지 않았다. 홀더가 바뀐 경우도 여기서
65
74
  * 새 홀더 테마로 갱신된다. (같은 조합이면 내부 가드로 no-op) */
66
75
  this._applyStatusAnimation();
67
- // 3D 오브젝트 최초 생성 (동기적 — reparent()에서 _realObject 필요)
68
- if (((_a = this.root) === null || _a === void 0 ? void 0 : _a.threed) && !this._realObject) {
69
- ;
70
- (_c = (_b = this.root.model_layer) === null || _b === void 0 ? void 0 : _b._threeCapability) === null || _c === void 0 ? void 0 : _c.addObject(this);
71
- }
72
- // reparent 애니메이션은 things-scene의 reparent()가 처리
76
+ /* 범례는 홀더가 물려준다: 새 홀더의 범례로 이 Carrier 의 피규어가 범례를 다시 고른다 (ADR-0166 결정 12) */
77
+ figureLegendSourcesChanged(this);
78
+ (_b = (_a = this._realObject) === null || _a === void 0 ? void 0 : _a.update) === null || _b === void 0 ? void 0 : _b.call(_a);
73
79
  }
74
80
  /**
75
81
  * data로부터 carrier 상태를 동기화 (생성 시, 부모 변경 시 공통 사용)
@@ -82,7 +88,9 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
82
88
  this.setState('CARRIERSTATUS', CARRIERSTATUS);
83
89
  }
84
90
  onchangeData(after, before) {
91
+ var _a, _b, _c, _d, _e, _f, _g, _h;
85
92
  const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {};
93
+ const typeChanged = ((_b = (_a = after.data) === null || _a === void 0 ? void 0 : _a.CARRIERTYPE) !== null && _b !== void 0 ? _b : '') !== ((_d = (_c = before.data) === null || _c === void 0 ? void 0 : _c.CARRIERTYPE) !== null && _d !== void 0 ? _d : '');
86
94
  this.setState('id', CARRIERNAME);
87
95
  this.setState('EMPTYTYPE', EMPTYTYPE);
88
96
  this.setState('CARRIERTYPE', CARRIERTYPE);
@@ -91,6 +99,14 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
91
99
  * (테마|상태) 키 비교로 하므로 여기서 status 변경 여부를 따지지 않는다 —
92
100
  * 상태는 그대로여도 홀더가 바뀌어(=테마가 바뀌어) 재적용이 필요할 수 있다. */
93
101
  this._applyStatusAnimation();
102
+ /* 같은 Carrier 의 타입이 바뀌었다 (계약 Q10): 그 타입의 피규어가 모양이 되고, 보이는 치수로 3D 를 다시 만들고, 그 자리를 다시 앉힌다 */
103
+ if (typeChanged)
104
+ this.refreshLook();
105
+ if (typeChanged && this.parent) {
106
+ ;
107
+ (_f = (_e = this._realObject) === null || _e === void 0 ? void 0 : _e.update) === null || _f === void 0 ? void 0 : _f.call(_e);
108
+ (_h = (_g = this.parent)._reseatSlot) === null || _h === void 0 ? void 0 : _h.call(_g, this[TRANSFER_SLOT_KEY]);
109
+ }
94
110
  }
95
111
  /**
96
112
  * 현재 (해석된 애니메이션 테마 | CARRIERSTATUS) 조합에 맞는 애니메이션/데코를 적용한다.
@@ -100,8 +116,8 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
100
116
  *
101
117
  * 호출 시점이 둘인 이유:
102
118
  * 1) onchangeData — 상태(또는 테마) 변화 반영
103
- * 2) added() — **부모가 생긴 직후**. CarrierManager 는 `carrier.data = item` 을
104
- * addComponent 보다 먼저 하므로, 생성 시 첫 판정에는 parent 가 없어 홀더의
119
+ * 2) held() — **홀더가 받은 직후**. 홀더는 data 를 넣어 Carrier 를 만든 뒤
120
+ * 받으므로, 생성 시 첫 판정에는 parent 가 없어 홀더의
105
121
  * carrierAnimationName 이 무시된다(루트 폴백만 보임). 부모가 붙은 뒤 한 번 더
106
122
  * 해석해야 홀더 테마가 반영된다.
107
123
  */
@@ -152,7 +168,7 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
152
168
  * 적용할 애니메이션 테마 이름 — 소유 홀더(부모) 설정이 우선, 없으면 보드(rootModel) 폴백.
153
169
  *
154
170
  * 부모가 아직 없는 시점(생성 직후 data 선할당)에는 홀더 설정을 알 수 없어 루트 폴백만
155
- * 보인다. 그래서 added() 에서 한 번 더 해석한다 — _applyStatusAnimation 참조.
171
+ * 보인다. 그래서 held() 에서 한 번 더 해석한다 — _applyStatusAnimation 참조.
156
172
  */
157
173
  get resolvedCarrierAnimationName() {
158
174
  var _a, _b, _c, _d;
@@ -246,20 +262,17 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
246
262
  return y > m * x + b;
247
263
  }
248
264
  render(ctx) {
265
+ var _a;
249
266
  const { EMPTYTYPE, id: CARRIERNAME, showText } = this.state;
250
267
  if (!CARRIERNAME) {
251
268
  return;
252
269
  }
253
- // 2D 에서는 stack 의 *맨 위 (= parent.components 의 마지막)* 1개만 그린다.
254
- // ADDITIONS 의 carrier 들도 인스턴스로는 살아있어 조회/reparent 가 가능하지만
255
- // 2D 시각에는 노출하지 않는다 — 정책: "2D 는 현행처럼" (대표 1개만).
256
- // 판단 기준은 *위치* — carrier 간 그룹/종속 정보는 두지 않는다.
257
- const parent = this.parent;
258
- if (parent && parent.components && parent.components.length > 1) {
259
- const siblings = parent.components;
260
- if (siblings[siblings.length - 1] !== this) {
261
- return;
262
- }
270
+ // 2D 에서는 홀더 데이터 stack 의 *맨 위 (MULTICARRIERS 다음 CARRIERNAME 순서의 마지막)* 1개만 그린다.
271
+ // 다른 carrier 들도 인스턴스로는 살아있어 조회·이동이 가능하지만
272
+ // 2D 시각에는 노출하지 않는다 — 정책: "2D 는 현행처럼" (1개만). 판단 기준은 위치 — carrier 간 그룹/종속 정보는 두지 않는다.
273
+ const top = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.topCarrier;
274
+ if (top && top !== this) {
275
+ return;
263
276
  }
264
277
  const { width, height } = this.bounds;
265
278
  const path = this.path;
@@ -306,12 +319,6 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
306
319
  }
307
320
  }
308
321
  }
309
- dispose() {
310
- var _a, _b;
311
- ;
312
- (_b = (_a = this.root) === null || _a === void 0 ? void 0 : _a.carrierManager) === null || _b === void 0 ? void 0 : _b.onCarrierDisposed(this.state.id);
313
- super.dispose();
314
- }
315
322
  async detailInfo() {
316
323
  const { type, id } = this.state;
317
324
  if (!id) {
@@ -324,4 +331,7 @@ Carrier = __decorate([
324
331
  sceneComponent('Carrier')
325
332
  ], Carrier);
326
333
  export default Carrier;
334
+ /* fmsim.Carrier (ADR-0167 12): what it gives a figure is its tables, handed by its descriptor -- no mixin of its own */
335
+ registerFigureCapability(CARRIER_FIGURE, base => class extends base {
336
+ });
327
337
  //# sourceMappingURL=carrier.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"carrier.js","sourceRoot":"","sources":["../src/carrier.ts"],"names":[],"mappings":"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","sourcesContent":["import JSON5 from 'json5'\nimport { getPopupData } from '@fmsim/api'\n\nimport { BOUNDS, Component, ComponentNature, Properties, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport { ANIMATION_DEFAULT, AnimationPreset, AnimationConfig } from './features/animation-default.js'\nimport { ParentObjectMixin } from './features/parent-object-mixin.js'\nimport { Legend, resolveCarrierLegend } from './features/mcs-status-default.js'\nimport { MCSStatusMixin } from './features/mcs-status-mixin.js'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: false,\n rotatable: false\n}\n\n\n/**\n * Feature flag — keep status text (F / E / X) screen-axis-aligned even\n * when the carrier (or its parent holder, or an in-flight reparent\n * tween) is rotated. Counter-rotates ONLY the text; the polygon path\n * still rotates with the carrier as before.\n *\n * ROLLBACK procedure (if customer decides to revert):\n * 1. Flip this flag to `false` — text rotates with carrier again.\n * 2. Or delete this constant and the — fully strip the feature: keep\n * `if (STATUS_TEXT_COUNTER_ROTATE)` only the `else` branch in\n * branch in `render()`. `render()` (the original code).\n */\nconst STATUS_TEXT_COUNTER_ROTATE = true\n\n@sceneComponent('Carrier')\nexport default class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {\n static get nature() {\n return NATURE\n }\n\n get decorators() {\n return ['decotag']\n }\n\n get path() {\n if (!this.parent) {\n return []\n }\n\n const { left, top, width, height } = this.calculateShrunkRectangle(this.parent.bounds)\n\n return [\n { x: left, y: top },\n { x: left + width / 2, y: top },\n { x: left + width, y: top + height / 2 },\n { x: left + width, y: top + height },\n { x: left, y: top + height }\n ]\n }\n\n set path(path) {}\n\n /** 마지막으로 적용한 (애니메이션 테마 | CARRIERSTATUS) 조합. 같은 조합 재적용을 막는다. */\n private _appliedAnimationKey?: string\n\n added(parent: any) {\n super.added(parent)\n\n // 새 부모에 맞춰 크기 및 상태 갱신 — width/height 만. depth 는 *절대로\n // 건드리지 않는다*. machine-3d.ts 의 CARRIER_CONFIG + MCSRealObject3D.\n // effectiveDepth 가 depthRatio(0.5) × min(w,h) 로 자동 결정한다.\n // 여기서 state.depth 를 명시 set 하면 그 resolution chain 의 step 1 에\n // 걸려 본래 두께가 덮어써진다 (= 사용자 호소 \"두께가 줄어들었다\" 의 원인).\n if (parent) {\n const { width, height } = parent.state || {}\n if (width && height) {\n this.set({ width, height })\n }\n }\n\n this.syncStateFromData()\n\n /* 부모가 정해진 지금 다시 해석한다 — 생성 시(data 선할당) 판정에는 parent 가 없어\n * 홀더의 carrierAnimationName 이 반영되지 않았다. reparent 로 홀더가 바뀐 경우도 여기서\n * 새 홀더 테마로 갱신된다. (같은 조합이면 내부 가드로 no-op) */\n this._applyStatusAnimation()\n\n // 3D 오브젝트 최초 생성 (동기적 — reparent()에서 _realObject 필요)\n if ((this.root as any)?.threed && !this._realObject) {\n ;(this.root as any).model_layer?._threeCapability?.addObject(this)\n }\n\n // reparent 애니메이션은 things-scene의 reparent()가 처리\n }\n\n /**\n * data로부터 carrier 상태를 동기화 (생성 시, 부모 변경 시 공통 사용)\n */\n private syncStateFromData(): void {\n const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}\n\n this.setState('id', CARRIERNAME)\n this.setState('EMPTYTYPE', EMPTYTYPE)\n this.setState('CARRIERTYPE', CARRIERTYPE)\n this.setState('CARRIERSTATUS', CARRIERSTATUS)\n }\n\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}\n\n this.setState('id', CARRIERNAME)\n this.setState('EMPTYTYPE', EMPTYTYPE)\n this.setState('CARRIERTYPE', CARRIERTYPE)\n this.setState('CARRIERSTATUS', CARRIERSTATUS)\n\n /* 상태에 매핑된 애니메이션/데코 적용. 조건 판정은 _applyStatusAnimation 내부의\n * (테마|상태) 키 비교로 하므로 여기서 status 변경 여부를 따지지 않는다 —\n * 상태는 그대로여도 홀더가 바뀌어(=테마가 바뀌어) 재적용이 필요할 수 있다. */\n this._applyStatusAnimation()\n }\n\n /**\n * 현재 (해석된 애니메이션 테마 | CARRIERSTATUS) 조합에 맞는 애니메이션/데코를 적용한다.\n *\n * 같은 조합이면 아무것도 하지 않는다 — data 는 매 update 마다 재할당되므로, 가드가 없으면\n * 매번 애니메이션이 재시작되어 끊긴다.\n *\n * 호출 시점이 둘인 이유:\n * 1) onchangeData — 상태(또는 테마) 변화 반영\n * 2) added() — **부모가 생긴 직후**. CarrierManager 는 `carrier.data = item` 을\n * addComponent 보다 먼저 하므로, 생성 시 첫 판정에는 parent 가 없어 홀더의\n * carrierAnimationName 이 무시된다(루트 폴백만 보임). 부모가 붙은 뒤 한 번 더\n * 해석해야 홀더 테마가 반영된다.\n */\n private _applyStatusAnimation(): void {\n const status = this.getState('CARRIERSTATUS')\n const key = `${this.resolvedCarrierAnimationName}|${status ?? ''}`\n if (key === this._appliedAnimationKey) return\n this._appliedAnimationKey = key\n\n /* 이전 적용분 해제 — 이전 테마/상태를 되짚지 않고 끈다(끄는 동작은 멱등).\n * 테마가 바뀐 경우 이전 config 로는 무엇이 켜져 있었는지 정확히 알 수 없다. */\n this.resetAnimation()\n this.setState('animation', { oncreate: null })\n this.trigger('deco-off')\n\n const animationConfig = this.getAnimationConfig(status)\n if (!animationConfig) return\n\n const { animation, decorator, border, arrow } = animationConfig\n\n if (animation) {\n this.setState('animation', { oncreate: animation })\n this.started = true\n }\n\n if (decorator) {\n this.trigger('deco-icon-off')\n this.trigger('deco-icon', decorator)\n }\n\n if (border) {\n this.trigger('deco-border-off')\n this.trigger('deco-border', border)\n }\n\n if (arrow) {\n this.trigger('deco-arrow-off')\n this.trigger('deco-arrow', arrow)\n }\n }\n\n get status() {\n return this.getState('EMPTYTYPE')\n }\n\n get legend(): Legend {\n // carrier·스토커 게이지(2D/3D) 공유 단일 소스.\n return resolveCarrierLegend(this)\n }\n\n getAnimationConfig(carrierStatus): AnimationConfig | null {\n const config = this.animationPreset[carrierStatus || 'default']\n return (config as AnimationConfig) || null\n }\n\n /**\n * 적용할 애니메이션 테마 이름 — 소유 홀더(부모) 설정이 우선, 없으면 보드(rootModel) 폴백.\n *\n * 부모가 아직 없는 시점(생성 직후 data 선할당)에는 홀더 설정을 알 수 없어 루트 폴백만\n * 보인다. 그래서 added() 에서 한 번 더 해석한다 — _applyStatusAnimation 참조.\n */\n get resolvedCarrierAnimationName(): string {\n return (\n this.parent?.state.carrierAnimationName || (this.root as any)?.rootModel?.state?.carrierAnimationName || ''\n )\n }\n\n get animationPreset(): AnimationPreset {\n const carrierAnimationName = this.resolvedCarrierAnimationName\n\n if (carrierAnimationName) {\n // 캐시에서 먼저 확인\n if (!(this.root as any)._animationConfigCache) {\n (this.root as any)._animationConfigCache = new Map<string, AnimationPreset>()\n }\n\n const cached = (this.root as any)._animationConfigCache.get(carrierAnimationName)\n if (cached) {\n return cached\n }\n\n // 캐시에 없으면 파싱하고 저장\n let theme = (this.root as any)?.style?.[carrierAnimationName]\n if (theme) {\n // theme이 string이면 먼저 파싱\n if (typeof theme === 'string') {\n try { theme = JSON5.parse(theme) } catch { return ANIMATION_DEFAULT }\n }\n if (typeof theme !== 'object' || theme === null) return ANIMATION_DEFAULT\n\n // 문자열 형태의 설정들을 미리 파싱\n const parsedTheme = {}\n for (const [status, config] of Object.entries(theme)) {\n if (typeof config === 'string' && config.trim()) {\n try {\n parsedTheme[status] = JSON5.parse(config)\n } catch (e) {\n console.error('JSON5 parse error:', e, 'input:', JSON.stringify(config), 'status:', status, 'theme:', carrierAnimationName)\n parsedTheme[status] = config\n }\n } else {\n parsedTheme[status] = config\n }\n }\n \n ;(this.root as any)._animationConfigCache.set(carrierAnimationName, parsedTheme)\n return parsedTheme\n }\n \n return ANIMATION_DEFAULT\n } else {\n return ANIMATION_DEFAULT\n }\n }\n\n get hasTextProperty() {\n return this.getState('showText') || false\n }\n\n calculateShrunkRectangle(originalRect: BOUNDS): BOUNDS {\n const shrunkRect: BOUNDS = { ...originalRect }\n\n shrunkRect.left = originalRect.width * 0.1\n shrunkRect.width *= 0.8\n\n shrunkRect.top = originalRect.height * 0.1\n shrunkRect.height *= 0.8\n\n return shrunkRect\n }\n\n contains(x, y) {\n const { id: CARRIERNAME } = this.state || {}\n\n if (!CARRIERNAME) {\n return false\n }\n\n const rect = this.bounds\n x -= rect.left\n y -= rect.top\n\n if (x < 0 || y < 0 || x > rect.width || y > rect.height) {\n return false\n }\n\n // 윗변 중앙 좌표\n const topCenterX = rect.width / 2\n const topCenterY = 0\n\n // 오른쪽 변 중앙 좌표\n const rightCenterX = rect.width\n const rightCenterY = rect.height / 2\n\n // 기울기 m = (y2 - y1) / (x2 - x1)\n const m = (rightCenterY - topCenterY) / (rightCenterX - topCenterX)\n\n // y 절편 b = y - mx\n const b = topCenterY - m * topCenterX\n\n // 점(px, py)이 선 아래(좌하부)에 있는지 확인\n return y > m * x + b\n }\n\n render(ctx: CanvasRenderingContext2D) {\n const { EMPTYTYPE, id: CARRIERNAME, showText } = this.state\n\n if (!CARRIERNAME) {\n return\n }\n\n // 2D 에서는 stack 의 *맨 위 (= parent.components 의 마지막)* 1개만 그린다.\n // ADDITIONS 의 carrier 들도 인스턴스로는 살아있어 조회/reparent 가 가능하지만\n // 2D 시각에는 노출하지 않는다 — 정책: \"2D 는 현행처럼\" (대표 1개만).\n // 판단 기준은 *위치* — carrier 간 그룹/종속 정보는 두지 않는다.\n const parent = this.parent\n if (parent && parent.components && parent.components.length > 1) {\n const siblings = parent.components\n if (siblings[siblings.length - 1] !== this) {\n return\n }\n }\n\n const { width, height } = this.bounds\n const path = this.path\n const round = Math.round(Math.min(width, height) * 0.1)\n const lineWidth = round > 5 ? 1 : 0.5\n\n ctx.beginPath()\n ctx.moveTo(path[0].x, path[0].y)\n\n path.slice(1).forEach(({ x, y }) => {\n ctx.lineTo(x, y)\n })\n\n ctx.closePath()\n\n ctx.lineWidth = lineWidth\n ctx.fillStyle = this.statusColor || 'transparent'\n ctx.strokeStyle = this.auxColor || 'transparent'\n\n ctx.fill()\n ctx.stroke()\n\n if (showText) {\n const text = EMPTYTYPE == 'FULL' ? 'F' : EMPTYTYPE == 'EMPTY' ? 'E' : EMPTYTYPE == 'EMPTYEMPTY' ? 'X' : ''\n\n if (text) {\n const { x: cx, y: cy } = this.center\n\n ctx.fillStyle = 'black'\n ctx.font = `normal ${Math.round(round * 8)}px Arial`\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n\n if (STATUS_TEXT_COUNTER_ROTATE) {\n // Counter-rotate so the status label stays screen-axis-aligned\n // even when the carrier (or its parent holder, or an in-flight\n // reparent transition) is rotated. Reading the current cumulative\n // rotation from the canvas transform handles all sources of\n // rotation uniformly (state.rotation, parent transforms,\n // animation tweens) — no need to enumerate them.\n const m = ctx.getTransform()\n const cumulRotation = Math.atan2(m.b, m.a)\n ctx.save()\n ctx.translate(cx, cy)\n ctx.rotate(-cumulRotation)\n ctx.fillText(text, -width / 8, height / 8)\n ctx.restore()\n } else {\n // Original — text rotates with the carrier.\n ctx.fillText(text, cx - width / 8, cy + height / 8)\n }\n }\n }\n }\n\n dispose(): void {\n ;(this.root as any)?.carrierManager?.onCarrierDisposed(this.state.id)\n\n super.dispose()\n }\n\n async detailInfo() {\n const { type, id } = this.state\n\n if (!id) {\n return\n }\n\n return await getPopupData(type, id)\n }\n}\n"]}
1
+ {"version":3,"file":"carrier.js","sourceRoot":"","sources":["../src/carrier.ts"],"names":[],"mappings":"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","sourcesContent":["import JSON5 from 'json5'\nimport { getPopupData } from '@fmsim/api'\n\nimport { BOUNDS, ComponentNature, Item, TRANSFER_SLOT_KEY, figureLegendSourcesChanged, registerFigureCapability, sceneComponent } from '@hatiolab/things-scene'\nimport { ANIMATION_DEFAULT, CARRIER_FIGURE, type AnimationConfig, type AnimationPreset } from '@fmsim/figure-capabilities'\nimport { ParentObjectMixin } from './features/parent-object-mixin.js'\nimport { Legend, resolveCarrierLegend } from './features/mcs-status-default.js'\nimport { MCSStatusMixin } from './features/mcs-status-mixin.js'\nimport { normalizeTypeCode } from './utils/carrier-type-code'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: false,\n rotatable: false\n}\n\n\n/**\n * Feature flag — keep status text (F / E / X) screen-axis-aligned even\n * when the carrier (or its parent holder, or an in-flight reparent\n * tween) is rotated. Counter-rotates ONLY the text; the polygon path\n * still rotates with the carrier as before.\n *\n * ROLLBACK procedure (if customer decides to revert):\n * 1. Flip this flag to `false` — text rotates with carrier again.\n * 2. Or delete this constant and the — fully strip the feature: keep\n * `if (STATUS_TEXT_COUNTER_ROTATE)` only the `else` branch in\n * branch in `render()`. `render()` (the original code).\n */\nconst STATUS_TEXT_COUNTER_ROTATE = true\n\n/**\n * 캐리어는 아이템이다 (공통 아이템 계약, ADR-0167 6). 컴포넌트 타입은 Carrier 그대로이고, 아이템 타입은 CARRIERTYPE 이다.\n * 3D 는 그 타입이 이름한 피규어(carrier-types)가 그리고, 그리는 피규어가 없으면 fmsim 의 캐리어 상자다. 2D 는 그대로.\n */\n@sceneComponent('Carrier')\nexport default class Carrier extends MCSStatusMixin(ParentObjectMixin(Item)) {\n static get nature() {\n return NATURE\n }\n\n /** 아이템 타입 코드: CARRIERTYPE 을 라이브 데이터 비교 규칙대로 (fmsim 문서 01, 6.8 — trim·대문자) */\n get itemTypeCode(): string {\n return normalizeTypeCode(this.data?.CARRIERTYPE ?? this.state.CARRIERTYPE)\n }\n\n /** 3D 가 아이템의 것(피규어)인지: 타입의 피규어가 있을 때만. 없으면 'Carrier' 로 등록된 fmsim 상자가 지어진다 */\n get drawnAsItem(): boolean {\n return !!this.look\n }\n\n get decorators() {\n return ['decotag']\n }\n\n get path() {\n if (!this.parent) {\n return []\n }\n\n const { left, top, width, height } = this.calculateShrunkRectangle(this.parent.bounds)\n\n return [\n { x: left, y: top },\n { x: left + width / 2, y: top },\n { x: left + width, y: top + height / 2 },\n { x: left + width, y: top + height },\n { x: left, y: top + height }\n ]\n }\n\n set path(path) {}\n\n /** 마지막으로 적용한 (애니메이션 테마 | CARRIERSTATUS) 조합. 같은 조합 재적용을 막는다. */\n private _appliedAnimationKey?: string\n\n /**\n * 홀더가 받았다 (공통 아이템 계약 3.5 의 handover 끝). 2D 는 홀더 평면을 덮으므로 width/height 를 새 홀더에 맞춘다.\n * depth 는 건드리지 않는다 — 3D 크기는 홀더가 보이는 치수(itemSeatOf().dims)로 정한다.\n */\n held(holder: any) {\n const { width, height } = holder?.state || {}\n if (width && height) this.set({ width, height })\n\n this.syncStateFromData()\n\n /* 부모가 정해진 지금 다시 해석한다 — 생성 시(data 선할당) 판정에는 parent 가 없어\n * 홀더의 carrierAnimationName 이 반영되지 않았다. 홀더가 바뀐 경우도 여기서\n * 새 홀더 테마로 갱신된다. (같은 조합이면 내부 가드로 no-op) */\n this._applyStatusAnimation()\n\n /* 범례는 홀더가 물려준다: 새 홀더의 범례로 이 Carrier 의 피규어가 범례를 다시 고른다 (ADR-0166 결정 12) */\n figureLegendSourcesChanged(this)\n\n // 새 홀더가 보이는 치수로 3D 를 다시 만든다 (자리는 홀더가 앉힌다)\n ;(this._realObject as any)?.update?.()\n }\n\n /**\n * data로부터 carrier 상태를 동기화 (생성 시, 부모 변경 시 공통 사용)\n */\n private syncStateFromData(): void {\n const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}\n\n this.setState('id', CARRIERNAME)\n this.setState('EMPTYTYPE', EMPTYTYPE)\n this.setState('CARRIERTYPE', CARRIERTYPE)\n this.setState('CARRIERSTATUS', CARRIERSTATUS)\n }\n\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}\n const typeChanged = (after.data?.CARRIERTYPE ?? '') !== (before.data?.CARRIERTYPE ?? '')\n\n this.setState('id', CARRIERNAME)\n this.setState('EMPTYTYPE', EMPTYTYPE)\n this.setState('CARRIERTYPE', CARRIERTYPE)\n this.setState('CARRIERSTATUS', CARRIERSTATUS)\n\n /* 상태에 매핑된 애니메이션/데코 적용. 조건 판정은 _applyStatusAnimation 내부의\n * (테마|상태) 키 비교로 하므로 여기서 status 변경 여부를 따지지 않는다 —\n * 상태는 그대로여도 홀더가 바뀌어(=테마가 바뀌어) 재적용이 필요할 수 있다. */\n this._applyStatusAnimation()\n\n /* 같은 Carrier 의 타입이 바뀌었다 (계약 Q10): 그 타입의 피규어가 모양이 되고, 보이는 치수로 3D 를 다시 만들고, 그 자리를 다시 앉힌다 */\n if (typeChanged) this.refreshLook()\n if (typeChanged && this.parent) {\n ;(this._realObject as any)?.update?.()\n ;(this.parent as any)._reseatSlot?.((this as any)[TRANSFER_SLOT_KEY])\n }\n }\n\n /**\n * 현재 (해석된 애니메이션 테마 | CARRIERSTATUS) 조합에 맞는 애니메이션/데코를 적용한다.\n *\n * 같은 조합이면 아무것도 하지 않는다 — data 는 매 update 마다 재할당되므로, 가드가 없으면\n * 매번 애니메이션이 재시작되어 끊긴다.\n *\n * 호출 시점이 둘인 이유:\n * 1) onchangeData — 상태(또는 테마) 변화 반영\n * 2) held() — **홀더가 받은 직후**. 홀더는 data 를 넣어 Carrier 를 만든 뒤\n * 받으므로, 생성 시 첫 판정에는 parent 가 없어 홀더의\n * carrierAnimationName 이 무시된다(루트 폴백만 보임). 부모가 붙은 뒤 한 번 더\n * 해석해야 홀더 테마가 반영된다.\n */\n private _applyStatusAnimation(): void {\n const status = this.getState('CARRIERSTATUS')\n const key = `${this.resolvedCarrierAnimationName}|${status ?? ''}`\n if (key === this._appliedAnimationKey) return\n this._appliedAnimationKey = key\n\n /* 이전 적용분 해제 — 이전 테마/상태를 되짚지 않고 끈다(끄는 동작은 멱등).\n * 테마가 바뀐 경우 이전 config 로는 무엇이 켜져 있었는지 정확히 알 수 없다. */\n this.resetAnimation()\n this.setState('animation', { oncreate: null })\n this.trigger('deco-off')\n\n const animationConfig = this.getAnimationConfig(status)\n if (!animationConfig) return\n\n const { animation, decorator, border, arrow } = animationConfig\n\n if (animation) {\n this.setState('animation', { oncreate: animation })\n this.started = true\n }\n\n if (decorator) {\n this.trigger('deco-icon-off')\n this.trigger('deco-icon', decorator)\n }\n\n if (border) {\n this.trigger('deco-border-off')\n this.trigger('deco-border', border)\n }\n\n if (arrow) {\n this.trigger('deco-arrow-off')\n this.trigger('deco-arrow', arrow)\n }\n }\n\n get status() {\n return this.getState('EMPTYTYPE')\n }\n\n get legend(): Legend {\n // carrier·스토커 게이지(2D/3D) 공유 단일 소스.\n return resolveCarrierLegend(this)\n }\n\n getAnimationConfig(carrierStatus): AnimationConfig | null {\n const config = this.animationPreset[carrierStatus || 'default']\n return (config as AnimationConfig) || null\n }\n\n /**\n * 적용할 애니메이션 테마 이름 — 소유 홀더(부모) 설정이 우선, 없으면 보드(rootModel) 폴백.\n *\n * 부모가 아직 없는 시점(생성 직후 data 선할당)에는 홀더 설정을 알 수 없어 루트 폴백만\n * 보인다. 그래서 held() 에서 한 번 더 해석한다 — _applyStatusAnimation 참조.\n */\n get resolvedCarrierAnimationName(): string {\n return (\n this.parent?.state.carrierAnimationName || (this.root as any)?.rootModel?.state?.carrierAnimationName || ''\n )\n }\n\n get animationPreset(): AnimationPreset {\n const carrierAnimationName = this.resolvedCarrierAnimationName\n\n if (carrierAnimationName) {\n // 캐시에서 먼저 확인\n if (!(this.root as any)._animationConfigCache) {\n (this.root as any)._animationConfigCache = new Map<string, AnimationPreset>()\n }\n\n const cached = (this.root as any)._animationConfigCache.get(carrierAnimationName)\n if (cached) {\n return cached\n }\n\n // 캐시에 없으면 파싱하고 저장\n let theme = (this.root as any)?.style?.[carrierAnimationName]\n if (theme) {\n // theme이 string이면 먼저 파싱\n if (typeof theme === 'string') {\n try { theme = JSON5.parse(theme) } catch { return ANIMATION_DEFAULT }\n }\n if (typeof theme !== 'object' || theme === null) return ANIMATION_DEFAULT\n\n // 문자열 형태의 설정들을 미리 파싱\n const parsedTheme = {}\n for (const [status, config] of Object.entries(theme)) {\n if (typeof config === 'string' && config.trim()) {\n try {\n parsedTheme[status] = JSON5.parse(config)\n } catch (e) {\n console.error('JSON5 parse error:', e, 'input:', JSON.stringify(config), 'status:', status, 'theme:', carrierAnimationName)\n parsedTheme[status] = config\n }\n } else {\n parsedTheme[status] = config\n }\n }\n \n ;(this.root as any)._animationConfigCache.set(carrierAnimationName, parsedTheme)\n return parsedTheme\n }\n \n return ANIMATION_DEFAULT\n } else {\n return ANIMATION_DEFAULT\n }\n }\n\n get hasTextProperty() {\n return this.getState('showText') || false\n }\n\n calculateShrunkRectangle(originalRect: BOUNDS): BOUNDS {\n const shrunkRect: BOUNDS = { ...originalRect }\n\n shrunkRect.left = originalRect.width * 0.1\n shrunkRect.width *= 0.8\n\n shrunkRect.top = originalRect.height * 0.1\n shrunkRect.height *= 0.8\n\n return shrunkRect\n }\n\n contains(x, y) {\n const { id: CARRIERNAME } = this.state || {}\n\n if (!CARRIERNAME) {\n return false\n }\n\n const rect = this.bounds\n x -= rect.left\n y -= rect.top\n\n if (x < 0 || y < 0 || x > rect.width || y > rect.height) {\n return false\n }\n\n // 윗변 중앙 좌표\n const topCenterX = rect.width / 2\n const topCenterY = 0\n\n // 오른쪽 변 중앙 좌표\n const rightCenterX = rect.width\n const rightCenterY = rect.height / 2\n\n // 기울기 m = (y2 - y1) / (x2 - x1)\n const m = (rightCenterY - topCenterY) / (rightCenterX - topCenterX)\n\n // y 절편 b = y - mx\n const b = topCenterY - m * topCenterX\n\n // 점(px, py)이 선 아래(좌하부)에 있는지 확인\n return y > m * x + b\n }\n\n render(ctx: CanvasRenderingContext2D) {\n const { EMPTYTYPE, id: CARRIERNAME, showText } = this.state\n\n if (!CARRIERNAME) {\n return\n }\n\n // 2D 에서는 홀더 데이터 stack 의 *맨 위 (MULTICARRIERS 다음 CARRIERNAME 순서의 마지막)* 1개만 그린다.\n // 다른 carrier 들도 인스턴스로는 살아있어 조회·이동이 가능하지만\n // 2D 시각에는 노출하지 않는다 — 정책: \"2D 는 현행처럼\" (1개만). 판단 기준은 위치 — carrier 간 그룹/종속 정보는 두지 않는다.\n const top = (this.parent as any)?.topCarrier\n if (top && top !== this) {\n return\n }\n\n const { width, height } = this.bounds\n const path = this.path\n const round = Math.round(Math.min(width, height) * 0.1)\n const lineWidth = round > 5 ? 1 : 0.5\n\n ctx.beginPath()\n ctx.moveTo(path[0].x, path[0].y)\n\n path.slice(1).forEach(({ x, y }) => {\n ctx.lineTo(x, y)\n })\n\n ctx.closePath()\n\n ctx.lineWidth = lineWidth\n ctx.fillStyle = this.statusColor || 'transparent'\n ctx.strokeStyle = this.auxColor || 'transparent'\n\n ctx.fill()\n ctx.stroke()\n\n if (showText) {\n const text = EMPTYTYPE == 'FULL' ? 'F' : EMPTYTYPE == 'EMPTY' ? 'E' : EMPTYTYPE == 'EMPTYEMPTY' ? 'X' : ''\n\n if (text) {\n const { x: cx, y: cy } = this.center\n\n ctx.fillStyle = 'black'\n ctx.font = `normal ${Math.round(round * 8)}px Arial`\n ctx.textAlign = 'center'\n ctx.textBaseline = 'middle'\n\n if (STATUS_TEXT_COUNTER_ROTATE) {\n // Counter-rotate so the status label stays screen-axis-aligned\n // even when the carrier (or its parent holder, or an in-flight\n // reparent transition) is rotated. Reading the current cumulative\n // rotation from the canvas transform handles all sources of\n // rotation uniformly (state.rotation, parent transforms,\n // animation tweens) — no need to enumerate them.\n const m = ctx.getTransform()\n const cumulRotation = Math.atan2(m.b, m.a)\n ctx.save()\n ctx.translate(cx, cy)\n ctx.rotate(-cumulRotation)\n ctx.fillText(text, -width / 8, height / 8)\n ctx.restore()\n } else {\n // Original — text rotates with the carrier.\n ctx.fillText(text, cx - width / 8, cy + height / 8)\n }\n }\n }\n }\n\n\n async detailInfo() {\n const { type, id } = this.state\n\n if (!id) {\n return\n }\n\n return await getPopupData(type, id)\n }\n}\n\n/* fmsim.Carrier (ADR-0167 12): what it gives a figure is its tables, handed by its descriptor -- no mixin of its own */\nregisterFigureCapability(CARRIER_FIGURE, base => class extends base {})\n"]}
package/dist/crane.js CHANGED
@@ -1,12 +1,23 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { sceneComponent, frameClock } from '@hatiolab/things-scene';
3
3
  import MCSVehicle from './mcs-vehicle';
4
+ import { LoadLayoutHolder } from './features/load-layout-holder';
5
+ import { HOLDER_ANCHOR, forkAnchorId } from '@hatiolab/figure-model';
6
+ import { craneForkCount } from './utils/crane-forks';
4
7
  import { axisRatio, mapCraneLocalPosition, normalizeCranePosition } from './utils/crane-position';
5
8
  const NATURE = {
6
9
  mutable: false,
7
10
  resizable: true,
8
11
  rotatable: true,
9
- properties: [...MCSVehicle.properties]
12
+ properties: [
13
+ ...MCSVehicle.properties,
14
+ {
15
+ type: 'select',
16
+ label: 'fork-count',
17
+ name: 'forkCount',
18
+ property: { options: ['1', '2'] }
19
+ }
20
+ ]
10
21
  };
11
22
  const GAP = 2;
12
23
  /** 좌표 보간 기본 duration (ms). data.DURATION_MS 가 없으면 이 값 사용. */
@@ -17,10 +28,34 @@ function lerp(a, b, t) {
17
28
  function easeInOut(t) {
18
29
  return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
19
30
  }
20
- let Crane = class Crane extends MCSVehicle {
31
+ let Crane = class Crane extends LoadLayoutHolder(MCSVehicle) {
21
32
  static get nature() {
22
33
  return NATURE;
23
34
  }
35
+ /**
36
+ * 크레인이 주는 Anchor (설계 02번 3.7.2절): 홀더 원점과 포크마다 하나(fork_1, fork_2). 포크가 하나뿐인 장비에
37
+ * fork_2 에 선 Tier 가 있으면 레이아웃 전체가 잘못된 값이다: Carrier 를 자리 없이 한 무더기로 쌓고 패널에 오류를 보인다
38
+ * (ADR-0051 4.1절 결정 8).
39
+ */
40
+ slotAnchors() {
41
+ const forks = craneForkCount(this.state.forkCount);
42
+ return [HOLDER_ANCHOR, ...Array.from({ length: forks }, (_, i) => forkAnchorId(i + 1))];
43
+ }
44
+ /** 포크 Anchor 는 크레인 3D 의 그 포크 판 윗면 노드다. 포크가 오르내리고 뻗으면 그 위의 Carrier 가 함께 간다 */
45
+ anchorObject3D(anchorId) {
46
+ var _a, _b, _c;
47
+ return (_c = (_b = (_a = this._realObject) === null || _a === void 0 ? void 0 : _a.forkAnchor) === null || _b === void 0 ? void 0 : _b.call(_a, anchorId)) !== null && _c !== void 0 ? _c : super.anchorObject3D(anchorId);
48
+ }
49
+ /** 포크 수가 바뀌면 주는 Anchor 가 바뀌므로, 데이터가 다시 오지 않아도 지금 데이터로 자리를 다시 배정한다 */
50
+ onchange(after, before) {
51
+ super.onchange(after, before);
52
+ if ('forkCount' in after && craneForkCount(after.forkCount) !== craneForkCount(before === null || before === void 0 ? void 0 : before.forkCount)) {
53
+ ;
54
+ this._carrierRecordsTaken = undefined;
55
+ this.syncCarriers(this.data || {});
56
+ this._carriersSeatedAgain();
57
+ }
58
+ }
24
59
  get auxColor() {
25
60
  return 'black';
26
61
  }
package/dist/crane.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"crane.js","sourceRoot":"","sources":["../src/crane.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ComponentNature, sceneComponent, frameClock } from '@hatiolab/things-scene'\nimport MCSVehicle from './mcs-vehicle'\nimport { LEGEND_CRANE } from './features/mcs-status-default'\nimport {\n axisRatio,\n mapCraneLocalPosition,\n normalizeCranePosition,\n type CranePositionData\n} from './utils/crane-position'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [...MCSVehicle.properties]\n}\n\nconst GAP = 2\n\n/** 좌표 보간 기본 duration (ms). data.DURATION_MS 가 없으면 이 값 사용. */\nconst DEFAULT_DURATION_MS = 1000\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t\n}\n\nfunction easeInOut(t: number): number {\n return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2\n}\n\ninterface CranePositionAnimator {\n fromLeft: number\n fromTop: number\n fromForkZ: number\n toLeft: number\n toTop: number\n toForkZ: number\n startTime: number\n duration: number\n unsubscribe: () => void\n}\n\n@sceneComponent('Crane')\nexport default class Crane extends MCSVehicle {\n static get nature() {\n return NATURE\n }\n\n get auxColor() {\n return 'black'\n }\n\n /** Crane 의 parentObject 는 STOCKER. */\n get parentObjectTypes(): string[] {\n return ['STOCKER']\n }\n\n getLegendFallback() {\n return (this.root as any).craneLegendTheme || super.getLegendFallback()\n }\n\n private _posAnimator?: CranePositionAnimator\n\n /**\n * data.cranePosition = { current: {x,y,z}, limit: {x|y|z:{min,max}} } 분기.\n * current 를 limit 범위 안의 0~1 비율로 환산한 뒤, 부모 (보통 Stocker) 기준으로\n * 실 좌표 (state.left/top) 와 forkZ 로 변환해 lerp 시작. x→host 긴쪽, y→짧은쪽,\n * z→포크 높이. 보간 중 새 값이 들어오면 *현재 보간 위치* 를 from 으로 잡고 새\n * target 으로 새 lerp 시작.\n * NODEID 경로는 super (MCSVehicle.onchangeData) 가 처리 — cranePosition 만 있는\n * 데이터에선 NODEID 가 없어 자연스럽게 noop.\n */\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n const afterData = after?.data\n // 데이터 키는 모두 대문자 전제. CRANEPOSITION 의 중첩 키(CURRENT/LIMIT/X/Y/Z/MIN/MAX)는\n // normalizeCranePosition 이 내부 표준형(소문자)으로 변환. DURATION_MS 는 사용하지 않음.\n const afterCP = normalizeCranePosition(afterData?.CRANEPOSITION)\n if (afterCP) {\n const beforeCP = normalizeCranePosition(before?.data?.CRANEPOSITION)\n if (this._cranePositionChanged(afterCP, beforeCP)) {\n this._startPositionAnimation(afterCP)\n }\n }\n super.onchangeData(after, before)\n }\n\n /** current 또는 limit 중 하나라도 바뀌면 재계산 필요 (limit 변화도 실 위치를 바꿈). */\n private _cranePositionChanged(a: CranePositionData, b?: CranePositionData): boolean {\n if (!b) return true\n return (\n JSON.stringify(a?.current ?? null) !== JSON.stringify(b?.current ?? null) ||\n JSON.stringify(a?.limit ?? null) !== JSON.stringify(b?.limit ?? null)\n )\n }\n\n dispose() {\n if (this._posAnimator) {\n this._posAnimator.unsubscribe()\n this._posAnimator = undefined\n }\n super.dispose()\n }\n\n private _startPositionAnimation(cranePosition: CranePositionData, durationMs?: number): void {\n const target = this._ratioToReal(cranePosition)\n if (!target) return // 부모(host) 없거나 크기 0 이면 변환 skip\n\n const now = frameClock.simTime\n const duration = Math.max(1, Number(durationMs) || DEFAULT_DURATION_MS)\n\n // 현재 위치 — 보간 중이면 그 시점 보간값을 from 으로\n let fromLeft = Number(this.state.left) || 0\n let fromTop = Number(this.state.top) || 0\n let fromForkZ = Number((this.state as any).forkZ) || 0\n\n if (this._posAnimator) {\n const a = this._posAnimator\n const t = Math.min(1, Math.max(0, (now - a.startTime) / a.duration))\n const e = easeInOut(t)\n fromLeft = lerp(a.fromLeft, a.toLeft, e)\n fromTop = lerp(a.fromTop, a.toTop, e)\n fromForkZ = lerp(a.fromForkZ, a.toForkZ, e)\n a.unsubscribe()\n this._posAnimator = undefined\n }\n\n const unsubscribe = frameClock.subscribe(\n (_dt: number, simTime: number) => this._tickPositionAnimation(simTime),\n { description: 'crane-position', component: this }\n )\n\n this._posAnimator = {\n fromLeft,\n fromTop,\n fromForkZ,\n toLeft: target.left,\n toTop: target.top,\n toForkZ: target.forkZ,\n startTime: now,\n duration,\n unsubscribe\n }\n // 즉시 첫 frame 반영 (t=0) — set 한 번 호출. paused 환경에서도 from→target\n // 진입 시점이 명확.\n this._tickPositionAnimation(now)\n }\n\n private _tickPositionAnimation(simTime: number): void {\n const a = this._posAnimator\n if (!a) return\n const t = Math.min(1, Math.max(0, (simTime - a.startTime) / a.duration))\n const e = easeInOut(t)\n this.set({\n left: lerp(a.fromLeft, a.toLeft, e),\n top: lerp(a.fromTop, a.toTop, e),\n forkZ: lerp(a.fromForkZ, a.toForkZ, e)\n })\n if (t >= 1) {\n a.unsubscribe()\n this._posAnimator = undefined\n }\n }\n\n /**\n * cranePosition (current + limit) → state.left/top + forkZ.\n *\n * 부모 오브젝트 (보통은 Stocker 이지만 다른 컨테이너일 수도) 의 width/height\n * 만 사용해 좌표 매핑 — 타입 무관 일반화. 축 배정과 원점은 host 의 내부 좌표계 설정을\n * 따른다 (coordAxisX / coordInvertX / coordInvertY, 미설정 시 아래 폴백):\n * - current.x → X축 (기본: host 의 긴쪽, 자동 판별 max(width, height))\n * - current.y → Y축 (기본: 짧은쪽)\n * - current.z → forkZ (state 에 0~100 으로 저장, 3D 측에서 mast 비율로 매핑).\n * Z 는 바닥 기준이 물리적으로 고정이라 반전 설정을 두지 않는다.\n *\n * 각 축 비율 = (current - limit.min) / (limit.max - limit.min) ∈ [0,1].\n *\n * **스토커 안쪽 오프셋**: 크레인의 폭/두께를 감안해, 크레인 *전체* 가 host 안에\n * 머무르도록 top-left 좌표를 [0, hostLen - craneLen] 범위로 매핑한다 (= 양끝에서\n * craneLen/2 만큼 inset 된 중심선 위를 움직임). 크레인이 host 보다 크면 span 0\n * 으로 떨어져 host 모서리에 정렬.\n *\n * 참조는 ParentObjectMixin 이 제공하는 `parentObject` 사용 —\n * state.parentObjectId 명시 시 그것, 미명시 시 this.parent 자동 fallback.\n */\n private _ratioToReal(\n cp: CranePositionData\n ): { left: number; top: number; forkZ: number } | undefined {\n const host: any = (this as any).parentObject\n if (!host) return undefined\n\n const hw = Number(host.state?.width) || 0\n const hh = Number(host.state?.height) || 0\n const cw = Number(this.state.width) || 0\n const ch = Number(this.state.height) || 0\n if (!hw || !hh) return undefined\n\n const rx = axisRatio(cp.current?.x, cp.limit?.x)\n const ry = axisRatio(cp.current?.y, cp.limit?.y)\n const rz = axisRatio(cp.current?.z, cp.limit?.z)\n\n // 축 배정/원점은 host(보통 STOCKER) 의 내부 좌표계 설정을 따른다. 타입을 묻지 않고\n // state 만 읽어 어떤 컨테이너가 host 여도 동일하게 동작한다 — 미설정이면 종전\n // 폴백(긴쪽=X, 원점 left/top).\n const { left, top } = mapCraneLocalPosition(hw, hh, cw, ch, rx, ry, {\n axisX: host.state?.coordAxisX,\n invertX: !!host.state?.coordInvertX,\n invertY: !!host.state?.coordInvertY\n })\n\n return {\n left,\n top,\n forkZ: rz * 100 // forkZ 0~100, 3D 측 (crane-3d) 에서 mast 비율로 매핑\n }\n }\n\n render(ctx: CanvasRenderingContext2D) {\n var { left, top, width, height } = this.bounds\n const { x: cx, y: cy } = this.center\n const rx = width / 2\n const ry = height / 2\n\n const round = Math.min(rx, ry) * 0.5\n\n ctx.translate(left, top)\n ctx.beginPath()\n\n ctx.strokeStyle = this.auxColor\n ctx.fillStyle = this.statusColor!\n ctx.lineWidth = 1\n ctx.lineCap = 'round'\n ctx.setLineDash([])\n\n const radius = Math.round(Math.min(width, height) * 0.1)\n ctx.roundRect(1, 0, width - 2, height, radius)\n ctx.roundRect(GAP + 1, GAP, width - (GAP + 1) * 2, height - GAP * 2, radius - 1)\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n ctx.arc(cx - left, cy - top, round, 0, Math.PI * 2)\n ctx.stroke()\n\n ctx.beginPath()\n ctx.roundRect(width / 2 - (round * 2) / 4 - round / 8, height / 2 - (round * 6) / 5, round / 4, (round * 6) / 5, 1)\n ctx.roundRect(width / 2 + (round * 2) / 4 - round / 8, height / 2 - (round * 6) / 5, round / 4, (round * 6) / 5, 1)\n\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n\n // draw crane bar metapore\n ctx.strokeStyle = 'black'\n ctx.lineWidth = 3\n ctx.lineCap = 'butt'\n\n ctx.moveTo(GAP * 3, height / 2)\n ctx.lineTo(width - GAP * 3, height / 2)\n\n ctx.stroke()\n\n ctx.translate(-left, -top)\n }\n}\n"]}
1
+ {"version":3,"file":"crane.js","sourceRoot":"","sources":["../src/crane.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ComponentNature, sceneComponent, frameClock } from '@hatiolab/things-scene'\nimport MCSVehicle from './mcs-vehicle'\nimport { LEGEND_CRANE } from './features/mcs-status-default'\nimport { LoadLayoutHolder } from './features/load-layout-holder'\nimport { HOLDER_ANCHOR, forkAnchorId } from '@hatiolab/figure-model'\nimport { craneForkCount } from './utils/crane-forks'\nimport {\n axisRatio,\n mapCraneLocalPosition,\n normalizeCranePosition,\n type CranePositionData\n} from './utils/crane-position'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n ...MCSVehicle.properties,\n {\n type: 'select',\n label: 'fork-count',\n name: 'forkCount',\n property: { options: ['1', '2'] }\n }\n ]\n}\n\n\nconst GAP = 2\n\n/** 좌표 보간 기본 duration (ms). data.DURATION_MS 가 없으면 이 값 사용. */\nconst DEFAULT_DURATION_MS = 1000\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t\n}\n\nfunction easeInOut(t: number): number {\n return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2\n}\n\ninterface CranePositionAnimator {\n fromLeft: number\n fromTop: number\n fromForkZ: number\n toLeft: number\n toTop: number\n toForkZ: number\n startTime: number\n duration: number\n unsubscribe: () => void\n}\n\n@sceneComponent('Crane')\nexport default class Crane extends LoadLayoutHolder(MCSVehicle) {\n static get nature() {\n return NATURE\n }\n\n /**\n * 크레인이 주는 Anchor (설계 02번 3.7.2절): 홀더 원점과 포크마다 하나(fork_1, fork_2). 포크가 하나뿐인 장비에\n * fork_2 에 선 Tier 가 있으면 레이아웃 전체가 잘못된 값이다: Carrier 를 자리 없이 한 무더기로 쌓고 패널에 오류를 보인다\n * (ADR-0051 4.1절 결정 8).\n */\n slotAnchors(): string[] {\n const forks = craneForkCount((this.state as any).forkCount)\n return [HOLDER_ANCHOR, ...Array.from({ length: forks }, (_, i) => forkAnchorId(i + 1))]\n }\n\n /** 포크 Anchor 는 크레인 3D 의 그 포크 판 윗면 노드다. 포크가 오르내리고 뻗으면 그 위의 Carrier 가 함께 간다 */\n anchorObject3D(anchorId: string): any {\n return (this as any)._realObject?.forkAnchor?.(anchorId) ?? super.anchorObject3D(anchorId)\n }\n\n /** 포크 수가 바뀌면 주는 Anchor 가 바뀌므로, 데이터가 다시 오지 않아도 지금 데이터로 자리를 다시 배정한다 */\n onchange(after: Record<string, any>, before: Record<string, any>): void {\n super.onchange(after, before)\n if ('forkCount' in after && craneForkCount(after.forkCount) !== craneForkCount(before?.forkCount)) {\n ;(this as any)._carrierRecordsTaken = undefined\n ;(this as any).syncCarriers(this.data || {})\n ;(this as any)._carriersSeatedAgain()\n }\n }\n\n get auxColor() {\n return 'black'\n }\n\n /** Crane 의 parentObject 는 STOCKER. */\n get parentObjectTypes(): string[] {\n return ['STOCKER']\n }\n\n getLegendFallback() {\n return (this.root as any).craneLegendTheme || super.getLegendFallback()\n }\n\n private _posAnimator?: CranePositionAnimator\n\n /**\n * data.cranePosition = { current: {x,y,z}, limit: {x|y|z:{min,max}} } 분기.\n * current 를 limit 범위 안의 0~1 비율로 환산한 뒤, 부모 (보통 Stocker) 기준으로\n * 실 좌표 (state.left/top) 와 forkZ 로 변환해 lerp 시작. x→host 긴쪽, y→짧은쪽,\n * z→포크 높이. 보간 중 새 값이 들어오면 *현재 보간 위치* 를 from 으로 잡고 새\n * target 으로 새 lerp 시작.\n * NODEID 경로는 super (MCSVehicle.onchangeData) 가 처리 — cranePosition 만 있는\n * 데이터에선 NODEID 가 없어 자연스럽게 noop.\n */\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n const afterData = after?.data\n // 데이터 키는 모두 대문자 전제. CRANEPOSITION 의 중첩 키(CURRENT/LIMIT/X/Y/Z/MIN/MAX)는\n // normalizeCranePosition 이 내부 표준형(소문자)으로 변환. DURATION_MS 는 사용하지 않음.\n const afterCP = normalizeCranePosition(afterData?.CRANEPOSITION)\n if (afterCP) {\n const beforeCP = normalizeCranePosition(before?.data?.CRANEPOSITION)\n if (this._cranePositionChanged(afterCP, beforeCP)) {\n this._startPositionAnimation(afterCP)\n }\n }\n super.onchangeData(after, before)\n }\n\n /** current 또는 limit 중 하나라도 바뀌면 재계산 필요 (limit 변화도 실 위치를 바꿈). */\n private _cranePositionChanged(a: CranePositionData, b?: CranePositionData): boolean {\n if (!b) return true\n return (\n JSON.stringify(a?.current ?? null) !== JSON.stringify(b?.current ?? null) ||\n JSON.stringify(a?.limit ?? null) !== JSON.stringify(b?.limit ?? null)\n )\n }\n\n dispose() {\n if (this._posAnimator) {\n this._posAnimator.unsubscribe()\n this._posAnimator = undefined\n }\n super.dispose()\n }\n\n private _startPositionAnimation(cranePosition: CranePositionData, durationMs?: number): void {\n const target = this._ratioToReal(cranePosition)\n if (!target) return // 부모(host) 없거나 크기 0 이면 변환 skip\n\n const now = frameClock.simTime\n const duration = Math.max(1, Number(durationMs) || DEFAULT_DURATION_MS)\n\n // 현재 위치 — 보간 중이면 그 시점 보간값을 from 으로\n let fromLeft = Number(this.state.left) || 0\n let fromTop = Number(this.state.top) || 0\n let fromForkZ = Number((this.state as any).forkZ) || 0\n\n if (this._posAnimator) {\n const a = this._posAnimator\n const t = Math.min(1, Math.max(0, (now - a.startTime) / a.duration))\n const e = easeInOut(t)\n fromLeft = lerp(a.fromLeft, a.toLeft, e)\n fromTop = lerp(a.fromTop, a.toTop, e)\n fromForkZ = lerp(a.fromForkZ, a.toForkZ, e)\n a.unsubscribe()\n this._posAnimator = undefined\n }\n\n const unsubscribe = frameClock.subscribe(\n (_dt: number, simTime: number) => this._tickPositionAnimation(simTime),\n { description: 'crane-position', component: this }\n )\n\n this._posAnimator = {\n fromLeft,\n fromTop,\n fromForkZ,\n toLeft: target.left,\n toTop: target.top,\n toForkZ: target.forkZ,\n startTime: now,\n duration,\n unsubscribe\n }\n // 즉시 첫 frame 반영 (t=0) — set 한 번 호출. paused 환경에서도 from→target\n // 진입 시점이 명확.\n this._tickPositionAnimation(now)\n }\n\n private _tickPositionAnimation(simTime: number): void {\n const a = this._posAnimator\n if (!a) return\n const t = Math.min(1, Math.max(0, (simTime - a.startTime) / a.duration))\n const e = easeInOut(t)\n this.set({\n left: lerp(a.fromLeft, a.toLeft, e),\n top: lerp(a.fromTop, a.toTop, e),\n forkZ: lerp(a.fromForkZ, a.toForkZ, e)\n })\n if (t >= 1) {\n a.unsubscribe()\n this._posAnimator = undefined\n }\n }\n\n /**\n * cranePosition (current + limit) → state.left/top + forkZ.\n *\n * 부모 오브젝트 (보통은 Stocker 이지만 다른 컨테이너일 수도) 의 width/height\n * 만 사용해 좌표 매핑 — 타입 무관 일반화. 축 배정과 원점은 host 의 내부 좌표계 설정을\n * 따른다 (coordAxisX / coordInvertX / coordInvertY, 미설정 시 아래 폴백):\n * - current.x → X축 (기본: host 의 긴쪽, 자동 판별 max(width, height))\n * - current.y → Y축 (기본: 짧은쪽)\n * - current.z → forkZ (state 에 0~100 으로 저장, 3D 측에서 mast 비율로 매핑).\n * Z 는 바닥 기준이 물리적으로 고정이라 반전 설정을 두지 않는다.\n *\n * 각 축 비율 = (current - limit.min) / (limit.max - limit.min) ∈ [0,1].\n *\n * **스토커 안쪽 오프셋**: 크레인의 폭/두께를 감안해, 크레인 *전체* 가 host 안에\n * 머무르도록 top-left 좌표를 [0, hostLen - craneLen] 범위로 매핑한다 (= 양끝에서\n * craneLen/2 만큼 inset 된 중심선 위를 움직임). 크레인이 host 보다 크면 span 0\n * 으로 떨어져 host 모서리에 정렬.\n *\n * 참조는 ParentObjectMixin 이 제공하는 `parentObject` 사용 —\n * state.parentObjectId 명시 시 그것, 미명시 시 this.parent 자동 fallback.\n */\n private _ratioToReal(\n cp: CranePositionData\n ): { left: number; top: number; forkZ: number } | undefined {\n const host: any = (this as any).parentObject\n if (!host) return undefined\n\n const hw = Number(host.state?.width) || 0\n const hh = Number(host.state?.height) || 0\n const cw = Number(this.state.width) || 0\n const ch = Number(this.state.height) || 0\n if (!hw || !hh) return undefined\n\n const rx = axisRatio(cp.current?.x, cp.limit?.x)\n const ry = axisRatio(cp.current?.y, cp.limit?.y)\n const rz = axisRatio(cp.current?.z, cp.limit?.z)\n\n // 축 배정/원점은 host(보통 STOCKER) 의 내부 좌표계 설정을 따른다. 타입을 묻지 않고\n // state 만 읽어 어떤 컨테이너가 host 여도 동일하게 동작한다 — 미설정이면 종전\n // 폴백(긴쪽=X, 원점 left/top).\n const { left, top } = mapCraneLocalPosition(hw, hh, cw, ch, rx, ry, {\n axisX: host.state?.coordAxisX,\n invertX: !!host.state?.coordInvertX,\n invertY: !!host.state?.coordInvertY\n })\n\n return {\n left,\n top,\n forkZ: rz * 100 // forkZ 0~100, 3D 측 (crane-3d) 에서 mast 비율로 매핑\n }\n }\n\n render(ctx: CanvasRenderingContext2D) {\n var { left, top, width, height } = this.bounds\n const { x: cx, y: cy } = this.center\n const rx = width / 2\n const ry = height / 2\n\n const round = Math.min(rx, ry) * 0.5\n\n ctx.translate(left, top)\n ctx.beginPath()\n\n ctx.strokeStyle = this.auxColor\n ctx.fillStyle = this.statusColor!\n ctx.lineWidth = 1\n ctx.lineCap = 'round'\n ctx.setLineDash([])\n\n const radius = Math.round(Math.min(width, height) * 0.1)\n ctx.roundRect(1, 0, width - 2, height, radius)\n ctx.roundRect(GAP + 1, GAP, width - (GAP + 1) * 2, height - GAP * 2, radius - 1)\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n ctx.arc(cx - left, cy - top, round, 0, Math.PI * 2)\n ctx.stroke()\n\n ctx.beginPath()\n ctx.roundRect(width / 2 - (round * 2) / 4 - round / 8, height / 2 - (round * 6) / 5, round / 4, (round * 6) / 5, 1)\n ctx.roundRect(width / 2 + (round * 2) / 4 - round / 8, height / 2 - (round * 6) / 5, round / 4, (round * 6) / 5, 1)\n\n ctx.fill()\n ctx.stroke()\n\n ctx.beginPath()\n\n // draw crane bar metapore\n ctx.strokeStyle = 'black'\n ctx.lineWidth = 3\n ctx.lineCap = 'butt'\n\n ctx.moveTo(GAP * 3, height / 2)\n ctx.lineTo(width - GAP * 3, height / 2)\n\n ctx.stroke()\n\n ctx.translate(-left, -top)\n }\n}\n"]}
@@ -1,5 +1,7 @@
1
1
  import './ox-input-nodes';
2
2
  import './ox-property-editor-nodes';
3
+ // 속성 패널의 "Carrier 적재 자리" 구역 (board specifics 가 적재 레이아웃 홀더를 선택했을 때 쓴다)
4
+ import './ox-load-layout-panel';
3
5
  export default [
4
6
  {
5
7
  type: 'nodes',
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AACzB,OAAO,4BAA4B,CAAA;AAEnC,eAAe;IACb;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,0BAA0B;KACpC;CACF,CAAA","sourcesContent":["import './ox-input-nodes'\nimport './ox-property-editor-nodes'\n\nexport default [\n {\n type: 'nodes',\n element: 'ox-property-editor-nodes'\n }\n]\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/editors/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AACzB,OAAO,4BAA4B,CAAA;AACnC,sEAAsE;AACtE,OAAO,wBAAwB,CAAA;AAE/B,eAAe;IACb;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,0BAA0B;KACpC;CACF,CAAA","sourcesContent":["import './ox-input-nodes'\nimport './ox-property-editor-nodes'\n// 속성 패널의 \"Carrier 적재 자리\" 구역 (board specifics 가 적재 레이아웃 홀더를 선택했을 때 쓴다)\nimport './ox-load-layout-panel'\n\nexport default [\n {\n type: 'nodes',\n element: 'ox-property-editor-nodes'\n }\n]\n"]}