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

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/carrier.js CHANGED
@@ -111,12 +111,12 @@ let Carrier = class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
111
111
  // 문자열 형태의 설정들을 미리 파싱
112
112
  const parsedTheme = {};
113
113
  for (const [status, config] of Object.entries(theme)) {
114
- if (typeof config === 'string') {
114
+ if (typeof config === 'string' && config.trim()) {
115
115
  try {
116
116
  parsedTheme[status] = JSON5.parse(config);
117
117
  }
118
118
  catch (e) {
119
- console.error('JSON5 parse error:', e);
119
+ console.error('JSON5 parse error:', e, 'input:', JSON.stringify(config), 'status:', status, 'theme:', carrierAnimationName);
120
120
  parsedTheme[status] = config;
121
121
  }
122
122
  }
@@ -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_CARRIER, Legend } 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@sceneComponent('Carrier')\nexport default class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {\n static get nature() {\n return NATURE\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 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 // TODO carrierstatus에 따라서 매핑되는 애니메이션 테마 수행\n if (after.data.CARRIERSTATUS !== before.data?.CARRIERSTATUS) {\n const { CARRIERSTATUS: lastCarrierStatus } = before.data || {}\n const lastAnimationConfig = lastCarrierStatus && this.getAnimationConfig(lastCarrierStatus)\n\n if (lastAnimationConfig) {\n let { animation, decorator, border, arrow } = lastAnimationConfig\n if (animation) {\n this.resetAnimation()\n this.setState('animation', {\n oncreate: null\n })\n }\n\n if (decorator || border || arrow) {\n this.trigger('deco-off')\n }\n }\n\n const animationConfig = this.getAnimationConfig(this.getState('CARRIERSTATUS'))\n\n if (animationConfig) {\n let { animation, decorator, border, arrow } = animationConfig\n if (animation) {\n this.resetAnimation()\n this.setState('animation', {\n oncreate: animation\n })\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 }\n\n get status() {\n return this.getState('EMPTYTYPE')\n }\n\n get legend(): Legend {\n const { carrierLegendName } = this.parent?.state || {}\n\n if (carrierLegendName) {\n return (this.root as any)?.style[carrierLegendName]\n }\n\n return (this.root as any)?.carrierLegendTheme || LEGEND_CARRIER\n }\n\n getAnimationConfig(carrierStatus): AnimationConfig | null {\n const config = this.animationPreset[carrierStatus || 'default']\n return (config as AnimationConfig) || null\n }\n\n get animationPreset(): AnimationPreset {\n const carrierAnimationName = this.parent?.state.carrierAnimationName || \n this.root?.rootModel.state.carrierAnimationName || ''\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 const theme = (this.root as any)?.style[carrierAnimationName]\n if (theme) {\n // 문자열 형태의 설정들을 미리 파싱\n const parsedTheme = {}\n for (const [status, config] of Object.entries(theme)) {\n if (typeof config === 'string') {\n try {\n parsedTheme[status] = JSON5.parse(config)\n } catch (e) {\n console.error('JSON5 parse error:', e)\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 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 ctx.fillText(text, cx - width / 8, cy + height / 8)\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"]}
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_CARRIER, Legend } 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@sceneComponent('Carrier')\nexport default class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {\n static get nature() {\n return NATURE\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 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 // TODO carrierstatus에 따라서 매핑되는 애니메이션 테마 수행\n if (after.data.CARRIERSTATUS !== before.data?.CARRIERSTATUS) {\n const { CARRIERSTATUS: lastCarrierStatus } = before.data || {}\n const lastAnimationConfig = lastCarrierStatus && this.getAnimationConfig(lastCarrierStatus)\n\n if (lastAnimationConfig) {\n let { animation, decorator, border, arrow } = lastAnimationConfig\n if (animation) {\n this.resetAnimation()\n this.setState('animation', {\n oncreate: null\n })\n }\n\n if (decorator || border || arrow) {\n this.trigger('deco-off')\n }\n }\n\n const animationConfig = this.getAnimationConfig(this.getState('CARRIERSTATUS'))\n\n if (animationConfig) {\n let { animation, decorator, border, arrow } = animationConfig\n if (animation) {\n this.resetAnimation()\n this.setState('animation', {\n oncreate: animation\n })\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 }\n\n get status() {\n return this.getState('EMPTYTYPE')\n }\n\n get legend(): Legend {\n const { carrierLegendName } = this.parent?.state || {}\n\n if (carrierLegendName) {\n return (this.root as any)?.style[carrierLegendName]\n }\n\n return (this.root as any)?.carrierLegendTheme || LEGEND_CARRIER\n }\n\n getAnimationConfig(carrierStatus): AnimationConfig | null {\n const config = this.animationPreset[carrierStatus || 'default']\n return (config as AnimationConfig) || null\n }\n\n get animationPreset(): AnimationPreset {\n const carrierAnimationName = this.parent?.state.carrierAnimationName || \n this.root?.rootModel.state.carrierAnimationName || ''\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 const theme = (this.root as any)?.style[carrierAnimationName]\n if (theme) {\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 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 ctx.fillText(text, cx - width / 8, cy + height / 8)\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"]}
@@ -14,12 +14,16 @@
14
14
  */
15
15
  import * as THREE from 'three';
16
16
  import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
17
- import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene';
17
+ import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene';
18
18
  import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js';
19
19
  const BUMPER_COLOR = 0x222222;
20
20
  const PLATFORM_COLOR = 0x333333;
21
21
  const DEFAULT_BODY_COLOR = 0x6688cc;
22
22
  export class AGV3D extends RealObjectGroup {
23
+ constructor() {
24
+ super(...arguments);
25
+ this._ownMeshes = [];
26
+ }
23
27
  get effectiveDepth() {
24
28
  const { depth } = this.component.state;
25
29
  return depth || TRANSPORT_SURFACE_HEIGHT;
@@ -78,7 +82,6 @@ export class AGV3D extends RealObjectGroup {
78
82
  bumper.position.y = bumperH / 2;
79
83
  bumper.castShadow = true;
80
84
  bumper.receiveShadow = true;
81
- this.object3d.add(bumper);
82
85
  // ── 본체 (둥근 모서리, statusColor 적용) ──
83
86
  const bodyColor = this.resolveColor();
84
87
  const bodyGeo = new RoundedBoxGeometry(w, bodyH, h, 4, cornerR);
@@ -91,7 +94,6 @@ export class AGV3D extends RealObjectGroup {
91
94
  body.position.y = bumperH + bodyH / 2;
92
95
  body.castShadow = true;
93
96
  body.receiveShadow = true;
94
- this.object3d.add(body);
95
97
  // ── 상단 플랫폼 (본체보다 안쪽, 어두운색) ──
96
98
  const platInset = Math.min(w, h) * 0.04;
97
99
  const platCornerR = Math.min(w - platInset * 2, h - platInset * 2, platH) * 0.15;
@@ -105,12 +107,37 @@ export class AGV3D extends RealObjectGroup {
105
107
  plat.position.y = bumperH + bodyH + gapH + platH / 2;
106
108
  plat.castShadow = true;
107
109
  plat.receiveShadow = true;
108
- this.object3d.add(plat);
110
+ this._ownMeshes = [bumper, body, plat];
111
+ for (const m of this._ownMeshes)
112
+ this.object3d.add(m);
109
113
  }
110
114
  updateDimension() { }
111
- updateAlpha() { }
115
+ update() {
116
+ // 자신의 mesh만 제거 (Carrier 등 자식 컴포넌트 3D 보존)
117
+ for (const m of this._ownMeshes) {
118
+ this.object3d.remove(m);
119
+ disposeObject3D(m);
120
+ }
121
+ this._ownMeshes = [];
122
+ this.build();
123
+ this.updateTransform();
124
+ this.updateAlpha();
125
+ this.updateHidden();
126
+ }
127
+ updateAlpha() {
128
+ const { alpha = 1 } = this.component.state;
129
+ for (const m of this._ownMeshes) {
130
+ const materials = Array.isArray(m.material) ? m.material : [m.material];
131
+ for (const mat of materials) {
132
+ ;
133
+ mat.opacity = alpha;
134
+ mat.transparent = alpha < 1;
135
+ }
136
+ }
137
+ }
112
138
  onchange(after, before) {
113
- if ('width' in after || 'height' in after || 'depth' in after || 'fillStyle' in after || 'strokeStyle' in after) {
139
+ if ('width' in after || 'height' in after || 'depth' in after ||
140
+ 'fillStyle' in after || 'strokeStyle' in after || 'id' in after) {
114
141
  this.update();
115
142
  return;
116
143
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agv-3d.js","sourceRoot":"","sources":["../../src/factories/agv-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AGV 3D Factory\n *\n * 범퍼 + 본체 + 플랫폼 3단 구조:\n * - 하단 범퍼 (10%, 본체보다 약간 넓음, #222222)\n * - 본체 (58%, RoundedBoxGeometry, statusColor 적용)\n * - 간격 (4%)\n * - 상단 플랫폼 (28%, 본체보다 안쪽, #333333)\n *\n * Based on operato-scene Vehicle3D, adapted for fmsim:\n * - extends RealObjectGroup (Conveyor3D와 동일 패턴)\n * - statusColor from MCSStatusMixin\n * - 바닥 원점 (y=0~depth)\n */\n\nimport * as THREE from 'three'\nimport { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'\nimport { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene'\nimport type { Component } from '@hatiolab/things-scene'\nimport { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'\n\nconst BUMPER_COLOR = 0x222222\nconst PLATFORM_COLOR = 0x333333\nconst DEFAULT_BODY_COLOR = 0x6688cc\n\nexport class AGV3D extends RealObjectGroup {\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || TRANSPORT_SURFACE_HEIGHT\n }\n\n protected get syncZPosOffset(): number {\n return 0\n }\n\n private resolveColor(): number {\n const comp = this.component as any\n\n // 1) MCSStatusMixin.statusColor (legend 기반 상태 색상)\n // '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용\n try {\n const sc = comp.statusColor\n if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()\n } catch {\n /* statusColor 미구현 시 무시 */\n }\n\n // 2) state.fillStyle fallback\n const { fillStyle } = this.component.state\n if (fillStyle && typeof fillStyle === 'string') {\n try {\n return new THREE.Color(fillStyle).getHex()\n } catch {\n /* 파싱 실패 시 무시 */\n }\n }\n\n return DEFAULT_BODY_COLOR\n }\n\n build() {\n super.build()\n\n const { width = 10, height = 10 } = this.component.state\n const w = Math.abs(width)\n const h = Math.abs(height)\n const d = this.effectiveDepth\n if (!w || !h) return\n\n // 비율: 하단 범퍼 10%, 본체 58%, 간격 4%, 플랫폼 28%\n const bumperH = d * 0.1\n const bodyH = d * 0.58\n const gapH = d * 0.04\n const platH = d * 0.28\n\n const cornerR = Math.min(w, h, bodyH) * 0.12\n\n // ── 하단 범퍼 밴드 (본체보다 약간 넓게 돌출) ──\n const bumperPad = Math.min(w, h) * 0.03\n const bumperCornerR = Math.min(w + bumperPad * 2, h + bumperPad * 2, bumperH) * 0.2\n const bumperGeo = new RoundedBoxGeometry(w + bumperPad * 2, bumperH, h + bumperPad * 2, 4, bumperCornerR)\n const bumperMat = new THREE.MeshStandardMaterial({\n color: BUMPER_COLOR,\n roughness: 0.8,\n metalness: 0.1,\n })\n const bumper = new THREE.Mesh(bumperGeo, bumperMat)\n bumper.position.y = bumperH / 2\n bumper.castShadow = true\n bumper.receiveShadow = true\n this.object3d.add(bumper)\n\n // ── 본체 (둥근 모서리, statusColor 적용) ──\n const bodyColor = this.resolveColor()\n const bodyGeo = new RoundedBoxGeometry(w, bodyH, h, 4, cornerR)\n const bodyMat = new THREE.MeshStandardMaterial({\n color: bodyColor,\n roughness: 0.3,\n metalness: 0.5,\n })\n const body = new THREE.Mesh(bodyGeo, bodyMat)\n body.position.y = bumperH + bodyH / 2\n body.castShadow = true\n body.receiveShadow = true\n this.object3d.add(body)\n\n // ── 상단 플랫폼 (본체보다 안쪽, 어두운색) ──\n const platInset = Math.min(w, h) * 0.04\n const platCornerR = Math.min(w - platInset * 2, h - platInset * 2, platH) * 0.15\n const platGeo = new RoundedBoxGeometry(w - platInset * 2, platH, h - platInset * 2, 4, platCornerR)\n const platMat = new THREE.MeshStandardMaterial({\n color: PLATFORM_COLOR,\n roughness: 0.5,\n metalness: 0.15,\n })\n const plat = new THREE.Mesh(platGeo, platMat)\n plat.position.y = bumperH + bodyH + gapH + platH / 2\n plat.castShadow = true\n plat.receiveShadow = true\n this.object3d.add(plat)\n }\n\n updateDimension() {}\n updateAlpha() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if ('width' in after || 'height' in after || 'depth' in after || 'fillStyle' in after || 'strokeStyle' in after) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n}\n\nregisterRealObjectFactory('AGV', (component: Component) => new AGV3D(component))\n"]}
1
+ {"version":3,"file":"agv-3d.js","sourceRoot":"","sources":["../../src/factories/agv-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * AGV 3D Factory\n *\n * 범퍼 + 본체 + 플랫폼 3단 구조:\n * - 하단 범퍼 (10%, 본체보다 약간 넓음, #222222)\n * - 본체 (58%, RoundedBoxGeometry, statusColor 적용)\n * - 간격 (4%)\n * - 상단 플랫폼 (28%, 본체보다 안쪽, #333333)\n *\n * Based on operato-scene Vehicle3D, adapted for fmsim:\n * - extends RealObjectGroup (Conveyor3D와 동일 패턴)\n * - statusColor from MCSStatusMixin\n * - 바닥 원점 (y=0~depth)\n */\n\nimport * as THREE from 'three'\nimport { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'\nimport { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene'\nimport type { Component } from '@hatiolab/things-scene'\nimport { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'\n\nconst BUMPER_COLOR = 0x222222\nconst PLATFORM_COLOR = 0x333333\nconst DEFAULT_BODY_COLOR = 0x6688cc\n\nexport class AGV3D extends RealObjectGroup {\n private _ownMeshes: THREE.Mesh[] = []\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || TRANSPORT_SURFACE_HEIGHT\n }\n\n protected get syncZPosOffset(): number {\n return 0\n }\n\n private resolveColor(): number {\n const comp = this.component as any\n\n // 1) MCSStatusMixin.statusColor (legend 기반 상태 색상)\n // '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용\n try {\n const sc = comp.statusColor\n if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0') return new THREE.Color(sc).getHex()\n } catch {\n /* statusColor 미구현 시 무시 */\n }\n\n // 2) state.fillStyle fallback\n const { fillStyle } = this.component.state\n if (fillStyle && typeof fillStyle === 'string') {\n try {\n return new THREE.Color(fillStyle).getHex()\n } catch {\n /* 파싱 실패 시 무시 */\n }\n }\n\n return DEFAULT_BODY_COLOR\n }\n\n build() {\n super.build()\n\n const { width = 10, height = 10 } = this.component.state\n const w = Math.abs(width)\n const h = Math.abs(height)\n const d = this.effectiveDepth\n if (!w || !h) return\n\n // 비율: 하단 범퍼 10%, 본체 58%, 간격 4%, 플랫폼 28%\n const bumperH = d * 0.1\n const bodyH = d * 0.58\n const gapH = d * 0.04\n const platH = d * 0.28\n\n const cornerR = Math.min(w, h, bodyH) * 0.12\n\n // ── 하단 범퍼 밴드 (본체보다 약간 넓게 돌출) ──\n const bumperPad = Math.min(w, h) * 0.03\n const bumperCornerR = Math.min(w + bumperPad * 2, h + bumperPad * 2, bumperH) * 0.2\n const bumperGeo = new RoundedBoxGeometry(w + bumperPad * 2, bumperH, h + bumperPad * 2, 4, bumperCornerR)\n const bumperMat = new THREE.MeshStandardMaterial({\n color: BUMPER_COLOR,\n roughness: 0.8,\n metalness: 0.1,\n })\n const bumper = new THREE.Mesh(bumperGeo, bumperMat)\n bumper.position.y = bumperH / 2\n bumper.castShadow = true\n bumper.receiveShadow = true\n\n // ── 본체 (둥근 모서리, statusColor 적용) ──\n const bodyColor = this.resolveColor()\n const bodyGeo = new RoundedBoxGeometry(w, bodyH, h, 4, cornerR)\n const bodyMat = new THREE.MeshStandardMaterial({\n color: bodyColor,\n roughness: 0.3,\n metalness: 0.5,\n })\n const body = new THREE.Mesh(bodyGeo, bodyMat)\n body.position.y = bumperH + bodyH / 2\n body.castShadow = true\n body.receiveShadow = true\n\n // ── 상단 플랫폼 (본체보다 안쪽, 어두운색) ──\n const platInset = Math.min(w, h) * 0.04\n const platCornerR = Math.min(w - platInset * 2, h - platInset * 2, platH) * 0.15\n const platGeo = new RoundedBoxGeometry(w - platInset * 2, platH, h - platInset * 2, 4, platCornerR)\n const platMat = new THREE.MeshStandardMaterial({\n color: PLATFORM_COLOR,\n roughness: 0.5,\n metalness: 0.15,\n })\n const plat = new THREE.Mesh(platGeo, platMat)\n plat.position.y = bumperH + bodyH + gapH + platH / 2\n plat.castShadow = true\n plat.receiveShadow = true\n\n this._ownMeshes = [bumper, body, plat]\n for (const m of this._ownMeshes) this.object3d.add(m)\n }\n\n updateDimension() {}\n\n update() {\n // 자신의 mesh만 제거 (Carrier 등 자식 컴포넌트 3D 보존)\n for (const m of this._ownMeshes) {\n this.object3d.remove(m)\n disposeObject3D(m)\n }\n this._ownMeshes = []\n\n this.build()\n this.updateTransform()\n this.updateAlpha()\n this.updateHidden()\n }\n\n updateAlpha() {\n const { alpha = 1 } = this.component.state\n for (const m of this._ownMeshes) {\n const materials = Array.isArray(m.material) ? m.material : [m.material]\n for (const mat of materials) {\n ;(mat as THREE.MeshStandardMaterial).opacity = alpha\n ;(mat as THREE.MeshStandardMaterial).transparent = alpha < 1\n }\n }\n }\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if ('width' in after || 'height' in after || 'depth' in after ||\n 'fillStyle' in after || 'strokeStyle' in after || 'id' in after) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n}\n\nregisterRealObjectFactory('AGV', (component: Component) => new AGV3D(component))\n"]}
@@ -8,7 +8,7 @@
8
8
  * - Unit: 낮은 depth, 불투명
9
9
  */
10
10
  import * as THREE from 'three';
11
- import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene';
11
+ import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene';
12
12
  import { LEGEND_CAPACITY } from '../features/mcs-status-default';
13
13
  import { getVaueOnRanges } from '../utils/get-value-on-ranges';
14
14
  /**
@@ -29,6 +29,8 @@ const DEFAULTS = {
29
29
  class MCSRealObject3D extends RealObjectGroup {
30
30
  constructor(component, config) {
31
31
  super(component);
32
+ // ── Change handling ──
33
+ this._updatePending = false;
32
34
  this._config = config;
33
35
  }
34
36
  // width, height 절대값 (음수 값은 양수로 취급)
@@ -141,7 +143,6 @@ class MCSRealObject3D extends RealObjectGroup {
141
143
  geo.rotateX(Math.PI / 2);
142
144
  return this.removeBottomFace(geo);
143
145
  }
144
- // ── Build ──
145
146
  build() {
146
147
  var _a;
147
148
  super.build();
@@ -174,30 +175,143 @@ class MCSRealObject3D extends RealObjectGroup {
174
175
  material.polygonOffset = true;
175
176
  material.polygonOffsetFactor = -1;
176
177
  material.polygonOffsetUnit = -1;
177
- const mesh = new THREE.Mesh(geometry, material);
178
+ // BoxGeometry material groups: +x(0), -x(1), +y(2), -y(3), +z(4), -z(5)
179
+ let meshMaterial = material;
180
+ if (shape === 'box') {
181
+ if (this._config.openTop) {
182
+ // 상단면 제거 → 5면 개방 박스
183
+ const invisible = new THREE.MeshBasicMaterial({ visible: false });
184
+ meshMaterial = [material, material, invisible, material, material, material];
185
+ }
186
+ else if (this._config.texturedTop) {
187
+ // 상판에 2D 렌더링 텍스처
188
+ const topMat = this.buildTopTexture(w, h);
189
+ meshMaterial = [material, material, topMat, material, material, material];
190
+ }
191
+ else if (this._config.matteTop) {
192
+ // 상판만 무반사
193
+ const matte = new THREE.MeshBasicMaterial({ color, opacity, transparent: opacity < 1 });
194
+ meshMaterial = [material, material, matte, material, material, material];
195
+ }
196
+ }
197
+ this._ownMesh = new THREE.Mesh(geometry, meshMaterial);
178
198
  // sizeRatio < 1이면 부모 중심에 배치 (rawDepth/2), 아니면 자체 중심 (depth/2)
179
- mesh.position.y = ratio < 1 ? rawDepth / 2 : depth / 2;
180
- mesh.castShadow = true;
181
- mesh.receiveShadow = true;
182
- this.object3d.add(mesh);
199
+ this._ownMesh.position.y = ratio < 1 ? rawDepth / 2 : depth / 2;
200
+ this._ownMesh.castShadow = true;
201
+ this._ownMesh.receiveShadow = true;
202
+ this.object3d.add(this._ownMesh);
183
203
  }
184
- // ── Change handling ──
185
204
  onchange(after, before) {
186
205
  if ('width' in after ||
187
206
  'height' in after ||
188
207
  'depth' in after ||
189
208
  'fillStyle' in after ||
190
209
  'strokeStyle' in after ||
191
- 'id' in after // Carrier: CARRIERNAME 변경 시 가시성 재판정
192
- ) {
193
- this.update();
210
+ 'id' in after ||
211
+ 'EMPTYTYPE' in after ||
212
+ 'CARRIERSTATUS' in after) {
213
+ // 연속 setState에 의한 다중 rebuild 방지: 한 프레임에 한 번만 실행
214
+ if (!this._updatePending) {
215
+ this._updatePending = true;
216
+ queueMicrotask(() => {
217
+ this._updatePending = false;
218
+ this.update();
219
+ });
220
+ }
194
221
  return;
195
222
  }
196
223
  super.onchange(after, before);
197
224
  }
198
- // RealObjectGroup 기본 동작 비활성화 (update()에서 일괄 처리)
225
+ /**
226
+ * 상판 텍스처: 컴포넌트의 2D render()를 캔버스에 그려서 텍스처로 사용
227
+ */
228
+ buildTopTexture(w, h) {
229
+ var _a;
230
+ const scale = 4;
231
+ const cw = this.nextPow2(w * scale);
232
+ const ch = this.nextPow2(h * scale);
233
+ const canvas = document.createElement('canvas');
234
+ canvas.width = cw;
235
+ canvas.height = ch;
236
+ const ctx = canvas.getContext('2d');
237
+ const statusColor = '#' + this.resolveColor().toString(16).padStart(6, '0');
238
+ const emptyType = ((_a = this.component.state) === null || _a === void 0 ? void 0 : _a.EMPTYTYPE) || '';
239
+ const text = emptyType === 'FULL' ? 'F' : emptyType === 'EMPTY' ? 'E' : emptyType === 'EMPTYEMPTY' ? 'X' : '';
240
+ // 실제 사용 영역 (nextPow2 이전 크기)
241
+ const usedW = w * scale;
242
+ const usedH = h * scale;
243
+ // 바탕: 캐리어 상태 색상 (사용 영역만)
244
+ ctx.fillStyle = statusColor;
245
+ ctx.fillRect(0, 0, usedW, usedH);
246
+ // 텍스트 (사용 영역 중앙)
247
+ if (text) {
248
+ const fontSize = Math.round(Math.min(usedW, usedH) * 0.7);
249
+ ctx.font = `bold ${fontSize}px Arial`;
250
+ ctx.textAlign = 'center';
251
+ ctx.textBaseline = 'middle';
252
+ ctx.fillStyle = '#000000';
253
+ ctx.fillText(text, usedW / 2, usedH / 2);
254
+ }
255
+ const texture = new THREE.CanvasTexture(canvas);
256
+ texture.offset.set(0, 1 - usedH / ch);
257
+ texture.repeat.set(usedW / cw, usedH / ch);
258
+ texture.colorSpace = THREE.SRGBColorSpace;
259
+ return new THREE.MeshBasicMaterial({
260
+ map: texture,
261
+ transparent: true,
262
+ side: THREE.DoubleSide,
263
+ });
264
+ }
265
+ nextPow2(n) {
266
+ if (!(n > 0))
267
+ return 1;
268
+ let v = Math.ceil(n);
269
+ v--;
270
+ v |= v >> 1;
271
+ v |= v >> 2;
272
+ v |= v >> 4;
273
+ v |= v >> 8;
274
+ v |= v >> 16;
275
+ return v + 1;
276
+ }
277
+ /**
278
+ * 자신의 mesh만 제거 후 rebuild.
279
+ * 기본 update()는 clear()로 object3d의 모든 children을 제거하는데,
280
+ * 컨테이너(Port 등)의 경우 자식 컴포넌트(Carrier 등)의 3D 오브젝트도 함께 제거됨.
281
+ * 이를 방지하기 위해 자신이 생성한 mesh만 제거한다.
282
+ */
283
+ update() {
284
+ // 자신의 mesh만 제거 (자식 컴포넌트 3D 오브젝트 보존)
285
+ if (this._ownMesh) {
286
+ this.object3d.remove(this._ownMesh);
287
+ disposeObject3D(this._ownMesh);
288
+ this._ownMesh = undefined;
289
+ }
290
+ this.build();
291
+ this.updateTransform();
292
+ this.updateAlpha();
293
+ this.updateHidden();
294
+ }
295
+ // RealObjectGroup 기본 동작 비활성화
199
296
  updateDimension() { }
200
- updateAlpha() { }
297
+ updateAlpha() {
298
+ const { alpha = 1 } = this.component.state;
299
+ const configOpacity = this._config.opacity;
300
+ const effective = configOpacity * alpha;
301
+ this.object3d.traverse((o) => {
302
+ if (!o.material)
303
+ return;
304
+ const materials = Array.isArray(o.material) ? o.material : [o.material];
305
+ for (const m of materials) {
306
+ if (!m.visible)
307
+ continue; // openTop invisible face 무시
308
+ if (m.map)
309
+ continue; // 텍스처 material은 자체 transparent 유지
310
+ m.opacity = effective;
311
+ m.transparent = effective < 1;
312
+ }
313
+ });
314
+ }
201
315
  }
202
316
  // ── 팩토리 등록 헬퍼 ─────────────────────────────────────
203
317
  function register(type, overrides = {}) {
@@ -391,28 +505,114 @@ registerRealObjectFactory('STOCKER', (component) => new StockerRealObject3D(comp
391
505
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
392
506
  // Unit (이동체 · 홀더) — 낮은 depth, 불투명
393
507
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
394
- register('Port', {
395
- depthRatio: 0.5,
396
- defaultZPosFromParent: true,
397
- opacity: 0.5,
398
- metalness: 0.4,
399
- roughness: 0.6,
400
- defaultColor: 0xb0a090,
401
- });
508
+ // Port: 상부 개방 5면 박스
509
+ const PORT_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { depthRatio: 0.5, defaultZPosFromParent: true, opacity: 0.5, metalness: 0.4, roughness: 0.6, defaultColor: 0xb0a090, openTop: true });
510
+ registerRealObjectFactory('Port', (component) => new MCSRealObject3D(component, PORT_CONFIG));
402
511
  register('Shelf', {
403
512
  depthRatio: 0.25,
404
513
  metalness: 0.3,
405
514
  roughness: 0.7,
406
515
  defaultColor: 0xaabbcc,
407
516
  });
408
- register('Carrier', {
409
- depthRatio: 0.5,
410
- metalness: 0.2,
411
- roughness: 0.8,
412
- defaultColor: 0x66bb66,
413
- isVisible: (c) => !!c.state.id,
414
- sizeRatio: 0.8,
415
- });
517
+ // Carrier: CARRIERSTATUS에 따른 3D 애니메이션 지원
518
+ const CARRIER_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { depthRatio: 0.5, metalness: 0.2, roughness: 0.8, defaultColor: 0x66bb66, isVisible: (c) => !!c.state.id, sizeRatio: 0.8, texturedTop: true });
519
+ class CarrierRealObject3D extends MCSRealObject3D {
520
+ constructor(component) {
521
+ super(component, CARRIER_CONFIG);
522
+ this._animRaf = null;
523
+ this._animStartTime = 0;
524
+ }
525
+ build() {
526
+ super.build();
527
+ this.startCarrierAnimation();
528
+ }
529
+ /**
530
+ * CARRIERSTATUS의 애니메이션 설정을 읽어 3D에서 직접 실행.
531
+ * things-scene의 delta 시스템과 독립적으로 동작.
532
+ */
533
+ startCarrierAnimation() {
534
+ var _a, _b, _c;
535
+ this.stopCarrierAnimation();
536
+ const comp = this.component;
537
+ if (!((_a = comp.app) === null || _a === void 0 ? void 0 : _a.isViewMode))
538
+ return;
539
+ const carrierStatus = (_b = comp.state) === null || _b === void 0 ? void 0 : _b.CARRIERSTATUS;
540
+ if (!carrierStatus)
541
+ return;
542
+ // 애니메이션 설정 가져오기
543
+ const config = (_c = comp.getAnimationConfig) === null || _c === void 0 ? void 0 : _c.call(comp, carrierStatus);
544
+ if (!(config === null || config === void 0 ? void 0 : config.animation))
545
+ return;
546
+ const { type, duration = 2000, scale: animScale = 1.3 } = config.animation;
547
+ if (!type)
548
+ return;
549
+ this._animStartTime = performance.now();
550
+ // 애니메이션 대상: group(object3d) — mesh 개별 변환 대신 group 전체를 변환하여
551
+ // 회전/스케일 중심이 component의 3D 위치(= 2D 중심)와 정확히 일치하도록 함
552
+ const obj = this.object3d;
553
+ const animate = () => {
554
+ var _a;
555
+ if (!this._ownMesh)
556
+ return;
557
+ const elapsed = performance.now() - this._animStartTime;
558
+ const progress = (elapsed % duration) / duration; // 0~1 반복
559
+ switch (type) {
560
+ case 'heartbeat': {
561
+ const t = progress < 0.5 ? progress * 2 : (1 - progress) * 2;
562
+ const s = 1 + (animScale - 1) * t;
563
+ obj.scale.set(s, 1, s);
564
+ break;
565
+ }
566
+ case 'rotation': {
567
+ obj.rotation.y = progress * Math.PI * 2;
568
+ break;
569
+ }
570
+ case 'vibration': {
571
+ const theta = (_a = config.animation.theta) !== null && _a !== void 0 ? _a : 0.26;
572
+ const current = Math.sin(progress * Math.PI * 2) * theta;
573
+ obj.rotation.y = current;
574
+ break;
575
+ }
576
+ case 'fade': {
577
+ const alpha = 0.3 + 0.7 * (progress < 0.5 ? progress * 2 : (1 - progress) * 2);
578
+ obj.traverse((o) => {
579
+ if (o.material && !o.material.map) {
580
+ o.material.opacity = alpha;
581
+ o.material.transparent = true;
582
+ }
583
+ });
584
+ break;
585
+ }
586
+ }
587
+ this._animRaf = requestAnimationFrame(animate);
588
+ };
589
+ this._animRaf = requestAnimationFrame(animate);
590
+ }
591
+ // 자체 애니메이션이 실행 중이면 things-scene의 syncAnimationDelta 비활성화
592
+ syncAnimationDelta() {
593
+ if (!this._animRaf) {
594
+ super.syncAnimationDelta();
595
+ }
596
+ }
597
+ stopCarrierAnimation() {
598
+ if (this._animRaf) {
599
+ cancelAnimationFrame(this._animRaf);
600
+ this._animRaf = null;
601
+ }
602
+ // group 스케일/회전 초기화
603
+ this.object3d.scale.set(1, 1, 1);
604
+ this.object3d.rotation.y = 0;
605
+ }
606
+ update() {
607
+ this.stopCarrierAnimation();
608
+ super.update();
609
+ }
610
+ clear() {
611
+ this.stopCarrierAnimation();
612
+ return super.clear();
613
+ }
614
+ }
615
+ registerRealObjectFactory('Carrier', (component) => new CarrierRealObject3D(component));
416
616
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
417
617
  // Gauge (용량 게이지) — 얇은 바
418
618
  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━