@fmsim/machine 2.0.0-beta.10 → 2.0.0-beta.101

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 (152) hide show
  1. package/dist/agv.js +6 -2
  2. package/dist/agv.js.map +1 -1
  3. package/dist/carrier-manager.js +183 -0
  4. package/dist/carrier-manager.js.map +1 -0
  5. package/dist/carrier.js +99 -9
  6. package/dist/carrier.js.map +1 -1
  7. package/dist/conveyor-join.js +8 -2
  8. package/dist/conveyor-join.js.map +1 -1
  9. package/dist/conveyor.js +8 -0
  10. package/dist/conveyor.js.map +1 -1
  11. package/dist/crane.js +144 -1
  12. package/dist/crane.js.map +1 -1
  13. package/dist/factories/agv-3d.js +17 -36
  14. package/dist/factories/agv-3d.js.map +1 -1
  15. package/dist/factories/conveyor-3d.js +179 -100
  16. package/dist/factories/conveyor-3d.js.map +1 -1
  17. package/dist/factories/crane-3d.js +56 -29
  18. package/dist/factories/crane-3d.js.map +1 -1
  19. package/dist/factories/crane-rail-3d.js +14 -18
  20. package/dist/factories/crane-rail-3d.js.map +1 -1
  21. package/dist/factories/machine-3d.js +533 -197
  22. package/dist/factories/machine-3d.js.map +1 -1
  23. package/dist/factories/oht-3d.js +29 -47
  24. package/dist/factories/oht-3d.js.map +1 -1
  25. package/dist/factories/shuttle-3d.js +13 -32
  26. package/dist/factories/shuttle-3d.js.map +1 -1
  27. package/dist/factories/z-priority-defaults.js +33 -0
  28. package/dist/factories/z-priority-defaults.js.map +1 -0
  29. package/dist/factories/z-priority.js +54 -0
  30. package/dist/factories/z-priority.js.map +1 -0
  31. package/dist/features/conveyor-mixin.js.map +1 -1
  32. package/dist/features/mcs-status-default.js +15 -1
  33. package/dist/features/mcs-status-default.js.map +1 -1
  34. package/dist/features/mcs-status-mixin.js +22 -0
  35. package/dist/features/mcs-status-mixin.js.map +1 -1
  36. package/dist/features/mcs-zone-mixin.js.map +1 -1
  37. package/dist/features/parent-object-mixin.js +15 -7
  38. package/dist/features/parent-object-mixin.js.map +1 -1
  39. package/dist/floating-container.js +5 -0
  40. package/dist/floating-container.js.map +1 -1
  41. package/dist/index.js +52 -34
  42. package/dist/index.js.map +1 -1
  43. package/dist/mcs-carrier-holder.js +56 -22
  44. package/dist/mcs-carrier-holder.js.map +1 -1
  45. package/dist/mcs-machine.js +20 -0
  46. package/dist/mcs-machine.js.map +1 -1
  47. package/dist/mcs-unit.js +17 -0
  48. package/dist/mcs-unit.js.map +1 -1
  49. package/dist/mcs-vehicle.js +8 -3
  50. package/dist/mcs-vehicle.js.map +1 -1
  51. package/dist/node.js +10 -3
  52. package/dist/node.js.map +1 -1
  53. package/dist/oht.js +4 -0
  54. package/dist/oht.js.map +1 -1
  55. package/dist/port-flow.js +4 -0
  56. package/dist/port-flow.js.map +1 -1
  57. package/dist/port.js +4 -0
  58. package/dist/port.js.map +1 -1
  59. package/dist/scanner.js +30 -0
  60. package/dist/scanner.js.map +1 -0
  61. package/dist/scene/root-container-override.js +160 -96
  62. package/dist/scene/root-container-override.js.map +1 -1
  63. package/dist/shelf.js +4 -0
  64. package/dist/shelf.js.map +1 -1
  65. package/dist/static-container.js +5 -0
  66. package/dist/static-container.js.map +1 -1
  67. package/dist/stocker-abnormal-bar.js +5 -1
  68. package/dist/stocker-abnormal-bar.js.map +1 -1
  69. package/dist/stocker-capacity-bar.js +253 -6
  70. package/dist/stocker-capacity-bar.js.map +1 -1
  71. package/dist/stocker-carrier-bar.js +5 -1
  72. package/dist/stocker-carrier-bar.js.map +1 -1
  73. package/dist/stocker.js +6 -0
  74. package/dist/stocker.js.map +1 -1
  75. package/dist/templates/backdrop.js +30 -0
  76. package/dist/templates/backdrop.js.map +1 -0
  77. package/dist/templates/decal.js +21 -0
  78. package/dist/templates/decal.js.map +1 -0
  79. package/dist/templates/index.js +7 -1
  80. package/dist/templates/index.js.map +1 -1
  81. package/dist/templates/scanner.js +20 -0
  82. package/dist/templates/scanner.js.map +1 -0
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/utils/crane-position.js +83 -0
  85. package/dist/utils/crane-position.js.map +1 -0
  86. package/dist/utils/data-value.js +21 -0
  87. package/dist/utils/data-value.js.map +1 -0
  88. package/dist/utils/fit-gauge-scale.js +28 -0
  89. package/dist/utils/fit-gauge-scale.js.map +1 -0
  90. package/dist/utils/get-value-on-ranges.js +2 -4
  91. package/dist/utils/get-value-on-ranges.js.map +1 -1
  92. package/dist/utils/stocker-gauge.js +78 -0
  93. package/dist/utils/stocker-gauge.js.map +1 -0
  94. package/dist/zone-capacity-bar.js +3 -3
  95. package/dist/zone-capacity-bar.js.map +1 -1
  96. package/icons/backdrop.png +0 -0
  97. package/icons/decal.png +0 -0
  98. package/jest.config.js +10 -0
  99. package/package.json +4 -3
  100. package/src/agv.ts +7 -2
  101. package/src/carrier-manager.ts +223 -0
  102. package/src/carrier.ts +103 -10
  103. package/src/conveyor-join.ts +5 -2
  104. package/src/conveyor.ts +8 -0
  105. package/src/crane.ts +181 -1
  106. package/src/factories/agv-3d.ts +19 -36
  107. package/src/factories/conveyor-3d.ts +183 -103
  108. package/src/factories/crane-3d.ts +62 -29
  109. package/src/factories/crane-rail-3d.ts +17 -20
  110. package/src/factories/machine-3d.ts +631 -205
  111. package/src/factories/oht-3d.ts +30 -46
  112. package/src/factories/shuttle-3d.ts +14 -32
  113. package/src/factories/z-priority-defaults.ts +38 -0
  114. package/src/factories/z-priority.ts +60 -0
  115. package/src/features/conveyor-mixin.ts +1 -1
  116. package/src/features/mcs-status-default.ts +15 -1
  117. package/src/features/mcs-status-mixin.ts +26 -4
  118. package/src/features/mcs-zone-mixin.ts +1 -1
  119. package/src/features/parent-object-mixin.ts +17 -8
  120. package/src/floating-container.ts +5 -0
  121. package/src/index.ts +58 -34
  122. package/src/mcs-carrier-holder.ts +57 -34
  123. package/src/mcs-machine.ts +23 -0
  124. package/src/mcs-unit.ts +21 -1
  125. package/src/mcs-vehicle.ts +9 -6
  126. package/src/node.ts +9 -3
  127. package/src/oht.ts +6 -1
  128. package/src/port-flow.ts +5 -0
  129. package/src/port.ts +5 -0
  130. package/src/scanner.ts +29 -0
  131. package/src/scene/root-container-override.ts +154 -103
  132. package/src/shelf.ts +5 -0
  133. package/src/static-container.ts +5 -0
  134. package/src/stocker-abnormal-bar.ts +6 -1
  135. package/src/stocker-capacity-bar.ts +298 -7
  136. package/src/stocker-carrier-bar.ts +6 -1
  137. package/src/stocker.ts +7 -0
  138. package/src/templates/backdrop.ts +30 -0
  139. package/src/templates/decal.ts +21 -0
  140. package/src/templates/index.ts +7 -1
  141. package/src/templates/scanner.ts +20 -0
  142. package/src/utils/crane-position.ts +105 -0
  143. package/src/utils/data-value.ts +19 -0
  144. package/src/utils/fit-gauge-scale.ts +35 -0
  145. package/src/utils/get-value-on-ranges.ts +2 -4
  146. package/src/utils/stocker-gauge.ts +111 -0
  147. package/src/zone-capacity-bar.ts +3 -3
  148. package/translations/en.json +26 -1
  149. package/translations/ja.json +26 -1
  150. package/translations/ko.json +26 -1
  151. package/translations/ms.json +26 -1
  152. package/translations/zh.json +26 -1
@@ -12,18 +12,31 @@
12
12
  */
13
13
  import * as THREE from 'three';
14
14
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
15
- import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene';
15
+ import { RealObjectGroup, registerRealObjectFactory, MaterialBank } from '@hatiolab/things-scene';
16
16
  import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js';
17
17
  const CLAMP_COLOR = 0x444444;
18
18
  const FORK_COLOR = 0x999999;
19
19
  const DEFAULT_BODY_COLOR = 0xcc8844;
20
20
  export class Crane3D extends RealObjectGroup {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.forkMastBaseY = 0;
24
+ this.forkMastH = 0;
25
+ }
21
26
  get effectiveDepth() {
22
27
  const { depth } = this.component.state;
23
28
  return depth || 5 * TRANSPORT_SURFACE_HEIGHT - 2;
24
29
  }
25
- get syncZPosOffset() {
26
- return 0;
30
+ /**
31
+ * 0~100 정규화 z (= state.forkZ) 를 mast 높이 비율로 변환해서 포크 group 의
32
+ * three-up y 위치 갱신. build() 가 안 끝났으면 noop — build 가 forkZ 를 다시
33
+ * 반영하면서 끝난다.
34
+ */
35
+ applyForkZ(z) {
36
+ if (!this.forkGroup)
37
+ return;
38
+ const clamped = Math.max(0, Math.min(100, Number(z) || 0));
39
+ this.forkGroup.position.y = this.forkMastBaseY + (clamped / 100) * this.forkMastH;
27
40
  }
28
41
  resolveColor() {
29
42
  const comp = this.component;
@@ -54,15 +67,13 @@ export class Crane3D extends RealObjectGroup {
54
67
  const d = this.effectiveDepth;
55
68
  if (!w || !h)
56
69
  return;
70
+ const inner = this.components3D;
57
71
  // 높이 배분: 하단 클램프 4%, 마스트 92%, 상단 클램프 4%
58
72
  const clampH = d * 0.04;
59
73
  const mastH = d * 0.92;
60
74
  const pillarThick = Math.min(w, h) * 0.12;
61
- const clampMat = new THREE.MeshStandardMaterial({
62
- color: CLAMP_COLOR,
63
- roughness: 0.5,
64
- metalness: 0.5,
65
- });
75
+ // MaterialBank 공유(dedup). 자체 update() 없이 base 생명주기(guarded disposeObject3D) 상속 → 안전.
76
+ const clampMat = MaterialBank.material('crane|clamp', { roughness: 0.5, metalness: 0.5 }, CLAMP_COLOR);
66
77
  // ── 1. 하단 클램프 (바닥 레일에 물리는 바퀴부) ──
67
78
  const clampW = w * 0.7;
68
79
  const clampD = h * 0.5;
@@ -70,15 +81,11 @@ export class Crane3D extends RealObjectGroup {
70
81
  const bottomClamp = new THREE.Mesh(bottomClampGeo, clampMat);
71
82
  bottomClamp.position.y = clampH / 2;
72
83
  bottomClamp.castShadow = true;
73
- this.object3d.add(bottomClamp);
84
+ inner.add(bottomClamp);
74
85
  // ── 2. 수직 마스트 (좌우 기둥 + 상·하 가로 연결 바) ──
75
86
  const mastBaseY = clampH;
76
87
  const bodyColor = this.resolveColor();
77
- const mastMat = new THREE.MeshStandardMaterial({
78
- color: bodyColor,
79
- roughness: 0.3,
80
- metalness: 0.6,
81
- });
88
+ const mastMat = MaterialBank.material('crane|mast', { roughness: 0.3, metalness: 0.6 }, bodyColor);
82
89
  const mastGeometries = [];
83
90
  // 좌우 수직 기둥
84
91
  for (const xSign of [-1, 1]) {
@@ -98,23 +105,19 @@ export class Crane3D extends RealObjectGroup {
98
105
  mastGeometries.push(upperCross);
99
106
  const mastMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(mastGeometries), mastMat);
100
107
  mastMesh.castShadow = true;
101
- this.object3d.add(mastMesh);
102
- // ── 3. 캐리지 포크 (마스트 40% 높이, 양쪽으로 뻗는 포크 암) ──
103
- const forkY = mastBaseY + mastH * 0.4;
104
- const forkMat = new THREE.MeshStandardMaterial({
105
- color: FORK_COLOR,
106
- roughness: 0.4,
107
- metalness: 0.5,
108
- });
108
+ inner.add(mastMesh);
109
+ // ── 3. 캐리지 포크 (별도 group — state.forkZ 변경 시 group.position.y lerp) ──
110
+ // 포크 geometry 는 *_mastBaseY 기준 local 0 에 둠* — 즉 group.position.y = 0
111
+ // 일 때 mast 바닥 자리에. forkZ 0~100 에 따라 group.position.y 만 변경하면
112
+ // mast 따라 위/아래 이동. mesh 재구축 불필요.
113
+ const forkMat = MaterialBank.material('crane|fork', { roughness: 0.4, metalness: 0.5 }, FORK_COLOR);
109
114
  const forkGeometries = [];
110
- // 캐리지 중심 플레이트 (마스트에 부착, 양쪽 포크의 베이스)
111
115
  const plateW = w * 0.9;
112
116
  const plateH = d * 0.08;
113
117
  const plateD = pillarThick * 0.8;
114
118
  const centerPlate = new THREE.BoxGeometry(plateW, plateH, plateD);
115
- centerPlate.translate(0, forkY + plateH / 2, 0);
119
+ centerPlate.translate(0, plateH / 2, 0); // local 0 = mast 바닥
116
120
  forkGeometries.push(centerPlate);
117
- // 양쪽 포크 암 (좌/우 선반 방향으로 뻗음)
118
121
  const armW = pillarThick * 0.6;
119
122
  const armH = plateH * 0.4;
120
123
  const armD = h * 0.6;
@@ -122,27 +125,51 @@ export class Crane3D extends RealObjectGroup {
122
125
  for (const zSign of [-1, 1]) {
123
126
  for (const xSign of [-1, 1]) {
124
127
  const arm = new THREE.BoxGeometry(armW, armH, armD);
125
- arm.translate(xSign * (armSpacing / 2), forkY + armH / 2, zSign * (plateD / 2 + armD / 2));
128
+ arm.translate(xSign * (armSpacing / 2), armH / 2, zSign * (plateD / 2 + armD / 2));
126
129
  forkGeometries.push(arm);
127
130
  }
128
131
  }
129
132
  const forkMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(forkGeometries), forkMat);
130
133
  forkMesh.castShadow = true;
131
- this.object3d.add(forkMesh);
134
+ const forkGroup = new THREE.Group();
135
+ forkGroup.add(forkMesh);
136
+ inner.add(forkGroup);
137
+ this.forkGroup = forkGroup;
138
+ this.forkMastBaseY = mastBaseY;
139
+ // forkZ=100 시점에 mast 의 *_위쪽 한계*. 포크 자체 두께 (plateH) 만큼은 마스트
140
+ // 안에 머무르도록 limit 보정 — 그렇지 않으면 forkZ=100 일 때 포크가 상단
141
+ // 클램프와 충돌. mastH * 0.92 로 약간 보수적 (= 마스트 본체 92% 까지만 이동).
142
+ this.forkMastH = mastH * 0.92;
143
+ // 초기 forkZ 반영 — state.forkZ 가 이미 set 되어 있으면 그 위치에 배치
144
+ const initialForkZ = Number(this.component.state.forkZ);
145
+ this.applyForkZ(Number.isFinite(initialForkZ) ? initialForkZ : 0);
132
146
  // ── 4. 상단 클램프 (천장 레일에 물리는 가이드부) ──
133
147
  const topClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD);
134
148
  const topClamp = new THREE.Mesh(topClampGeo, clampMat);
135
149
  topClamp.position.y = mastBaseY + mastH + clampH / 2;
136
150
  topClamp.castShadow = true;
137
- this.object3d.add(topClamp);
151
+ inner.add(topClamp);
152
+ // mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).
153
+ this.updateAlpha();
138
154
  }
139
155
  updateDimension() { }
140
- updateAlpha() { }
156
+ // updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).
157
+ /** build() 직전 호출. forkGroup 참조를 깨끗이 비워둔다 — 새 build 가 다시 set. */
158
+ clear() {
159
+ this.forkGroup = undefined;
160
+ super.clear();
161
+ }
141
162
  onchange(after, before) {
142
163
  if ('width' in after || 'height' in after || 'depth' in after || 'fillStyle' in after || 'strokeStyle' in after) {
143
164
  this.update();
144
165
  return;
145
166
  }
167
+ // forkZ 는 mesh 재구축 없이 group.position.y 만 갱신. 같은 onchange 에서
168
+ // left/top 도 함께 변경될 수 있으므로 super 도 반드시 호출해 본체 transform
169
+ // 갱신 path (state.left/top → obj3d.position.x/z) 가 동작하게 함.
170
+ if ('forkZ' in after) {
171
+ this.applyForkZ(Number(after.forkZ) || 0);
172
+ }
146
173
  super.onchange(after, before);
147
174
  }
148
175
  }
@@ -1 +1 @@
1
- {"version":3,"file":"crane-3d.js","sourceRoot":"","sources":["../../src/factories/crane-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Crane (Stacker Crane) 3D Factory\n *\n * 스토커 내부의 스태커 크레인 — CraneRail 위를 이동하는 차체.\n * 레일 인프라는 CraneRail이 담당하므로 크레인은 이동체만 표현.\n *\n * 구조 (바닥 원점, y=0~depth):\n * - 하단 바퀴/클램프 (4%, 바닥 레일에 물리는 부분)\n * - 수직 마스트 (좌우 기둥 88%, statusColor 적용)\n * - 캐리지 포크 (마스트 40% 높이, 양쪽 포크 암)\n * - 상단 클램프 (4%, 천장 레일에 물리는 부분)\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.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 CLAMP_COLOR = 0x444444\nconst FORK_COLOR = 0x999999\nconst DEFAULT_BODY_COLOR = 0xcc8844\n\nexport class Crane3D extends RealObjectGroup {\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || 5 * TRANSPORT_SURFACE_HEIGHT - 2\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 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 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 // 높이 배분: 하단 클램프 4%, 마스트 92%, 상단 클램프 4%\n const clampH = d * 0.04\n const mastH = d * 0.92\n\n const pillarThick = Math.min(w, h) * 0.12\n\n const clampMat = new THREE.MeshStandardMaterial({\n color: CLAMP_COLOR,\n roughness: 0.5,\n metalness: 0.5,\n })\n\n // ── 1. 하단 클램프 (바닥 레일에 물리는 바퀴부) ──\n const clampW = w * 0.7\n const clampD = h * 0.5\n const bottomClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)\n const bottomClamp = new THREE.Mesh(bottomClampGeo, clampMat)\n bottomClamp.position.y = clampH / 2\n bottomClamp.castShadow = true\n this.object3d.add(bottomClamp)\n\n // ── 2. 수직 마스트 (좌우 기둥 + 상·하 가로 연결 바) ──\n const mastBaseY = clampH\n const bodyColor = this.resolveColor()\n const mastMat = new THREE.MeshStandardMaterial({\n color: bodyColor,\n roughness: 0.3,\n metalness: 0.6,\n })\n\n const mastGeometries: THREE.BufferGeometry[] = []\n\n // 좌우 수직 기둥\n for (const xSign of [-1, 1]) {\n const pillar = new THREE.BoxGeometry(pillarThick, mastH, pillarThick)\n pillar.translate(xSign * (w / 2 - pillarThick / 2), mastBaseY + mastH / 2, 0)\n mastGeometries.push(pillar)\n }\n\n // 하단 가로 연결 바\n const crossW = w - pillarThick\n const crossH = pillarThick * 0.5\n const lowerCross = new THREE.BoxGeometry(crossW, crossH, pillarThick)\n lowerCross.translate(0, mastBaseY + crossH / 2, 0)\n mastGeometries.push(lowerCross)\n\n // 상단 가로 연결 바\n const upperCross = new THREE.BoxGeometry(crossW, crossH, pillarThick)\n upperCross.translate(0, mastBaseY + mastH - crossH / 2, 0)\n mastGeometries.push(upperCross)\n\n const mastMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(mastGeometries), mastMat)\n mastMesh.castShadow = true\n this.object3d.add(mastMesh)\n\n // ── 3. 캐리지 포크 (마스트 40% 높이, 양쪽으로 뻗는 포크 암) ──\n const forkY = mastBaseY + mastH * 0.4\n const forkMat = new THREE.MeshStandardMaterial({\n color: FORK_COLOR,\n roughness: 0.4,\n metalness: 0.5,\n })\n\n const forkGeometries: THREE.BufferGeometry[] = []\n\n // 캐리지 중심 플레이트 (마스트에 부착, 양쪽 포크의 베이스)\n const plateW = w * 0.9\n const plateH = d * 0.08\n const plateD = pillarThick * 0.8\n const centerPlate = new THREE.BoxGeometry(plateW, plateH, plateD)\n centerPlate.translate(0, forkY + plateH / 2, 0)\n forkGeometries.push(centerPlate)\n\n // 양쪽 포크 암 (좌/우 선반 방향으로 뻗음)\n const armW = pillarThick * 0.6\n const armH = plateH * 0.4\n const armD = h * 0.6\n const armSpacing = w * 0.5\n\n for (const zSign of [-1, 1]) {\n for (const xSign of [-1, 1]) {\n const arm = new THREE.BoxGeometry(armW, armH, armD)\n arm.translate(xSign * (armSpacing / 2), forkY + armH / 2, zSign * (plateD / 2 + armD / 2))\n forkGeometries.push(arm)\n }\n }\n\n const forkMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(forkGeometries), forkMat)\n forkMesh.castShadow = true\n this.object3d.add(forkMesh)\n\n // ── 4. 상단 클램프 (천장 레일에 물리는 가이드부) ──\n const topClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)\n const topClamp = new THREE.Mesh(topClampGeo, clampMat)\n topClamp.position.y = mastBaseY + mastH + clampH / 2\n topClamp.castShadow = true\n this.object3d.add(topClamp)\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('Crane', (component: Component) => new Crane3D(component))\n"]}
1
+ {"version":3,"file":"crane-3d.js","sourceRoot":"","sources":["../../src/factories/crane-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Crane (Stacker Crane) 3D Factory\n *\n * 스토커 내부의 스태커 크레인 — CraneRail 위를 이동하는 차체.\n * 레일 인프라는 CraneRail이 담당하므로 크레인은 이동체만 표현.\n *\n * 구조 (바닥 원점, y=0~depth):\n * - 하단 바퀴/클램프 (4%, 바닥 레일에 물리는 부분)\n * - 수직 마스트 (좌우 기둥 88%, statusColor 적용)\n * - 캐리지 포크 (마스트 40% 높이, 양쪽 포크 암)\n * - 상단 클램프 (4%, 천장 레일에 물리는 부분)\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup, registerRealObjectFactory, MaterialBank } from '@hatiolab/things-scene'\nimport type { Component } from '@hatiolab/things-scene'\nimport { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'\n\nconst CLAMP_COLOR = 0x444444\nconst FORK_COLOR = 0x999999\nconst DEFAULT_BODY_COLOR = 0xcc8844\n\nexport class Crane3D extends RealObjectGroup {\n /** 캐리지 포크 group — Crane.state.forkZ (0~100) 가 변하면 position.y 만 lerp.\n * build() 시 inner 에 add 되고, *_mastBaseY 와 mastH 를 함께 보관* 해서\n * setForkZ 가 비율 계산에 사용. */\n private forkGroup?: THREE.Group\n private forkMastBaseY = 0\n private forkMastH = 0\n\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || 5 * TRANSPORT_SURFACE_HEIGHT - 2\n }\n\n /**\n * 0~100 정규화 z (= state.forkZ) 를 mast 높이 비율로 변환해서 포크 group 의\n * three-up y 위치 갱신. build() 가 안 끝났으면 noop — build 가 forkZ 를 다시\n * 반영하면서 끝난다.\n */\n applyForkZ(z: number): void {\n if (!this.forkGroup) return\n const clamped = Math.max(0, Math.min(100, Number(z) || 0))\n this.forkGroup.position.y = this.forkMastBaseY + (clamped / 100) * this.forkMastH\n }\n\n private resolveColor(): number {\n const comp = this.component as any\n\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 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 const inner = this.components3D\n\n // 높이 배분: 하단 클램프 4%, 마스트 92%, 상단 클램프 4%\n const clampH = d * 0.04\n const mastH = d * 0.92\n\n const pillarThick = Math.min(w, h) * 0.12\n\n // MaterialBank 공유(dedup). 자체 update() 없이 base 생명주기(guarded disposeObject3D) 상속 → 안전.\n const clampMat = MaterialBank.material('crane|clamp', { roughness: 0.5, metalness: 0.5 }, CLAMP_COLOR)\n\n // ── 1. 하단 클램프 (바닥 레일에 물리는 바퀴부) ──\n const clampW = w * 0.7\n const clampD = h * 0.5\n const bottomClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)\n const bottomClamp = new THREE.Mesh(bottomClampGeo, clampMat)\n bottomClamp.position.y = clampH / 2\n bottomClamp.castShadow = true\n inner.add(bottomClamp)\n\n // ── 2. 수직 마스트 (좌우 기둥 + 상·하 가로 연결 바) ──\n const mastBaseY = clampH\n const bodyColor = this.resolveColor()\n const mastMat = MaterialBank.material('crane|mast', { roughness: 0.3, metalness: 0.6 }, bodyColor)\n\n const mastGeometries: THREE.BufferGeometry[] = []\n\n // 좌우 수직 기둥\n for (const xSign of [-1, 1]) {\n const pillar = new THREE.BoxGeometry(pillarThick, mastH, pillarThick)\n pillar.translate(xSign * (w / 2 - pillarThick / 2), mastBaseY + mastH / 2, 0)\n mastGeometries.push(pillar)\n }\n\n // 하단 가로 연결 바\n const crossW = w - pillarThick\n const crossH = pillarThick * 0.5\n const lowerCross = new THREE.BoxGeometry(crossW, crossH, pillarThick)\n lowerCross.translate(0, mastBaseY + crossH / 2, 0)\n mastGeometries.push(lowerCross)\n\n // 상단 가로 연결 바\n const upperCross = new THREE.BoxGeometry(crossW, crossH, pillarThick)\n upperCross.translate(0, mastBaseY + mastH - crossH / 2, 0)\n mastGeometries.push(upperCross)\n\n const mastMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(mastGeometries), mastMat)\n mastMesh.castShadow = true\n inner.add(mastMesh)\n\n // ── 3. 캐리지 포크 (별도 group — state.forkZ 변경 시 group.position.y lerp) ──\n // 포크 geometry 는 *_mastBaseY 기준 local 0 에 둠* — 즉 group.position.y = 0\n // 일 때 mast 바닥 자리에. forkZ 0~100 에 따라 group.position.y 만 변경하면\n // mast 따라 위/아래 이동. mesh 재구축 불필요.\n const forkMat = MaterialBank.material('crane|fork', { roughness: 0.4, metalness: 0.5 }, FORK_COLOR)\n\n const forkGeometries: THREE.BufferGeometry[] = []\n\n const plateW = w * 0.9\n const plateH = d * 0.08\n const plateD = pillarThick * 0.8\n const centerPlate = new THREE.BoxGeometry(plateW, plateH, plateD)\n centerPlate.translate(0, plateH / 2, 0) // local 0 = mast 바닥\n forkGeometries.push(centerPlate)\n\n const armW = pillarThick * 0.6\n const armH = plateH * 0.4\n const armD = h * 0.6\n const armSpacing = w * 0.5\n\n for (const zSign of [-1, 1]) {\n for (const xSign of [-1, 1]) {\n const arm = new THREE.BoxGeometry(armW, armH, armD)\n arm.translate(xSign * (armSpacing / 2), armH / 2, zSign * (plateD / 2 + armD / 2))\n forkGeometries.push(arm)\n }\n }\n\n const forkMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(forkGeometries), forkMat)\n forkMesh.castShadow = true\n\n const forkGroup = new THREE.Group()\n forkGroup.add(forkMesh)\n inner.add(forkGroup)\n this.forkGroup = forkGroup\n this.forkMastBaseY = mastBaseY\n // forkZ=100 시점에 mast 의 *_위쪽 한계*. 포크 자체 두께 (plateH) 만큼은 마스트\n // 안에 머무르도록 limit 보정 — 그렇지 않으면 forkZ=100 일 때 포크가 상단\n // 클램프와 충돌. mastH * 0.92 로 약간 보수적 (= 마스트 본체 92% 까지만 이동).\n this.forkMastH = mastH * 0.92\n\n // 초기 forkZ 반영 — state.forkZ 가 이미 set 되어 있으면 그 위치에 배치\n const initialForkZ = Number((this.component.state as any).forkZ)\n this.applyForkZ(Number.isFinite(initialForkZ) ? initialForkZ : 0)\n\n // ── 4. 상단 클램프 (천장 레일에 물리는 가이드부) ──\n const topClampGeo = new THREE.BoxGeometry(clampW, clampH, clampD)\n const topClamp = new THREE.Mesh(topClampGeo, clampMat)\n topClamp.position.y = mastBaseY + mastH + clampH / 2\n topClamp.castShadow = true\n inner.add(topClamp)\n\n // mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).\n this.updateAlpha()\n }\n\n updateDimension() {}\n // updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).\n\n /** build() 직전 호출. forkGroup 참조를 깨끗이 비워둔다 — 새 build 가 다시 set. */\n clear() {\n this.forkGroup = undefined\n super.clear()\n }\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 // forkZ 는 mesh 재구축 없이 group.position.y 만 갱신. 같은 onchange 에서\n // left/top 도 함께 변경될 수 있으므로 super 도 반드시 호출해 본체 transform\n // 갱신 path (state.left/top → obj3d.position.x/z) 가 동작하게 함.\n if ('forkZ' in after) {\n this.applyForkZ(Number(after.forkZ) || 0)\n }\n super.onchange(after, before)\n }\n}\n\nregisterRealObjectFactory('Crane', (component: Component) => new Crane3D(component))\n"]}
@@ -11,20 +11,19 @@
11
11
  */
12
12
  import * as THREE from 'three';
13
13
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
14
- import { RealObjectGroup, registerRealObjectFactory } from '@hatiolab/things-scene';
14
+ import { RealObjectGroup, registerRealObjectFactory, MaterialBank } from '@hatiolab/things-scene';
15
15
  import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js';
16
+ import { Z_FMSIM } from './z-priority';
16
17
  const RAIL_COLOR = 0x666677;
17
18
  const SUPPORT_COLOR = 0x555566;
18
19
  /** 크레인 레일 기본 높이: 스토커와 동일 */
19
20
  const DEFAULT_RAIL_DEPTH = 5 * TRANSPORT_SURFACE_HEIGHT;
20
21
  export class CraneRail3D extends RealObjectGroup {
22
+ get zPriority() { return Z_FMSIM.CRANE_RAIL; }
21
23
  get effectiveDepth() {
22
24
  const { depth } = this.component.state;
23
25
  return depth || DEFAULT_RAIL_DEPTH;
24
26
  }
25
- get syncZPosOffset() {
26
- return 0;
27
- }
28
27
  build() {
29
28
  super.build();
30
29
  const { width = 10, height = 10 } = this.component.state;
@@ -33,19 +32,14 @@ export class CraneRail3D extends RealObjectGroup {
33
32
  const d = this.effectiveDepth;
34
33
  if (!w || !h)
35
34
  return;
35
+ const inner = this.components3D;
36
36
  const railH = Math.max(d * 0.02, 2); // 레일 높이 (얇은 트랙)
37
37
  const railW = Math.max(h * 0.06, 2); // 레일 폭
38
38
  const supportThick = Math.max(Math.min(w, h) * 0.05, 2);
39
- const railMat = new THREE.MeshStandardMaterial({
40
- color: RAIL_COLOR,
41
- roughness: 0.3,
42
- metalness: 0.7,
43
- });
44
- const supportMat = new THREE.MeshStandardMaterial({
45
- color: SUPPORT_COLOR,
46
- roughness: 0.5,
47
- metalness: 0.5,
48
- });
39
+ // MaterialBank 공유(정적 색 dedup). 자체 update() 없이 base 생명주기(guarded disposeObject3D)
40
+ // 상속 → 공유 base dispose 안전.
41
+ const railMat = MaterialBank.material('crane-rail|rail', { roughness: 0.3, metalness: 0.7 }, RAIL_COLOR);
42
+ const supportMat = MaterialBank.material('crane-rail|support', { roughness: 0.5, metalness: 0.5 }, SUPPORT_COLOR);
49
43
  // ── 바닥 레일 (2개 평행 트랙) ──
50
44
  const bottomGeometries = [];
51
45
  for (const zSign of [-1, 1]) {
@@ -56,7 +50,7 @@ export class CraneRail3D extends RealObjectGroup {
56
50
  const bottomMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(bottomGeometries), railMat);
57
51
  bottomMesh.castShadow = true;
58
52
  bottomMesh.receiveShadow = true;
59
- this.object3d.add(bottomMesh);
53
+ inner.add(bottomMesh);
60
54
  // ── 천장 가이드 레일 (2개 평행 트랙) ──
61
55
  const topGeometries = [];
62
56
  for (const zSign of [-1, 1]) {
@@ -66,7 +60,7 @@ export class CraneRail3D extends RealObjectGroup {
66
60
  }
67
61
  const topMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topGeometries), railMat);
68
62
  topMesh.castShadow = true;
69
- this.object3d.add(topMesh);
63
+ inner.add(topMesh);
70
64
  // ── 양 끝단 수직 지지대 (바닥~천장 연결) ──
71
65
  const supportH = d - railH * 2;
72
66
  const supportGeometries = [];
@@ -79,10 +73,12 @@ export class CraneRail3D extends RealObjectGroup {
79
73
  }
80
74
  const supportMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(supportGeometries), supportMat);
81
75
  supportMesh.castShadow = true;
82
- this.object3d.add(supportMesh);
76
+ inner.add(supportMesh);
77
+ // mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).
78
+ this.updateAlpha();
83
79
  }
84
80
  updateDimension() { }
85
- updateAlpha() { }
81
+ // updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).
86
82
  onchange(after, before) {
87
83
  if ('width' in after || 'height' in after || 'depth' in after || 'fillStyle' in after || 'strokeStyle' in after) {
88
84
  this.update();
@@ -1 +1 @@
1
- {"version":3,"file":"crane-rail-3d.js","sourceRoot":"","sources":["../../src/factories/crane-rail-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * CraneRail 3D Factory\n *\n * 스토커 내부 스태커 크레인의 주행 레일.\n * 바닥 레일 + 천장 가이드 레일로 크레인의 2축 이동(수평 주행 + 수직 승강)을 지원.\n *\n * 구조 (바닥 원점, y=0~depth):\n * - 바닥 레일: 2개의 평행 트랙 (크레인 바퀴 주행)\n * - 천장 가이드 레일: 2개의 평행 트랙 (마스트 상단 안내)\n * - 수직 지지대: 양 끝단 기둥 (바닥~천장 연결)\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.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 RAIL_COLOR = 0x666677\nconst SUPPORT_COLOR = 0x555566\n\n/** 크레인 레일 기본 높이: 스토커와 동일 */\nconst DEFAULT_RAIL_DEPTH = 5 * TRANSPORT_SURFACE_HEIGHT\n\nexport class CraneRail3D extends RealObjectGroup {\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || DEFAULT_RAIL_DEPTH\n }\n\n protected get syncZPosOffset(): number {\n return 0\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 const railH = Math.max(d * 0.02, 2) // 레일 높이 (얇은 트랙)\n const railW = Math.max(h * 0.06, 2) // 레일 폭\n const supportThick = Math.max(Math.min(w, h) * 0.05, 2)\n\n const railMat = new THREE.MeshStandardMaterial({\n color: RAIL_COLOR,\n roughness: 0.3,\n metalness: 0.7,\n })\n\n const supportMat = new THREE.MeshStandardMaterial({\n color: SUPPORT_COLOR,\n roughness: 0.5,\n metalness: 0.5,\n })\n\n // ── 바닥 레일 (2개 평행 트랙) ──\n const bottomGeometries: THREE.BufferGeometry[] = []\n for (const zSign of [-1, 1]) {\n const rail = new THREE.BoxGeometry(w, railH, railW)\n rail.translate(0, railH / 2, zSign * (h / 2 - railW / 2))\n bottomGeometries.push(rail)\n }\n const bottomMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(bottomGeometries), railMat)\n bottomMesh.castShadow = true\n bottomMesh.receiveShadow = true\n this.object3d.add(bottomMesh)\n\n // ── 천장 가이드 레일 (2개 평행 트랙) ──\n const topGeometries: THREE.BufferGeometry[] = []\n for (const zSign of [-1, 1]) {\n const rail = new THREE.BoxGeometry(w, railH, railW)\n rail.translate(0, d - railH / 2, zSign * (h / 2 - railW / 2))\n topGeometries.push(rail)\n }\n const topMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topGeometries), railMat)\n topMesh.castShadow = true\n this.object3d.add(topMesh)\n\n // ── 양 끝단 수직 지지대 (바닥~천장 연결) ──\n const supportH = d - railH * 2\n const supportGeometries: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const support = new THREE.BoxGeometry(supportThick, supportH, supportThick)\n support.translate(\n xSign * (w / 2 - supportThick / 2),\n railH + supportH / 2,\n zSign * (h / 2 - railW / 2)\n )\n supportGeometries.push(support)\n }\n }\n const supportMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(supportGeometries), supportMat)\n supportMesh.castShadow = true\n this.object3d.add(supportMesh)\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('CraneRail', (component: Component) => new CraneRail3D(component))\n"]}
1
+ {"version":3,"file":"crane-rail-3d.js","sourceRoot":"","sources":["../../src/factories/crane-rail-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * CraneRail 3D Factory\n *\n * 스토커 내부 스태커 크레인의 주행 레일.\n * 바닥 레일 + 천장 가이드 레일로 크레인의 2축 이동(수평 주행 + 수직 승강)을 지원.\n *\n * 구조 (바닥 원점, y=0~depth):\n * - 바닥 레일: 2개의 평행 트랙 (크레인 바퀴 주행)\n * - 천장 가이드 레일: 2개의 평행 트랙 (마스트 상단 안내)\n * - 수직 지지대: 양 끝단 기둥 (바닥~천장 연결)\n */\n\nimport * as THREE from 'three'\nimport * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'\nimport { RealObjectGroup, registerRealObjectFactory, MaterialBank } from '@hatiolab/things-scene'\nimport type { Component } from '@hatiolab/things-scene'\nimport { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js'\nimport { Z_FMSIM } from './z-priority'\n\nconst RAIL_COLOR = 0x666677\nconst SUPPORT_COLOR = 0x555566\n\n/** 크레인 레일 기본 높이: 스토커와 동일 */\nconst DEFAULT_RAIL_DEPTH = 5 * TRANSPORT_SURFACE_HEIGHT\n\nexport class CraneRail3D extends RealObjectGroup {\n get zPriority(): number { return Z_FMSIM.CRANE_RAIL }\n\n get effectiveDepth(): number {\n const { depth } = this.component.state\n return depth || DEFAULT_RAIL_DEPTH\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 const inner = this.components3D\n\n const railH = Math.max(d * 0.02, 2) // 레일 높이 (얇은 트랙)\n const railW = Math.max(h * 0.06, 2) // 레일 폭\n const supportThick = Math.max(Math.min(w, h) * 0.05, 2)\n\n // MaterialBank 공유(정적 색 dedup). 자체 update() 없이 base 생명주기(guarded disposeObject3D)\n // 상속 → 공유 base dispose 안전.\n const railMat = MaterialBank.material('crane-rail|rail', { roughness: 0.3, metalness: 0.7 }, RAIL_COLOR)\n const supportMat = MaterialBank.material('crane-rail|support', { roughness: 0.5, metalness: 0.5 }, SUPPORT_COLOR)\n\n // ── 바닥 레일 (2개 평행 트랙) ──\n const bottomGeometries: THREE.BufferGeometry[] = []\n for (const zSign of [-1, 1]) {\n const rail = new THREE.BoxGeometry(w, railH, railW)\n rail.translate(0, railH / 2, zSign * (h / 2 - railW / 2))\n bottomGeometries.push(rail)\n }\n const bottomMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(bottomGeometries), railMat)\n bottomMesh.castShadow = true\n bottomMesh.receiveShadow = true\n inner.add(bottomMesh)\n\n // ── 천장 가이드 레일 (2개 평행 트랙) ──\n const topGeometries: THREE.BufferGeometry[] = []\n for (const zSign of [-1, 1]) {\n const rail = new THREE.BoxGeometry(w, railH, railW)\n rail.translate(0, d - railH / 2, zSign * (h / 2 - railW / 2))\n topGeometries.push(rail)\n }\n const topMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(topGeometries), railMat)\n topMesh.castShadow = true\n inner.add(topMesh)\n\n // ── 양 끝단 수직 지지대 (바닥~천장 연결) ──\n const supportH = d - railH * 2\n const supportGeometries: THREE.BufferGeometry[] = []\n for (const xSign of [-1, 1]) {\n for (const zSign of [-1, 1]) {\n const support = new THREE.BoxGeometry(supportThick, supportH, supportThick)\n support.translate(\n xSign * (w / 2 - supportThick / 2),\n railH + supportH / 2,\n zSign * (h / 2 - railW / 2)\n )\n supportGeometries.push(support)\n }\n }\n const supportMesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(supportGeometries), supportMat)\n supportMesh.castShadow = true\n inner.add(supportMesh)\n\n // mesh 는 super.build() 이후 생성 → 코어 faint 를 명시 적용(편집 흐림이 mesh 에 닿게).\n this.updateAlpha()\n }\n\n updateDimension() {}\n // updateAlpha override 제거 — 코어 RealObject.updateAlpha(=applyMaterialOpacity)로 수렴(faint).\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('CraneRail', (component: Component) => new CraneRail3D(component))\n"]}