@fmsim/machine 2.0.0-beta.111 → 2.0.0-beta.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/crane.js CHANGED
@@ -125,10 +125,12 @@ let Crane = class Crane extends MCSVehicle {
125
125
  * cranePosition (current + limit) → state.left/top + forkZ.
126
126
  *
127
127
  * 부모 오브젝트 (보통은 Stocker 이지만 다른 컨테이너일 수도) 의 width/height
128
- * 만 사용해 좌표 매핑 — 타입 무관 일반화. 가상 rail 은 host 의 긴쪽:
129
- * - current.x → host 의 긴쪽 (자동 판별: max(width, height))
130
- * - current.y → 짧은쪽
131
- * - current.z → forkZ (state 에 0~100 으로 저장, 3D 측에서 mast 비율로 매핑)
128
+ * 만 사용해 좌표 매핑 — 타입 무관 일반화. 축 배정과 원점은 host 의 내부 좌표계 설정을
129
+ * 따른다 (coordAxisX / coordInvertX / coordInvertY, 미설정 시 아래 폴백):
130
+ * - current.x → X축 (기본: host 의 긴쪽, 자동 판별 max(width, height))
131
+ * - current.y → Y축 (기본: 짧은쪽)
132
+ * - current.z → forkZ (state 에 0~100 으로 저장, 3D 측에서 mast 비율로 매핑).
133
+ * Z 는 바닥 기준이 물리적으로 고정이라 반전 설정을 두지 않는다.
132
134
  *
133
135
  * 각 축 비율 = (current - limit.min) / (limit.max - limit.min) ∈ [0,1].
134
136
  *
@@ -141,7 +143,7 @@ let Crane = class Crane extends MCSVehicle {
141
143
  * state.parentObjectId 명시 시 그것, 미명시 시 this.parent 자동 fallback.
142
144
  */
143
145
  _ratioToReal(cp) {
144
- var _a, _b, _c, _d, _e, _f, _g, _h;
146
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
145
147
  const host = this.parentObject;
146
148
  if (!host)
147
149
  return undefined;
@@ -154,7 +156,14 @@ let Crane = class Crane extends MCSVehicle {
154
156
  const rx = axisRatio((_c = cp.current) === null || _c === void 0 ? void 0 : _c.x, (_d = cp.limit) === null || _d === void 0 ? void 0 : _d.x);
155
157
  const ry = axisRatio((_e = cp.current) === null || _e === void 0 ? void 0 : _e.y, (_f = cp.limit) === null || _f === void 0 ? void 0 : _f.y);
156
158
  const rz = axisRatio((_g = cp.current) === null || _g === void 0 ? void 0 : _g.z, (_h = cp.limit) === null || _h === void 0 ? void 0 : _h.z);
157
- const { left, top } = mapCraneLocalPosition(hw, hh, cw, ch, rx, ry);
159
+ // 축 배정/원점은 host(보통 STOCKER) 의 내부 좌표계 설정을 따른다. 타입을 묻지 않고
160
+ // state 만 읽어 어떤 컨테이너가 host 여도 동일하게 동작한다 — 미설정이면 종전
161
+ // 폴백(긴쪽=X, 원점 left/top).
162
+ const { left, top } = mapCraneLocalPosition(hw, hh, cw, ch, rx, ry, {
163
+ axisX: (_j = host.state) === null || _j === void 0 ? void 0 : _j.coordAxisX,
164
+ invertX: !!((_k = host.state) === null || _k === void 0 ? void 0 : _k.coordInvertX),
165
+ invertY: !!((_l = host.state) === null || _l === void 0 ? void 0 : _l.coordInvertY)
166
+ });
158
167
  return {
159
168
  left,
160
169
  top,
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 * 만 사용해 좌표 매핑 — 타입 무관 일반화. 가상 rail 은 host 의 긴쪽:\n * - current.x → host 의 긴쪽 (자동 판별: max(width, height))\n * - current.y → 짧은쪽\n * - current.z → forkZ (state 에 0~100 으로 저장, 3D 측에서 mast 비율로 매핑)\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 const { left, top } = mapCraneLocalPosition(hw, hh, cw, ch, rx, ry)\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 {\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"]}
package/dist/stocker.js CHANGED
@@ -5,7 +5,35 @@ const NATURE = {
5
5
  mutable: false,
6
6
  resizable: true,
7
7
  rotatable: true,
8
- properties: [...MCSTransport.properties]
8
+ properties: [
9
+ ...MCSTransport.properties,
10
+ /* 이 스토커의 *내부 좌표계* 규약 — 외부 시스템이 보내는 X/Y 를 footprint 의 어느
11
+ * 축·어느 방향에 대응시킬지 정한다(크레인 위치 데이터 CRANEPOSITION 등이 이를 따른다).
12
+ * 미설정이면 종전 폴백(긴 변 = X축, 원점 = 좌상단). 모두 *로컬 프레임* 기준이라
13
+ * 스토커 회전(rotation)과는 무관하다. Z(높이)는 바닥 기준이 물리적으로 고정이라 설정 없음. */
14
+ {
15
+ type: 'select',
16
+ label: 'coord-axis-x',
17
+ name: 'coordAxisX',
18
+ property: {
19
+ options: [
20
+ { display: 'auto (longer side)', value: '' },
21
+ { display: 'width', value: 'width' },
22
+ { display: 'height', value: 'height' }
23
+ ]
24
+ }
25
+ },
26
+ {
27
+ type: 'checkbox',
28
+ label: 'coord-invert-x',
29
+ name: 'coordInvertX'
30
+ },
31
+ {
32
+ type: 'checkbox',
33
+ label: 'coord-invert-y',
34
+ name: 'coordInvertY'
35
+ }
36
+ ]
9
37
  };
10
38
  // const capacity = { ko: 'Capacity', en: 'Capacity', zh_CN: '容量' }
11
39
  // export function getLocaleLanguage() {
@@ -1 +1 @@
1
- {"version":3,"file":"stocker.js","sourceRoot":"","sources":["../src/stocker.ts"],"names":[],"mappings":";AAAA,OAAO,EAA0C,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC/F,OAAO,YAAY,MAAM,iBAAiB,CAAA;AAE1C,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC;CACzC,CAAA;AAED,mEAAmE;AAEnE,wCAAwC;AACxC,oBAAoB;AACpB,wCAAwC;AACxC,cAAc;AACd,0CAA0C;AAC1C,sCAAsC;AACtC,6DAA6D;AAC7D,wBAAwB;AACxB,wGAAwG;AACxG,0EAA0E;AAC1E,QAAQ;AACR,8CAA8C;AAC9C,wBAAwB;AACxB,MAAM;AACN,uBAAuB;AACvB,IAAI;AAGW,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,YAAY;IAC/C,MAAM,KAAK,MAAM;QACf,OAAO,MAAM,CAAA;IACf,CAAC;IAED,iBAAiB;QACf,OAAQ,IAAI,CAAC,IAAY,CAAC,iBAAiB,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAA;IAC1E,CAAC;IAED,WAAW,CAAC,SAAoB;QAC9B,OAAO,CACL,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC;YAC5B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CACzJ,SAAS,CAAC,KAAK,CAAC,IAAI,CACrB,CACF,CAAA;IACH,CAAC;IAED,YAAY,CAAC,KAA0B,EAAE,MAA2B;QAClE;;;;0EAIkE;QAClE,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAEjC,kEAAkE;QAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IACrH,CAAC;CACF,CAAA;AA7BoB,OAAO;IAD3B,cAAc,CAAC,SAAS,CAAC;GACL,OAAO,CA6B3B;eA7BoB,OAAO","sourcesContent":["import { Component, ComponentNature, Properties, sceneComponent } from '@hatiolab/things-scene'\nimport MCSTransport from './mcs-transport'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [...MCSTransport.properties]\n}\n\n// const capacity = { ko: 'Capacity', en: 'Capacity', zh_CN: '容量' }\n\n// export function getLocaleLanguage() {\n// let localeValue\n// let nameOfCookie = 'miipLang' + '='\n// let x = 0\n// while (x <= document.cookie.length) {\n// let y = x + nameOfCookie.length\n// if (document.cookie.substring(x, y) == nameOfCookie) {\n// let endOfCookie\n// if ((endOfCookie = document.cookie.indexOf(';', y)) == -1) endOfCookie = document.cookie.length\n// localeValue = unescape(document.cookie.substring(y, endOfCookie))\n// }\n// x = document.cookie.indexOf(' ', x) + 1\n// if (x == 0) break\n// }\n// return localeValue\n// }\n\n@sceneComponent('STOCKER')\nexport default class Stocker extends MCSTransport {\n static get nature() {\n return NATURE\n }\n\n getLegendFallback() {\n return (this.root as any).stokerLegendTheme || super.getLegendFallback()\n }\n\n containable(component: Component) {\n return (\n super.containable(component) ||\n ['Crane', 'CraneRail', 'Shelf', 'Port', 'MCSGaugeCapacityBar', 'StockerCapacityBar', 'StockerCarrierBar', 'StockerAbnormalBar', 'ZoneCapacityBar'].includes(\n component.state.type\n )\n )\n }\n\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n /* MCSStatusMixin.onchangeData 의 statusColor → state.fillStyle 동기화 로직을\n * 호출. 3D 경로에서는 mesh material 의 color 가 build 시점 한 번 결정되므로\n * 데이터 변경 시 fillStyle 이 갱신되어야 MCSRealObject3D.onchange 의\n * 'fillStyle in after' 분기를 타고 mesh rebuild 가 발생한다.\n * (2D 는 render() 가 매 프레임 statusColor getter 를 직접 사용하므로 영향 없음.) */\n super.onchangeData(after, before)\n\n /* non-identifiable component 즉, StockerCapacityBar 에게 데이터를 전달함 */\n this.components.filter(component => !component.isIdentifiable()).forEach(component => (component.data = this.data))\n }\n}\n"]}
1
+ {"version":3,"file":"stocker.js","sourceRoot":"","sources":["../src/stocker.ts"],"names":[],"mappings":";AAAA,OAAO,EAA0C,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC/F,OAAO,YAAY,MAAM,iBAAiB,CAAA;AAE1C,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV,GAAG,YAAY,CAAC,UAAU;QAC1B;;;sEAG8D;QAC9D;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,cAAc;YACrB,IAAI,EAAE,YAAY;YAClB,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,EAAE,EAAE;oBAC5C,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;oBACpC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;iBACvC;aACF;SACF;QACD;YACE,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE,cAAc;SACrB;QACD;YACE,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE,cAAc;SACrB;KACF;CACF,CAAA;AAED,mEAAmE;AAEnE,wCAAwC;AACxC,oBAAoB;AACpB,wCAAwC;AACxC,cAAc;AACd,0CAA0C;AAC1C,sCAAsC;AACtC,6DAA6D;AAC7D,wBAAwB;AACxB,wGAAwG;AACxG,0EAA0E;AAC1E,QAAQ;AACR,8CAA8C;AAC9C,wBAAwB;AACxB,MAAM;AACN,uBAAuB;AACvB,IAAI;AAGW,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,YAAY;IAC/C,MAAM,KAAK,MAAM;QACf,OAAO,MAAM,CAAA;IACf,CAAC;IAED,iBAAiB;QACf,OAAQ,IAAI,CAAC,IAAY,CAAC,iBAAiB,IAAI,KAAK,CAAC,iBAAiB,EAAE,CAAA;IAC1E,CAAC;IAED,WAAW,CAAC,SAAoB;QAC9B,OAAO,CACL,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC;YAC5B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,iBAAiB,CAAC,CAAC,QAAQ,CACzJ,SAAS,CAAC,KAAK,CAAC,IAAI,CACrB,CACF,CAAA;IACH,CAAC;IAED,YAAY,CAAC,KAA0B,EAAE,MAA2B;QAClE;;;;0EAIkE;QAClE,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QAEjC,kEAAkE;QAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;IACrH,CAAC;CACF,CAAA;AA7BoB,OAAO;IAD3B,cAAc,CAAC,SAAS,CAAC;GACL,OAAO,CA6B3B;eA7BoB,OAAO","sourcesContent":["import { Component, ComponentNature, Properties, sceneComponent } from '@hatiolab/things-scene'\nimport MCSTransport from './mcs-transport'\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n ...MCSTransport.properties,\n /* 이 스토커의 *내부 좌표계* 규약 — 외부 시스템이 보내는 X/Y 를 footprint 의 어느\n * 축·어느 방향에 대응시킬지 정한다(크레인 위치 데이터 CRANEPOSITION 등이 이를 따른다).\n * 미설정이면 종전 폴백(긴 변 = X축, 원점 = 좌상단). 모두 *로컬 프레임* 기준이라\n * 스토커 회전(rotation)과는 무관하다. Z(높이)는 바닥 기준이 물리적으로 고정이라 설정 없음. */\n {\n type: 'select',\n label: 'coord-axis-x',\n name: 'coordAxisX',\n property: {\n options: [\n { display: 'auto (longer side)', value: '' },\n { display: 'width', value: 'width' },\n { display: 'height', value: 'height' }\n ]\n }\n },\n {\n type: 'checkbox',\n label: 'coord-invert-x',\n name: 'coordInvertX'\n },\n {\n type: 'checkbox',\n label: 'coord-invert-y',\n name: 'coordInvertY'\n }\n ]\n}\n\n// const capacity = { ko: 'Capacity', en: 'Capacity', zh_CN: '容量' }\n\n// export function getLocaleLanguage() {\n// let localeValue\n// let nameOfCookie = 'miipLang' + '='\n// let x = 0\n// while (x <= document.cookie.length) {\n// let y = x + nameOfCookie.length\n// if (document.cookie.substring(x, y) == nameOfCookie) {\n// let endOfCookie\n// if ((endOfCookie = document.cookie.indexOf(';', y)) == -1) endOfCookie = document.cookie.length\n// localeValue = unescape(document.cookie.substring(y, endOfCookie))\n// }\n// x = document.cookie.indexOf(' ', x) + 1\n// if (x == 0) break\n// }\n// return localeValue\n// }\n\n@sceneComponent('STOCKER')\nexport default class Stocker extends MCSTransport {\n static get nature() {\n return NATURE\n }\n\n getLegendFallback() {\n return (this.root as any).stokerLegendTheme || super.getLegendFallback()\n }\n\n containable(component: Component) {\n return (\n super.containable(component) ||\n ['Crane', 'CraneRail', 'Shelf', 'Port', 'MCSGaugeCapacityBar', 'StockerCapacityBar', 'StockerCarrierBar', 'StockerAbnormalBar', 'ZoneCapacityBar'].includes(\n component.state.type\n )\n )\n }\n\n onchangeData(after: Record<string, any>, before: Record<string, any>): void {\n /* MCSStatusMixin.onchangeData 의 statusColor → state.fillStyle 동기화 로직을\n * 호출. 3D 경로에서는 mesh material 의 color 가 build 시점 한 번 결정되므로\n * 데이터 변경 시 fillStyle 이 갱신되어야 MCSRealObject3D.onchange 의\n * 'fillStyle in after' 분기를 타고 mesh rebuild 가 발생한다.\n * (2D 는 render() 가 매 프레임 statusColor getter 를 직접 사용하므로 영향 없음.) */\n super.onchangeData(after, before)\n\n /* non-identifiable component 즉, StockerCapacityBar 에게 데이터를 전달함 */\n this.components.filter(component => !component.isIdentifiable()).forEach(component => (component.data = this.data))\n }\n}\n"]}