@fmsim/machine 2.0.0-beta.87 → 2.0.0-beta.89

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 (56) hide show
  1. package/dist/agv.js +4 -0
  2. package/dist/agv.js.map +1 -1
  3. package/dist/carrier-manager.js +10 -4
  4. package/dist/carrier-manager.js.map +1 -1
  5. package/dist/crane.js +49 -41
  6. package/dist/crane.js.map +1 -1
  7. package/dist/factories/machine-3d.js +12 -5
  8. package/dist/factories/machine-3d.js.map +1 -1
  9. package/dist/features/parent-object-mixin.js +15 -7
  10. package/dist/features/parent-object-mixin.js.map +1 -1
  11. package/dist/mcs-carrier-holder.js +13 -10
  12. package/dist/mcs-carrier-holder.js.map +1 -1
  13. package/dist/mcs-machine.js +11 -0
  14. package/dist/mcs-machine.js.map +1 -1
  15. package/dist/mcs-unit.js +10 -0
  16. package/dist/mcs-unit.js.map +1 -1
  17. package/dist/oht.js +4 -0
  18. package/dist/oht.js.map +1 -1
  19. package/dist/port-flow.js +4 -0
  20. package/dist/port-flow.js.map +1 -1
  21. package/dist/port.js +4 -0
  22. package/dist/port.js.map +1 -1
  23. package/dist/shelf.js +4 -0
  24. package/dist/shelf.js.map +1 -1
  25. package/dist/stocker-abnormal-bar.js +4 -0
  26. package/dist/stocker-abnormal-bar.js.map +1 -1
  27. package/dist/stocker-capacity-bar.js +4 -0
  28. package/dist/stocker-capacity-bar.js.map +1 -1
  29. package/dist/stocker-carrier-bar.js +4 -0
  30. package/dist/stocker-carrier-bar.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/utils/carrier-data.js +55 -0
  33. package/dist/utils/carrier-data.js.map +1 -0
  34. package/dist/utils/crane-position.js +96 -0
  35. package/dist/utils/crane-position.js.map +1 -0
  36. package/dist/utils/fit-gauge-scale.js +28 -0
  37. package/dist/utils/fit-gauge-scale.js.map +1 -0
  38. package/package.json +3 -3
  39. package/src/agv.ts +5 -0
  40. package/src/carrier-manager.ts +11 -4
  41. package/src/crane.ts +57 -47
  42. package/src/factories/machine-3d.ts +14 -5
  43. package/src/features/parent-object-mixin.ts +16 -7
  44. package/src/mcs-carrier-holder.ts +13 -10
  45. package/src/mcs-machine.ts +13 -0
  46. package/src/mcs-unit.ts +12 -0
  47. package/src/oht.ts +5 -0
  48. package/src/port-flow.ts +5 -0
  49. package/src/port.ts +5 -0
  50. package/src/shelf.ts +5 -0
  51. package/src/stocker-abnormal-bar.ts +5 -0
  52. package/src/stocker-capacity-bar.ts +5 -0
  53. package/src/stocker-carrier-bar.ts +5 -0
  54. package/src/utils/carrier-data.ts +57 -0
  55. package/src/utils/crane-position.ts +115 -0
  56. package/src/utils/fit-gauge-scale.ts +35 -0
package/src/port-flow.ts CHANGED
@@ -45,6 +45,11 @@ export default class PortFlow extends ParentObjectMixin(RectPath(Shape)) {
45
45
  return NATURE
46
46
  }
47
47
 
48
+ /** PortFlow 의 parentObject 는 Port (유닛). */
49
+ get parentObjectTypes(): string[] {
50
+ return ['Port']
51
+ }
52
+
48
53
  get hasTextProperty() {
49
54
  return this.getState('showText') || false
50
55
  }
package/src/port.ts CHANGED
@@ -23,6 +23,11 @@ export default class Port extends MCSZoneMixin(MCSCarrierHolder) {
23
23
  return (this.root as any).portLegendTheme || super.getLegendFallback()
24
24
  }
25
25
 
26
+ /** Port 는 후보가 매우 많아 부모 머신 없이는 id 리스트를 조회하지 않는다 (machine 필수). */
27
+ get idLookupRequiresMachine() {
28
+ return true
29
+ }
30
+
26
31
  render(ctx: CanvasRenderingContext2D) {
27
32
  var { left, top, width, height } = this.bounds
28
33
 
package/src/shelf.ts CHANGED
@@ -19,6 +19,11 @@ export default class Shelf extends MCSZoneMixin(MCSCarrierHolder) {
19
19
  return 'black'
20
20
  }
21
21
 
22
+ /** Shelf 의 parentObject 는 STOCKER. */
23
+ get parentObjectTypes(): string[] {
24
+ return ['STOCKER']
25
+ }
26
+
22
27
  getLegendFallback() {
23
28
  return (this.root as any).shelfLegendTheme || super.getLegendFallback()
24
29
  }
@@ -72,6 +72,11 @@ export default class StockerAbnormalBar extends ParentObjectMixin(RectPath(Shape
72
72
  return NATURE
73
73
  }
74
74
 
75
+ /** StockerAbnormalBar 의 parentObject 는 STOCKER. */
76
+ get parentObjectTypes(): string[] {
77
+ return ['STOCKER']
78
+ }
79
+
75
80
  get controls() {
76
81
  var { left, top, width, round, height } = this.state
77
82
  round = round == undefined ? 0 : safeRound(round, width, height)
@@ -92,6 +92,11 @@ export default class StockerCapacityBar extends ParentObjectMixin(MCSStatusMixin
92
92
  return NATURE
93
93
  }
94
94
 
95
+ /** StockerCapacityBar / MCSGaugeCapacityBar 의 parentObject 는 STOCKER. */
96
+ get parentObjectTypes(): string[] {
97
+ return ['STOCKER']
98
+ }
99
+
95
100
  get controls() {
96
101
  var { left, top, width, round, height } = this.state
97
102
  round = round == undefined ? 0 : safeRound(round, width, height)
@@ -81,6 +81,11 @@ export default class StockerCarrierBar extends ParentObjectMixin(RectPath(Shape)
81
81
  return NATURE
82
82
  }
83
83
 
84
+ /** StockerCarrierBar 의 parentObject 는 STOCKER. */
85
+ get parentObjectTypes(): string[] {
86
+ return ['STOCKER']
87
+ }
88
+
84
89
  get controls() {
85
90
  var { left, top, width, round, height } = this.state
86
91
  round = round == undefined ? 0 : safeRound(round, width, height)
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Carrier 데이터 키 정규화 — 데이터 명세의 키 대소문자가 흔들려도(CARRIERNAME /
5
+ * carrierName / multiCarriers / MULTICARRIERS …) 동일하게 읽기 위함.
6
+ *
7
+ * 성능: 케이스 무관 처리를 "매 읽기"가 아니라 **데이터 진입 경계에서 1회**만 수행.
8
+ * 이후 다운스트림은 캐노니컬(대문자) 키를 평범한 O(1) 직접 접근으로 읽는다.
9
+ */
10
+
11
+ import { ciGet } from './crane-position'
12
+
13
+ /** 다중 carrier 배열의 캐노니컬 키. (구 명세의 ADDITIONS 는 더 이상 지원하지 않음) */
14
+ export const MULTI_CARRIER_KEY = 'multiCarriers'
15
+
16
+ /** carrier 식별 4 키 — 캐노니컬(대문자) 표기. */
17
+ const CARRIER_FIELDS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const
18
+ const FIELD_BY_LOWER = new Map(CARRIER_FIELDS.map(f => [f.toLowerCase(), f]))
19
+
20
+ /**
21
+ * carrier 객체의 키를 캐노니컬(대문자)로 승격 — 식별 4 키만 매핑하고 값/기타 키는 보존.
22
+ * 대소문자 무관 (carrierName → CARRIERNAME). 비객체면 그대로 반환.
23
+ */
24
+ export function normalizeCarrierItem(raw: any): any {
25
+ if (!raw || typeof raw !== 'object') return raw
26
+ const out: any = {}
27
+ for (const k of Object.keys(raw)) {
28
+ out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]
29
+ }
30
+ return out
31
+ }
32
+
33
+ /**
34
+ * holder.data 정규화 (진입 경계 1회):
35
+ * - 대표 carrier 4 키를 캐노니컬로 승격
36
+ * - 다중 carrier 배열을 캐노니컬 키 `multiCarriers` 로 통일 (대소문자 무관), 각 항목도 정규화
37
+ *
38
+ * 비객체면 그대로 반환. 정규화 결과는 idempotent (이미 캐노니컬이면 동일 형태).
39
+ */
40
+ export function normalizeCarrierData(raw: any): any {
41
+ if (!raw || typeof raw !== 'object') return raw
42
+
43
+ const multiLower = MULTI_CARRIER_KEY.toLowerCase()
44
+ const out: any = {}
45
+ for (const k of Object.keys(raw)) {
46
+ // 다중 carrier 배열 키는 아래에서 캐노니컬로 통일 처리하므로 여기선 건너뜀.
47
+ if (k.toLowerCase() === multiLower) continue
48
+ out[FIELD_BY_LOWER.get(k.toLowerCase()) ?? k] = raw[k]
49
+ }
50
+
51
+ const list = ciGet(raw, MULTI_CARRIER_KEY)
52
+ if (Array.isArray(list)) {
53
+ out[MULTI_CARRIER_KEY] = list.map(normalizeCarrierItem)
54
+ }
55
+
56
+ return out
57
+ }
@@ -0,0 +1,115 @@
1
+ /*
2
+ * Crane 위치 데이터 (data.cranePosition) 변환 유틸.
3
+ *
4
+ * 명세:
5
+ * {
6
+ * cranePosition: {
7
+ * current: { x, y, z }, // 실 좌표값
8
+ * limit: { x:{min,max}, y:{min,max}, z:{min,max} } // 각 축 한계
9
+ * }
10
+ * }
11
+ *
12
+ * current 를 limit 범위 안의 0~1 비율로 환산한 뒤 host(부모) footprint 에 매핑한다.
13
+ * x → host 긴쪽, y → 짧은쪽, z → 포크 높이(forkZ 0~100).
14
+ */
15
+
16
+ /** 축별 한계 (cranePosition.limit.{x|y|z}). */
17
+ export interface AxisLimit {
18
+ min?: number
19
+ max?: number
20
+ }
21
+
22
+ /** 새 데이터 명세 — data.cranePosition. */
23
+ export interface CranePositionData {
24
+ current?: { x?: number; y?: number; z?: number }
25
+ limit?: { x?: AxisLimit; y?: AxisLimit; z?: AxisLimit }
26
+ }
27
+
28
+ /**
29
+ * current 값을 limit 범위 안에서 0~1 비율로 환산.
30
+ * limit 누락 / 0 span / 비정상 입력 시 0 반환 (안전 fallback).
31
+ */
32
+ export function axisRatio(value: unknown, lim?: AxisLimit): number {
33
+ const v = Number(value)
34
+ const min = Number(lim?.min) || 0
35
+ const max = Number(lim?.max)
36
+ if (!Number.isFinite(v) || !Number.isFinite(max) || max === min) return 0
37
+ return Math.max(0, Math.min(1, (v - min) / (max - min)))
38
+ }
39
+
40
+ /**
41
+ * host(부모) footprint 안에서 0~1 비율 (rx,ry) → 크레인 top-left 로컬 좌표.
42
+ *
43
+ * **스토커 안쪽 오프셋**: 크레인의 폭/두께를 감안해, 크레인 *전체* 가 host 안에
44
+ * 머무르도록 top-left 좌표를 [0, hostLen - craneLen] 범위로 매핑한다 (= 양끝에서
45
+ * craneLen/2 만큼 inset 된 중심선 위를 움직임). 크레인이 host 보다 크면 span 0
46
+ * 으로 떨어져 host 모서리에 정렬.
47
+ *
48
+ * x(rx) 는 host 의 긴쪽, y(ry) 는 짧은쪽에 매핑 (가상 rail 은 host 긴쪽 방향).
49
+ */
50
+ export function mapCraneLocalPosition(
51
+ hostW: number,
52
+ hostH: number,
53
+ craneW: number,
54
+ craneH: number,
55
+ rx: number,
56
+ ry: number
57
+ ): { left: number; top: number } {
58
+ const longIsWidth = hostW >= hostH
59
+ const longLen = longIsWidth ? hostW : hostH
60
+ const shortLen = longIsWidth ? hostH : hostW
61
+ const craneLongSize = longIsWidth ? craneW : craneH
62
+ const craneShortSize = longIsWidth ? craneH : craneW
63
+
64
+ // 크레인 크기를 뺀 inset 가용 span. top-left = ratio * span 이므로 크레인
65
+ // 전체가 host [0, hostLen] 안에 들어온다.
66
+ const longSpan = Math.max(0, longLen - craneLongSize)
67
+ const shortSpan = Math.max(0, shortLen - craneShortSize)
68
+ const longPos = rx * longSpan
69
+ const shortPos = ry * shortSpan
70
+
71
+ return {
72
+ left: longIsWidth ? longPos : shortPos,
73
+ top: longIsWidth ? shortPos : longPos
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 객체에서 key 를 **대소문자 무관**으로 찾아 값 반환 (정확 매치 우선, 없으면 소문자 비교).
79
+ * 데이터 명세의 키 대소문자가 흔들려도(cranePosition / CRANEPOSITION / DURATION_MS …)
80
+ * 동일하게 읽기 위함. 매치 없으면 undefined.
81
+ */
82
+ export function ciGet(obj: any, key: string): any {
83
+ if (!obj || typeof obj !== 'object') return undefined
84
+ if (key in obj) return obj[key]
85
+ const lower = key.toLowerCase()
86
+ for (const k of Object.keys(obj)) {
87
+ if (k.toLowerCase() === lower) return obj[k]
88
+ }
89
+ return undefined
90
+ }
91
+
92
+ /**
93
+ * 객체 키를 재귀적으로 소문자화한 사본 반환 (배열/원시값은 그대로, 값은 변형 안 함).
94
+ * 중첩 키(current/limit/x/y/z/min/max)를 대소문자 무관으로 접근하기 위한 정규화.
95
+ */
96
+ export function lowerKeysDeep<T = any>(value: any): T {
97
+ if (Array.isArray(value)) return value.map(v => lowerKeysDeep(v)) as any
98
+ if (value && typeof value === 'object') {
99
+ const out: Record<string, any> = {}
100
+ for (const k of Object.keys(value)) {
101
+ out[k.toLowerCase()] = lowerKeysDeep(value[k])
102
+ }
103
+ return out as T
104
+ }
105
+ return value as T
106
+ }
107
+
108
+ /**
109
+ * raw cranePosition (키 대소문자 무관) → 캐노니컬 소문자 키 구조(CranePositionData).
110
+ * 이후 current.x / limit.x.min 등 소문자 키로 안전하게 접근 가능. 비객체면 undefined.
111
+ */
112
+ export function normalizeCranePosition(raw: any): CranePositionData | undefined {
113
+ if (!raw || typeof raw !== 'object') return undefined
114
+ return lowerKeysDeep<CranePositionData>(raw)
115
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 스토커 지붕 위 용량 게이지 바의 비례 축소 계수를 계산한다.
3
+ *
4
+ * 정책:
5
+ * - 게이지 바의 *최대* 크기는 maxW × maxH. 스토커가 충분히 크면 이 크기 그대로 (계수 1).
6
+ * - 스토커가 그보다 작으면 지붕 footprint 안에 들어오도록 *비례* 축소 (uniform).
7
+ * - *확대는 하지 않는다* (계수 상한 1) — 현재 크기가 상한이라는 요구사항.
8
+ *
9
+ * 방향 매핑: 바의 가로축(maxW)은 스토커의 *장축*에 정렬된다 (buildGauge 의
10
+ * `isWidthLonger ? geo.rotateY` 처리와 일치). 따라서 가용 치수도 그 기준으로 매칭한다.
11
+ *
12
+ * @param w 스토커 footprint 가로 (월드 단위)
13
+ * @param h 스토커 footprint 세로 (월드 단위)
14
+ * @param maxW 게이지 바 최대 가로
15
+ * @param maxH 게이지 바 최대 세로
16
+ * @param margin 지붕 가장자리 여백 비율 (0~1, 기본 0.85)
17
+ * @returns 0 < scale <= 1
18
+ */
19
+ export function fitGaugeScale(
20
+ w: number,
21
+ h: number,
22
+ maxW: number,
23
+ maxH: number,
24
+ margin: number = 0.85
25
+ ): number {
26
+ const isWidthLonger = w >= h
27
+ // 바 가로축(maxW)이 정렬되는 스토커 치수 = 장축, 세로축(maxH) = 단축
28
+ const availAlongW = isWidthLonger ? w : h
29
+ const availAlongH = isWidthLonger ? h : w
30
+
31
+ const scale = Math.min(1, (availAlongW * margin) / maxW, (availAlongH * margin) / maxH)
32
+
33
+ // 음수/NaN 방어 — 0 이하로 떨어지면 안 됨
34
+ return scale > 0 && Number.isFinite(scale) ? scale : 1
35
+ }