@fmsim/machine 2.0.0-beta.3 → 2.0.0-beta.30
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-manager.js +92 -0
- package/dist/carrier-manager.js.map +1 -0
- package/dist/carrier.js +51 -6
- package/dist/carrier.js.map +1 -1
- package/dist/conveyor-join.js +8 -2
- package/dist/conveyor-join.js.map +1 -1
- package/dist/conveyor.js +8 -0
- package/dist/conveyor.js.map +1 -1
- package/dist/factories/agv-3d.js +36 -8
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +159 -69
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +604 -82
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +50 -24
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/factories/shuttle-3d.js +32 -6
- package/dist/factories/shuttle-3d.js.map +1 -1
- package/dist/index.js +50 -34
- package/dist/index.js.map +1 -1
- package/dist/mcs-carrier-holder.js +39 -22
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/node.js +1 -1
- package/dist/node.js.map +1 -1
- package/dist/scanner.js +30 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scene/root-container-override.js +9 -0
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/scanner.js +20 -0
- package/dist/templates/scanner.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/jest.config.js +10 -0
- package/package.json +4 -3
- package/src/carrier-manager.ts +116 -0
- package/src/carrier.ts +50 -4
- package/src/conveyor-join.ts +5 -2
- package/src/conveyor.ts +8 -0
- package/src/factories/agv-3d.ts +36 -8
- package/src/factories/conveyor-3d.ts +161 -71
- package/src/factories/machine-3d.ts +708 -72
- package/src/factories/oht-3d.ts +48 -24
- package/src/factories/shuttle-3d.ts +30 -6
- package/src/index.ts +56 -34
- package/src/mcs-carrier-holder.ts +38 -34
- package/src/node.ts +1 -1
- package/src/scanner.ts +29 -0
- package/src/scene/root-container-override.ts +12 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/scanner.ts +20 -0
- package/translations/en.json +2 -1
- package/translations/ja.json +2 -1
- package/translations/ko.json +2 -1
- package/translations/ms.json +2 -1
- package/translations/zh.json +2 -1
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
* - Unit: 낮은 depth, 불투명
|
|
9
9
|
*/
|
|
10
10
|
import * as THREE from 'three';
|
|
11
|
-
import
|
|
11
|
+
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
12
|
+
import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene';
|
|
13
|
+
import { LEGEND_CAPACITY } from '../features/mcs-status-default';
|
|
14
|
+
import { getVaueOnRanges } from '../utils/get-value-on-ranges';
|
|
12
15
|
/**
|
|
13
16
|
* 포트 기본 높이 (절대값).
|
|
14
17
|
* 모든 이송장치의 상단 높이 = 이 값.
|
|
@@ -27,6 +30,8 @@ const DEFAULTS = {
|
|
|
27
30
|
class MCSRealObject3D extends RealObjectGroup {
|
|
28
31
|
constructor(component, config) {
|
|
29
32
|
super(component);
|
|
33
|
+
// ── Change handling ──
|
|
34
|
+
this._updatePending = false;
|
|
30
35
|
this._config = config;
|
|
31
36
|
}
|
|
32
37
|
// width, height 절대값 (음수 값은 양수로 취급)
|
|
@@ -45,14 +50,22 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
45
50
|
}
|
|
46
51
|
get syncZPosOffset() {
|
|
47
52
|
var _a, _b;
|
|
53
|
+
// zPosOverride: 고정 높이 (OHTLine 등)
|
|
54
|
+
if (this._config.zPosOverride != null) {
|
|
55
|
+
return this._config.zPosOverride;
|
|
56
|
+
}
|
|
48
57
|
// Port: 이송 표면 높이에 배치
|
|
49
58
|
if (this._config.defaultZPosFromParent) {
|
|
50
59
|
return TRANSPORT_SURFACE_HEIGHT;
|
|
51
60
|
}
|
|
52
|
-
// Carrier:
|
|
61
|
+
// Carrier: 부모 유형에 따라 배치 위치 결정
|
|
53
62
|
if (this._config.sizeRatio && this._config.sizeRatio < 1) {
|
|
54
63
|
const parentType = (_b = (_a = this.component.parent) === null || _a === void 0 ? void 0 : _a.state) === null || _b === void 0 ? void 0 : _b.type;
|
|
55
|
-
|
|
64
|
+
// OHT: 그리퍼 아래에 매달림 (캐리어 상단이 OHT 하단에 붙음)
|
|
65
|
+
if (parentType === 'OHT') {
|
|
66
|
+
return -this.effectiveDepth;
|
|
67
|
+
}
|
|
68
|
+
if (parentType === 'Port' || parentType === 'Shelf' || parentType === 'Shuttle') {
|
|
56
69
|
return 0;
|
|
57
70
|
}
|
|
58
71
|
return TRANSPORT_SURFACE_HEIGHT;
|
|
@@ -66,7 +79,7 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
66
79
|
// '#F0F0F0'은 상태 미설정 시 기본 fallback — 무시하고 defaultColor 사용
|
|
67
80
|
try {
|
|
68
81
|
const sc = comp.statusColor;
|
|
69
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0')
|
|
82
|
+
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent')
|
|
70
83
|
return new THREE.Color(sc).getHex();
|
|
71
84
|
}
|
|
72
85
|
catch (_a) {
|
|
@@ -74,7 +87,7 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
74
87
|
}
|
|
75
88
|
// 2) state.fillStyle fallback
|
|
76
89
|
const { fillStyle } = this.component.state;
|
|
77
|
-
if (fillStyle && typeof fillStyle === 'string') {
|
|
90
|
+
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
78
91
|
try {
|
|
79
92
|
return new THREE.Color(fillStyle).getHex();
|
|
80
93
|
}
|
|
@@ -114,7 +127,7 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
114
127
|
// ── Geometry builders ──
|
|
115
128
|
buildBoxGeometry(w, d, h) {
|
|
116
129
|
const geo = new THREE.BoxGeometry(w, d, h);
|
|
117
|
-
return this.removeBottomFace(geo);
|
|
130
|
+
return this._config.keepBottomFace ? geo : this.removeBottomFace(geo);
|
|
118
131
|
}
|
|
119
132
|
buildHexagonGeometry(w, d, h) {
|
|
120
133
|
// Buffer의 2D 형태: 좌우 끝이 꺾인 육각형
|
|
@@ -139,7 +152,11 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
139
152
|
geo.rotateX(Math.PI / 2);
|
|
140
153
|
return this.removeBottomFace(geo);
|
|
141
154
|
}
|
|
142
|
-
|
|
155
|
+
buildPlaneGeometry(w, h) {
|
|
156
|
+
const geo = new THREE.PlaneGeometry(w, h);
|
|
157
|
+
geo.rotateX(-Math.PI / 2);
|
|
158
|
+
return geo;
|
|
159
|
+
}
|
|
143
160
|
build() {
|
|
144
161
|
var _a;
|
|
145
162
|
super.build();
|
|
@@ -156,46 +173,164 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
156
173
|
const depth = rawDepth * ratio;
|
|
157
174
|
const color = this.resolveColor();
|
|
158
175
|
const { opacity, metalness, roughness, shape } = this._config;
|
|
159
|
-
const geometry = shape === '
|
|
160
|
-
? this.
|
|
161
|
-
:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
176
|
+
const geometry = shape === 'plane'
|
|
177
|
+
? this.buildPlaneGeometry(w, h)
|
|
178
|
+
: shape === 'hexagon'
|
|
179
|
+
? this.buildHexagonGeometry(w, depth, h)
|
|
180
|
+
: this.buildBoxGeometry(w, depth, h);
|
|
181
|
+
const isPlane = shape === 'plane';
|
|
182
|
+
const material = isPlane
|
|
183
|
+
? new THREE.MeshBasicMaterial({
|
|
184
|
+
color,
|
|
185
|
+
opacity,
|
|
186
|
+
transparent: opacity < 1,
|
|
187
|
+
side: THREE.DoubleSide,
|
|
188
|
+
})
|
|
189
|
+
: new THREE.MeshStandardMaterial(Object.assign({ color,
|
|
190
|
+
metalness,
|
|
191
|
+
roughness,
|
|
192
|
+
opacity, transparent: opacity < 1, side: opacity < 1 ? THREE.DoubleSide : THREE.FrontSide }, (this._config.emissiveIntensity ? { emissive: color, emissiveIntensity: this._config.emissiveIntensity } : {})));
|
|
171
193
|
// z-fighting 방지: 바닥면(floor grid)보다 항상 앞에 렌더링
|
|
172
194
|
material.polygonOffset = true;
|
|
173
195
|
material.polygonOffsetFactor = -1;
|
|
174
196
|
material.polygonOffsetUnit = -1;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
// BoxGeometry material groups: +x(0), -x(1), +y(2), -y(3), +z(4), -z(5)
|
|
198
|
+
let meshMaterial = material;
|
|
199
|
+
if (shape === 'box') {
|
|
200
|
+
if (this._config.openTop) {
|
|
201
|
+
// 상단면 제거 → 5면 개방 박스
|
|
202
|
+
const invisible = new THREE.MeshBasicMaterial({ visible: false });
|
|
203
|
+
meshMaterial = [material, material, invisible, material, material, material];
|
|
204
|
+
}
|
|
205
|
+
else if (this._config.texturedTop) {
|
|
206
|
+
// 상판에 2D 렌더링 텍스처
|
|
207
|
+
const topMat = this.buildTopTexture(w, h);
|
|
208
|
+
meshMaterial = [material, material, topMat, material, material, material];
|
|
209
|
+
}
|
|
210
|
+
else if (this._config.matteTop) {
|
|
211
|
+
// 상판만 무반사
|
|
212
|
+
const matte = new THREE.MeshBasicMaterial({ color, opacity, transparent: opacity < 1 });
|
|
213
|
+
meshMaterial = [material, material, matte, material, material, material];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this._ownMesh = new THREE.Mesh(geometry, meshMaterial);
|
|
217
|
+
// sizeRatio < 1이면 부모 중심에 배치 (rawDepth/2), plane은 살짝 띄움
|
|
218
|
+
this._ownMesh.position.y = isPlane ? 1 : ratio < 1 ? rawDepth / 2 : depth / 2;
|
|
219
|
+
this._ownMesh.castShadow = !isPlane;
|
|
220
|
+
this._ownMesh.receiveShadow = true;
|
|
221
|
+
this.object3d.add(this._ownMesh);
|
|
222
|
+
}
|
|
183
223
|
onchange(after, before) {
|
|
184
224
|
if ('width' in after ||
|
|
185
225
|
'height' in after ||
|
|
186
226
|
'depth' in after ||
|
|
187
227
|
'fillStyle' in after ||
|
|
188
228
|
'strokeStyle' in after ||
|
|
189
|
-
'id' in after
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
'id' in after ||
|
|
230
|
+
'EMPTYTYPE' in after ||
|
|
231
|
+
'CARRIERSTATUS' in after) {
|
|
232
|
+
// 연속 setState에 의한 다중 rebuild 방지: 한 프레임에 한 번만 실행
|
|
233
|
+
if (!this._updatePending) {
|
|
234
|
+
this._updatePending = true;
|
|
235
|
+
queueMicrotask(() => {
|
|
236
|
+
this._updatePending = false;
|
|
237
|
+
this.update();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
192
240
|
return;
|
|
193
241
|
}
|
|
194
242
|
super.onchange(after, before);
|
|
195
243
|
}
|
|
196
|
-
|
|
244
|
+
/**
|
|
245
|
+
* 상판 텍스처: 컴포넌트의 2D render()를 캔버스에 그려서 텍스처로 사용
|
|
246
|
+
*/
|
|
247
|
+
buildTopTexture(w, h) {
|
|
248
|
+
var _a;
|
|
249
|
+
const scale = 4;
|
|
250
|
+
const cw = this.nextPow2(w * scale);
|
|
251
|
+
const ch = this.nextPow2(h * scale);
|
|
252
|
+
const canvas = document.createElement('canvas');
|
|
253
|
+
canvas.width = cw;
|
|
254
|
+
canvas.height = ch;
|
|
255
|
+
const ctx = canvas.getContext('2d');
|
|
256
|
+
const statusColor = '#' + this.resolveColor().toString(16).padStart(6, '0');
|
|
257
|
+
const emptyType = ((_a = this.component.state) === null || _a === void 0 ? void 0 : _a.EMPTYTYPE) || '';
|
|
258
|
+
const text = emptyType === 'FULL' ? 'F' : emptyType === 'EMPTY' ? 'E' : emptyType === 'EMPTYEMPTY' ? 'X' : '';
|
|
259
|
+
// 실제 사용 영역 (nextPow2 이전 크기)
|
|
260
|
+
const usedW = w * scale;
|
|
261
|
+
const usedH = h * scale;
|
|
262
|
+
// 바탕: 캐리어 상태 색상 (사용 영역만)
|
|
263
|
+
ctx.fillStyle = statusColor;
|
|
264
|
+
ctx.fillRect(0, 0, usedW, usedH);
|
|
265
|
+
// 텍스트 (사용 영역 중앙)
|
|
266
|
+
if (text) {
|
|
267
|
+
const fontSize = Math.round(Math.min(usedW, usedH) * 0.7);
|
|
268
|
+
ctx.font = `bold ${fontSize}px Arial`;
|
|
269
|
+
ctx.textAlign = 'center';
|
|
270
|
+
ctx.textBaseline = 'middle';
|
|
271
|
+
ctx.fillStyle = '#000000';
|
|
272
|
+
ctx.fillText(text, usedW / 2, usedH / 2);
|
|
273
|
+
}
|
|
274
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
275
|
+
texture.offset.set(0, 1 - usedH / ch);
|
|
276
|
+
texture.repeat.set(usedW / cw, usedH / ch);
|
|
277
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
278
|
+
return new THREE.MeshBasicMaterial({
|
|
279
|
+
map: texture,
|
|
280
|
+
transparent: true,
|
|
281
|
+
side: THREE.DoubleSide,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
nextPow2(n) {
|
|
285
|
+
if (!(n > 0))
|
|
286
|
+
return 1;
|
|
287
|
+
let v = Math.ceil(n);
|
|
288
|
+
v--;
|
|
289
|
+
v |= v >> 1;
|
|
290
|
+
v |= v >> 2;
|
|
291
|
+
v |= v >> 4;
|
|
292
|
+
v |= v >> 8;
|
|
293
|
+
v |= v >> 16;
|
|
294
|
+
return v + 1;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 자신의 mesh만 제거 후 rebuild.
|
|
298
|
+
* 기본 update()는 clear()로 object3d의 모든 children을 제거하는데,
|
|
299
|
+
* 컨테이너(Port 등)의 경우 자식 컴포넌트(Carrier 등)의 3D 오브젝트도 함께 제거됨.
|
|
300
|
+
* 이를 방지하기 위해 자신이 생성한 mesh만 제거한다.
|
|
301
|
+
*/
|
|
302
|
+
update() {
|
|
303
|
+
// 자신의 mesh만 제거 (자식 컴포넌트 3D 오브젝트 보존)
|
|
304
|
+
if (this._ownMesh) {
|
|
305
|
+
this.object3d.remove(this._ownMesh);
|
|
306
|
+
disposeObject3D(this._ownMesh);
|
|
307
|
+
this._ownMesh = undefined;
|
|
308
|
+
}
|
|
309
|
+
this.build();
|
|
310
|
+
this.updateTransform();
|
|
311
|
+
this.updateAlpha();
|
|
312
|
+
this.updateHidden();
|
|
313
|
+
}
|
|
314
|
+
// RealObjectGroup 기본 동작 비활성화
|
|
197
315
|
updateDimension() { }
|
|
198
|
-
updateAlpha() {
|
|
316
|
+
updateAlpha() {
|
|
317
|
+
const state = this.component.state;
|
|
318
|
+
// alpha가 명시적으로 설정되어 있으면 그 값을 사용, 없으면 config 기본값 적용
|
|
319
|
+
const effective = ('alpha' in state) ? state.alpha : this._config.opacity;
|
|
320
|
+
this.object3d.traverse((o) => {
|
|
321
|
+
if (!o.material)
|
|
322
|
+
return;
|
|
323
|
+
const materials = Array.isArray(o.material) ? o.material : [o.material];
|
|
324
|
+
for (const m of materials) {
|
|
325
|
+
if (!m.visible)
|
|
326
|
+
continue; // openTop invisible face 무시
|
|
327
|
+
if (m.map)
|
|
328
|
+
continue; // 텍스처 material은 자체 transparent 유지
|
|
329
|
+
m.opacity = effective;
|
|
330
|
+
m.transparent = effective < 1;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
199
334
|
}
|
|
200
335
|
// ── 팩토리 등록 헬퍼 ─────────────────────────────────────
|
|
201
336
|
function register(type, overrides = {}) {
|
|
@@ -207,82 +342,469 @@ function register(type, overrides = {}) {
|
|
|
207
342
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
208
343
|
register('Equipment', {
|
|
209
344
|
defaultDepth: 3 * TRANSPORT_SURFACE_HEIGHT,
|
|
210
|
-
opacity: 0.
|
|
345
|
+
opacity: 0.75,
|
|
211
346
|
metalness: 0.4,
|
|
212
347
|
roughness: 0.6,
|
|
213
348
|
defaultColor: 0x8899aa,
|
|
214
349
|
});
|
|
215
350
|
register('Buffer', {
|
|
216
351
|
defaultDepth: TRANSPORT_SURFACE_HEIGHT,
|
|
217
|
-
opacity: 0
|
|
352
|
+
opacity: 1.0,
|
|
218
353
|
metalness: 0.3,
|
|
219
354
|
roughness: 0.7,
|
|
220
355
|
defaultColor: 0x99aa88,
|
|
221
356
|
shape: 'hexagon',
|
|
222
357
|
});
|
|
223
358
|
register('AGVLine', {
|
|
224
|
-
depthRatio: 0
|
|
225
|
-
|
|
226
|
-
roughness: 0.3,
|
|
227
|
-
defaultColor: 0x888899,
|
|
228
|
-
});
|
|
229
|
-
register('OHTLine', {
|
|
230
|
-
depthRatio: 0.12,
|
|
359
|
+
depthRatio: 0,
|
|
360
|
+
defaultDepth: 0.1,
|
|
231
361
|
metalness: 0.6,
|
|
232
362
|
roughness: 0.3,
|
|
233
363
|
defaultColor: 0x888899,
|
|
234
364
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
365
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
366
|
+
// OHTLine — 천장 I-beam 레일
|
|
367
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
368
|
+
const OHT_LINE_RAIL_COLOR = 0xcccccc;
|
|
369
|
+
const OHT_LINE_HEIGHT = 7 * TRANSPORT_SURFACE_HEIGHT; // OHT 상단과 일치
|
|
370
|
+
class OHTLineRealObject3D extends RealObjectGroup {
|
|
371
|
+
get effectiveDepth() { return 0; }
|
|
372
|
+
get syncZPosOffset() { return OHT_LINE_HEIGHT; }
|
|
373
|
+
build() {
|
|
374
|
+
super.build();
|
|
375
|
+
const { width = 10, height = 10 } = this.component.state;
|
|
376
|
+
const w = Math.abs(width);
|
|
377
|
+
const h = Math.abs(height);
|
|
378
|
+
if (!w || !h)
|
|
379
|
+
return;
|
|
380
|
+
// I-beam 치수: 이송 방향 = 긴 변, 폭은 2D의 1/2
|
|
381
|
+
const isVertical = w < h;
|
|
382
|
+
const shortSide = isVertical ? w : h; // 2D 원본 짧은 쪽 폭
|
|
383
|
+
const beltW = shortSide * 0.25; // I-beam 폭: 원본의 1/4
|
|
384
|
+
const beamH = Math.max(beltW * 0.5, 3); // I-beam 전체 높이
|
|
385
|
+
const inset = shortSide * 0.5; // 양끝 여백: 원본 폭의 1/2
|
|
386
|
+
const railLen = (isVertical ? h : w) - inset * 2;
|
|
387
|
+
const flangeH = beamH * 0.2; // 플랜지 두께
|
|
388
|
+
const flangeW = beltW * 0.8; // 플랜지 폭
|
|
389
|
+
const webH = beamH - flangeH * 2; // 웹 높이
|
|
390
|
+
const webW = Math.max(flangeW * 0.25, 1); // 웹 두께
|
|
391
|
+
const mat = new THREE.MeshStandardMaterial({
|
|
392
|
+
color: OHT_LINE_RAIL_COLOR,
|
|
393
|
+
metalness: 0.5,
|
|
394
|
+
roughness: 0.4,
|
|
395
|
+
});
|
|
396
|
+
const geoms = [];
|
|
397
|
+
// 상부 플랜지
|
|
398
|
+
const topFlange = isVertical
|
|
399
|
+
? new THREE.BoxGeometry(flangeW, flangeH, railLen)
|
|
400
|
+
: new THREE.BoxGeometry(railLen, flangeH, flangeW);
|
|
401
|
+
topFlange.translate(0, -flangeH / 2, 0);
|
|
402
|
+
geoms.push(topFlange);
|
|
403
|
+
// 웹 (수직판)
|
|
404
|
+
const web = isVertical
|
|
405
|
+
? new THREE.BoxGeometry(webW, webH, railLen)
|
|
406
|
+
: new THREE.BoxGeometry(railLen, webH, webW);
|
|
407
|
+
web.translate(0, -flangeH - webH / 2, 0);
|
|
408
|
+
geoms.push(web);
|
|
409
|
+
// 하부 플랜지
|
|
410
|
+
const botFlange = isVertical
|
|
411
|
+
? new THREE.BoxGeometry(flangeW, flangeH, railLen)
|
|
412
|
+
: new THREE.BoxGeometry(railLen, flangeH, flangeW);
|
|
413
|
+
botFlange.translate(0, -flangeH - webH - flangeH / 2, 0);
|
|
414
|
+
geoms.push(botFlange);
|
|
415
|
+
this._mesh = new THREE.Mesh(BufferGeometryUtils.mergeGeometries(geoms), mat);
|
|
416
|
+
this._mesh.castShadow = true;
|
|
417
|
+
this.object3d.add(this._mesh);
|
|
418
|
+
}
|
|
419
|
+
update() {
|
|
420
|
+
if (this._mesh) {
|
|
421
|
+
this.object3d.remove(this._mesh);
|
|
422
|
+
this._mesh.geometry.dispose();
|
|
423
|
+
this._mesh.material.dispose();
|
|
424
|
+
this._mesh = undefined;
|
|
425
|
+
}
|
|
426
|
+
this.build();
|
|
427
|
+
this.updateTransform();
|
|
428
|
+
this.updateHidden();
|
|
429
|
+
}
|
|
430
|
+
onchange(after, before) {
|
|
431
|
+
if ('width' in after || 'height' in after || 'depth' in after ||
|
|
432
|
+
'fillStyle' in after || 'strokeStyle' in after) {
|
|
433
|
+
this.update();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
super.onchange(after, before);
|
|
437
|
+
}
|
|
438
|
+
updateDimension() { }
|
|
439
|
+
updateAlpha() { }
|
|
440
|
+
}
|
|
441
|
+
registerRealObjectFactory('OHTLine', (component) => new OHTLineRealObject3D(component));
|
|
442
|
+
// STOCKER: 벽면에 용량 게이지 바를 포함하는 커스텀 3D 오브젝트
|
|
443
|
+
const STOCKER_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { defaultDepth: 5 * TRANSPORT_SURFACE_HEIGHT, opacity: 0.75, metalness: 0.3, roughness: 0.6, defaultColor: 0x8899bb });
|
|
444
|
+
class StockerRealObject3D extends MCSRealObject3D {
|
|
445
|
+
constructor(component) {
|
|
446
|
+
super(component, STOCKER_CONFIG);
|
|
447
|
+
}
|
|
448
|
+
build() {
|
|
449
|
+
super.build();
|
|
450
|
+
this.buildGauge();
|
|
451
|
+
}
|
|
452
|
+
buildGauge() {
|
|
453
|
+
var _a, _b;
|
|
454
|
+
const comp = this.component;
|
|
455
|
+
const data = comp.data || {};
|
|
456
|
+
const gaugeMode = ((_b = (_a = comp.root) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.stockerGaugeMode) || 'capacity';
|
|
457
|
+
const gauge = this.resolveGauge(data, gaugeMode);
|
|
458
|
+
// 테두리는 스토커의 상태 색상 (machine status)
|
|
459
|
+
const statusColor = comp.statusColor;
|
|
460
|
+
if (statusColor && statusColor.toUpperCase() !== '#F0F0F0') {
|
|
461
|
+
gauge.borderColor = statusColor;
|
|
462
|
+
}
|
|
463
|
+
const { w, h } = this.absSize;
|
|
464
|
+
const depth = this.effectiveDepth;
|
|
465
|
+
const isWidthLonger = w >= h;
|
|
466
|
+
// 게이지 캔버스 (고해상도)
|
|
467
|
+
const barW = 130;
|
|
468
|
+
const barH = 26;
|
|
469
|
+
const scale = 4;
|
|
470
|
+
const canvasW = barW * scale;
|
|
471
|
+
const canvasH = barH * scale;
|
|
472
|
+
const canvas = document.createElement('canvas');
|
|
473
|
+
canvas.width = canvasW;
|
|
474
|
+
canvas.height = canvasH;
|
|
475
|
+
const ctx = canvas.getContext('2d');
|
|
476
|
+
const pad = 12;
|
|
477
|
+
// 배경 + 테두리
|
|
478
|
+
ctx.fillStyle = '#dddddd';
|
|
479
|
+
ctx.fillRect(0, 0, canvasW, canvasH);
|
|
480
|
+
ctx.strokeStyle = gauge.borderColor;
|
|
481
|
+
ctx.lineWidth = 12;
|
|
482
|
+
ctx.strokeRect(6, 6, canvasW - 12, canvasH - 12);
|
|
483
|
+
// 세그먼트 채움 (왼→오)
|
|
484
|
+
const innerW = canvasW - pad * 2;
|
|
485
|
+
const innerH = canvasH - pad * 2;
|
|
486
|
+
let x = pad;
|
|
487
|
+
for (const seg of gauge.segments) {
|
|
488
|
+
const segW = Math.max(innerW * seg.ratio, seg.ratio > 0 ? 1 : 0);
|
|
489
|
+
if (segW <= 0)
|
|
490
|
+
continue;
|
|
491
|
+
ctx.fillStyle = seg.color;
|
|
492
|
+
ctx.fillRect(x, pad, segW, innerH);
|
|
493
|
+
x += segW;
|
|
494
|
+
}
|
|
495
|
+
// 텍스트
|
|
496
|
+
ctx.font = `bold ${10 * scale}px sans-serif`;
|
|
497
|
+
ctx.textAlign = 'center';
|
|
498
|
+
ctx.textBaseline = 'middle';
|
|
499
|
+
ctx.fillStyle = '#333333';
|
|
500
|
+
ctx.fillText(gauge.label, canvasW / 2, canvasH / 2);
|
|
501
|
+
// 텍스처 → Plane
|
|
502
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
503
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
504
|
+
const geo = new THREE.PlaneGeometry(barW, barH);
|
|
505
|
+
geo.rotateX(-Math.PI / 2); // 수평 (지붕 위)
|
|
506
|
+
if (!isWidthLonger)
|
|
507
|
+
geo.rotateY(Math.PI / 2);
|
|
508
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
509
|
+
map: texture,
|
|
510
|
+
transparent: true,
|
|
511
|
+
side: THREE.DoubleSide,
|
|
512
|
+
});
|
|
513
|
+
mat.polygonOffset = true;
|
|
514
|
+
mat.polygonOffsetFactor = -2;
|
|
515
|
+
mat.polygonOffsetUnit = -2;
|
|
516
|
+
this.gaugeMesh = new THREE.Mesh(geo, mat);
|
|
517
|
+
this.gaugeMesh.position.set(0, depth + 0.5, 0);
|
|
518
|
+
this.object3d.add(this.gaugeMesh);
|
|
519
|
+
}
|
|
520
|
+
resolveGauge(data, mode) {
|
|
521
|
+
var _a, _b;
|
|
522
|
+
switch (mode) {
|
|
523
|
+
case 'carrier': {
|
|
524
|
+
const full = Number(data.FULL_CNT) || 0;
|
|
525
|
+
const empty = Number(data.EMPTY_CNT) || 0;
|
|
526
|
+
const emptyEmpty = Number(data.EMPTYEMPTY_CNT) || 0;
|
|
527
|
+
const total = full + empty + emptyEmpty;
|
|
528
|
+
const segments = total > 0
|
|
529
|
+
? [
|
|
530
|
+
{ ratio: full / total, color: '#4AA7FE' },
|
|
531
|
+
{ ratio: empty / total, color: '#8BDA5B' },
|
|
532
|
+
{ ratio: emptyEmpty / total, color: '#019D59' },
|
|
533
|
+
]
|
|
534
|
+
: [{ ratio: 1, color: '#dddddd' }];
|
|
535
|
+
return {
|
|
536
|
+
segments,
|
|
537
|
+
borderColor: '#4AA7FE',
|
|
538
|
+
label: total > 0 ? `F${full} E${empty} EE${emptyEmpty}` : '',
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
case 'abnormal': {
|
|
542
|
+
const normal = Number(data.CARRIER_NORMAL) || 0;
|
|
543
|
+
const abnormal = Number(data.CARRIER_ABNORMAL) || 0;
|
|
544
|
+
const total = normal + abnormal;
|
|
545
|
+
const segments = total > 0
|
|
546
|
+
? [
|
|
547
|
+
{ ratio: normal / total, color: '#8BDA5B' },
|
|
548
|
+
{ ratio: abnormal / total, color: '#F4B4B4' },
|
|
549
|
+
]
|
|
550
|
+
: [{ ratio: 1, color: '#dddddd' }];
|
|
551
|
+
return {
|
|
552
|
+
segments,
|
|
553
|
+
borderColor: abnormal > 0 ? '#F4B4B4' : '#8BDA5B',
|
|
554
|
+
label: total > 0 ? `N${normal} A${abnormal}` : '',
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
default: { // capacity
|
|
558
|
+
const cur = Number(data.CURRENTCAPACITY) || 0;
|
|
559
|
+
const max = Number(data.MAXCAPACITY) || 100;
|
|
560
|
+
const fullup = data.FULLUP === 'T';
|
|
561
|
+
const usage = Math.round((cur / max) * 100);
|
|
562
|
+
const legend = ((_b = (_a = this.component.root) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.LEGEND_CAPACITY) || LEGEND_CAPACITY;
|
|
563
|
+
const color = fullup ? 'red' : (getVaueOnRanges(usage, legend) || '#44aa44');
|
|
564
|
+
return {
|
|
565
|
+
segments: [{ ratio: usage / 100, color }],
|
|
566
|
+
borderColor: color,
|
|
567
|
+
label: `${usage}%`,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
onchange(after, before) {
|
|
573
|
+
if ('data' in after) {
|
|
574
|
+
this.clearGauge();
|
|
575
|
+
this.buildGauge();
|
|
576
|
+
}
|
|
577
|
+
super.onchange(after, before);
|
|
578
|
+
}
|
|
579
|
+
clear() {
|
|
580
|
+
this.clearGauge();
|
|
581
|
+
return super.clear();
|
|
582
|
+
}
|
|
583
|
+
clearGauge() {
|
|
584
|
+
var _a, _b;
|
|
585
|
+
if (this.gaugeMesh) {
|
|
586
|
+
this.object3d.remove(this.gaugeMesh);
|
|
587
|
+
(_a = this.gaugeMesh.geometry) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
588
|
+
const mat = this.gaugeMesh.material;
|
|
589
|
+
(_b = mat === null || mat === void 0 ? void 0 : mat.map) === null || _b === void 0 ? void 0 : _b.dispose();
|
|
590
|
+
mat === null || mat === void 0 ? void 0 : mat.dispose();
|
|
591
|
+
this.gaugeMesh = undefined;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
registerRealObjectFactory('STOCKER', (component) => new StockerRealObject3D(component));
|
|
242
596
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
243
597
|
// Unit (이동체 · 홀더) — 낮은 depth, 불투명
|
|
244
598
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
opacity: 0.5,
|
|
249
|
-
metalness: 0.4,
|
|
250
|
-
roughness: 0.6,
|
|
251
|
-
defaultColor: 0xb0a090,
|
|
252
|
-
});
|
|
599
|
+
// Port: 바닥판 (PlaneGeometry)
|
|
600
|
+
const PORT_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { depthRatio: 0.01, defaultZPosFromParent: true, opacity: 0.1, metalness: 0.3, roughness: 0.8, defaultColor: 0xb0a090, shape: 'plane' });
|
|
601
|
+
registerRealObjectFactory('Port', (component) => new MCSRealObject3D(component, PORT_CONFIG));
|
|
253
602
|
register('Shelf', {
|
|
254
603
|
depthRatio: 0.25,
|
|
255
604
|
metalness: 0.3,
|
|
256
605
|
roughness: 0.7,
|
|
257
606
|
defaultColor: 0xaabbcc,
|
|
258
607
|
});
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
608
|
+
// Carrier: CARRIERSTATUS에 따른 3D 애니메이션 지원
|
|
609
|
+
const CARRIER_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { depthRatio: 0.5, metalness: 0, roughness: 1, defaultColor: 0x66bb66, isVisible: (c) => !!c.state.id, sizeRatio: 0.8, texturedTop: true });
|
|
610
|
+
class CarrierRealObject3D extends MCSRealObject3D {
|
|
611
|
+
constructor(component) {
|
|
612
|
+
super(component, CARRIER_CONFIG);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
registerRealObjectFactory('Carrier', (component) => new CarrierRealObject3D(component));
|
|
267
616
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
268
617
|
// Gauge (용량 게이지) — 얇은 바
|
|
269
618
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
619
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
620
|
+
// Gauge (용량 게이지) — 2D 렌더링을 Plane 텍스처로 3D 표시
|
|
621
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
622
|
+
// Gauge 컴포넌트 (StockerCapacityBar, ZoneCapacityBar, MCSGaugeCapacityBar)는
|
|
623
|
+
// 3D에서 표시하지 않음. 대신 부모(STOCKER 등)의 3D 모델에서 직접 게이지를 렌더링.
|
|
624
|
+
registerRealObjectFactory('StockerCapacityBar', () => null);
|
|
625
|
+
registerRealObjectFactory('MCSGaugeCapacityBar', () => null);
|
|
626
|
+
registerRealObjectFactory('ZoneCapacityBar', () => null);
|
|
627
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
628
|
+
// PortFlow — 2D render를 Plane 텍스처로 3D 표시
|
|
629
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
630
|
+
class PortFlowRealObject3D extends RealObjectGroup {
|
|
631
|
+
get effectiveDepth() { return 0; }
|
|
632
|
+
get syncZPosOffset() {
|
|
633
|
+
var _a;
|
|
634
|
+
// 부모 Port/Conveyor의 zPos와 동일하게
|
|
635
|
+
const parent = this.component.parentObject;
|
|
636
|
+
if (((_a = parent === null || parent === void 0 ? void 0 : parent.state) === null || _a === void 0 ? void 0 : _a.zPos) != null)
|
|
637
|
+
return parent.state.zPos;
|
|
638
|
+
return TRANSPORT_SURFACE_HEIGHT;
|
|
639
|
+
}
|
|
640
|
+
build() {
|
|
641
|
+
super.build();
|
|
642
|
+
const { width = 10, height = 10 } = this.component.state;
|
|
643
|
+
const w = Math.abs(width);
|
|
644
|
+
const h = Math.abs(height);
|
|
645
|
+
if (!w || !h)
|
|
646
|
+
return;
|
|
647
|
+
// 2D render를 캔버스에 캡처
|
|
648
|
+
// 화살표 날개 + 텍스트 영역이 bounds 밖으로 나가므로 전체 영역 계산
|
|
649
|
+
const { left, top } = this.component.bounds;
|
|
650
|
+
const { fontSize = 24, labelPosition = 'top' } = this.component.state;
|
|
651
|
+
const arrowPad = Math.min(w, h) * 0.4;
|
|
652
|
+
// 텍스트 영역 확장
|
|
653
|
+
let padLeft = arrowPad, padRight = arrowPad, padTop = arrowPad, padBottom = arrowPad;
|
|
654
|
+
if (this.component.hasTextProperty) {
|
|
655
|
+
switch (labelPosition) {
|
|
656
|
+
case 'top':
|
|
657
|
+
padTop = Math.max(padTop, fontSize + 4);
|
|
658
|
+
break;
|
|
659
|
+
case 'bottom':
|
|
660
|
+
padBottom = Math.max(padBottom, fontSize + 4);
|
|
661
|
+
break;
|
|
662
|
+
case 'left':
|
|
663
|
+
padLeft = Math.max(padLeft, fontSize * 4 + 4);
|
|
664
|
+
break;
|
|
665
|
+
case 'right':
|
|
666
|
+
padRight = Math.max(padRight, fontSize * 4 + 4);
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const totalW = w + padLeft + padRight;
|
|
671
|
+
const totalH = h + padTop + padBottom;
|
|
672
|
+
const scale = 2;
|
|
673
|
+
const canvas = document.createElement('canvas');
|
|
674
|
+
canvas.width = totalW * scale;
|
|
675
|
+
canvas.height = totalH * scale;
|
|
676
|
+
const ctx = canvas.getContext('2d');
|
|
677
|
+
ctx.save();
|
|
678
|
+
ctx.scale(scale, scale);
|
|
679
|
+
ctx.translate(-left + padLeft, -top + padTop);
|
|
680
|
+
try {
|
|
681
|
+
// 화살표 렌더
|
|
682
|
+
this.component.render(ctx);
|
|
683
|
+
ctx.fillStyle = this.component.state.strokeStyle || '#666666';
|
|
684
|
+
ctx.fill();
|
|
685
|
+
// 텍스트 렌더
|
|
686
|
+
if (this.component.hasTextProperty && this.component.drawText) {
|
|
687
|
+
;
|
|
688
|
+
this.component.drawText(ctx);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch ( /* render 실패 시 빈 텍스처 */_a) { /* render 실패 시 빈 텍스처 */ }
|
|
692
|
+
ctx.restore();
|
|
693
|
+
this._texture = new THREE.CanvasTexture(canvas);
|
|
694
|
+
this._texture.colorSpace = THREE.SRGBColorSpace;
|
|
695
|
+
const geo = new THREE.PlaneGeometry(totalW, totalH);
|
|
696
|
+
geo.rotateX(-Math.PI / 2);
|
|
697
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
698
|
+
map: this._texture,
|
|
699
|
+
alphaTest: 0.1,
|
|
700
|
+
side: THREE.DoubleSide,
|
|
701
|
+
});
|
|
702
|
+
this._mesh = new THREE.Mesh(geo, mat);
|
|
703
|
+
// 비대칭 패딩 보정: 플레인 센터를 컴포넌트 bounds 센터에 맞춤
|
|
704
|
+
this._mesh.position.x = (padRight - padLeft) / 2;
|
|
705
|
+
this._mesh.position.y = 1;
|
|
706
|
+
this._mesh.position.z = (padBottom - padTop) / 2;
|
|
707
|
+
this.object3d.add(this._mesh);
|
|
708
|
+
}
|
|
709
|
+
update() {
|
|
710
|
+
var _a;
|
|
711
|
+
if (this._mesh) {
|
|
712
|
+
this.object3d.remove(this._mesh);
|
|
713
|
+
this._mesh.geometry.dispose();
|
|
714
|
+
this._mesh.material.dispose();
|
|
715
|
+
(_a = this._texture) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
716
|
+
this._mesh = undefined;
|
|
717
|
+
this._texture = undefined;
|
|
718
|
+
}
|
|
719
|
+
this.build();
|
|
720
|
+
this.updateTransform();
|
|
721
|
+
this.updateHidden();
|
|
722
|
+
}
|
|
723
|
+
onchange(after, before) {
|
|
724
|
+
if ('width' in after || 'height' in after || 'orientation' in after ||
|
|
725
|
+
'direction' in after || 'strokeStyle' in after || 'INOUTTYPE' in after) {
|
|
726
|
+
this.update();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
super.onchange(after, before);
|
|
730
|
+
}
|
|
731
|
+
updateDimension() { }
|
|
732
|
+
updateAlpha() { }
|
|
733
|
+
}
|
|
734
|
+
registerRealObjectFactory('PortFlow', (component) => new PortFlowRealObject3D(component));
|
|
735
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
736
|
+
// Node — 2D render를 바닥 Plane 텍스처로 3D 표시
|
|
737
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
738
|
+
class NodeRealObject3D extends RealObjectGroup {
|
|
739
|
+
get effectiveDepth() { return 0; }
|
|
740
|
+
get syncZPosOffset() { return 0; }
|
|
741
|
+
build() {
|
|
742
|
+
super.build();
|
|
743
|
+
const { cx, cy } = this.component.state;
|
|
744
|
+
const radius = 8;
|
|
745
|
+
const size = radius * 2;
|
|
746
|
+
const scale = 2;
|
|
747
|
+
const pad = 2;
|
|
748
|
+
const canvas = document.createElement('canvas');
|
|
749
|
+
canvas.width = (size + pad * 2) * scale;
|
|
750
|
+
canvas.height = (size + pad * 2) * scale;
|
|
751
|
+
const ctx = canvas.getContext('2d');
|
|
752
|
+
ctx.save();
|
|
753
|
+
ctx.scale(scale, scale);
|
|
754
|
+
ctx.translate(-cx + radius + pad, -cy + radius + pad);
|
|
755
|
+
try {
|
|
756
|
+
this.component.render(ctx);
|
|
757
|
+
const { fillStyle = '#888888' } = this.component.state;
|
|
758
|
+
ctx.fillStyle = typeof fillStyle === 'string' ? fillStyle : '#888888';
|
|
759
|
+
ctx.fill();
|
|
760
|
+
const { strokeStyle } = this.component.state;
|
|
761
|
+
if (strokeStyle && typeof strokeStyle === 'string') {
|
|
762
|
+
ctx.strokeStyle = strokeStyle;
|
|
763
|
+
ctx.lineWidth = 1;
|
|
764
|
+
ctx.stroke();
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch ( /* render 실패 시 빈 텍스처 */_a) { /* render 실패 시 빈 텍스처 */ }
|
|
768
|
+
ctx.restore();
|
|
769
|
+
this._texture = new THREE.CanvasTexture(canvas);
|
|
770
|
+
this._texture.colorSpace = THREE.SRGBColorSpace;
|
|
771
|
+
const geo = new THREE.PlaneGeometry(size + pad * 2, size + pad * 2);
|
|
772
|
+
geo.rotateX(-Math.PI / 2);
|
|
773
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
774
|
+
map: this._texture,
|
|
775
|
+
alphaTest: 0.1,
|
|
776
|
+
side: THREE.DoubleSide,
|
|
777
|
+
});
|
|
778
|
+
this._mesh = new THREE.Mesh(geo, mat);
|
|
779
|
+
this._mesh.position.y = 1;
|
|
780
|
+
this.object3d.add(this._mesh);
|
|
781
|
+
}
|
|
782
|
+
update() {
|
|
783
|
+
var _a;
|
|
784
|
+
if (this._mesh) {
|
|
785
|
+
this.object3d.remove(this._mesh);
|
|
786
|
+
this._mesh.geometry.dispose();
|
|
787
|
+
this._mesh.material.dispose();
|
|
788
|
+
(_a = this._texture) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
789
|
+
this._mesh = undefined;
|
|
790
|
+
this._texture = undefined;
|
|
791
|
+
}
|
|
792
|
+
this.build();
|
|
793
|
+
this.updateTransform();
|
|
794
|
+
this.updateHidden();
|
|
795
|
+
}
|
|
796
|
+
onchange(after, before) {
|
|
797
|
+
if ('fillStyle' in after || 'strokeStyle' in after) {
|
|
798
|
+
this.update();
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
super.onchange(after, before);
|
|
802
|
+
}
|
|
803
|
+
updateDimension() { }
|
|
804
|
+
updateAlpha() { }
|
|
805
|
+
}
|
|
806
|
+
registerRealObjectFactory('Node', (component) => new NodeRealObject3D(component));
|
|
807
|
+
// Scanner — 이송 표면 높이의 기둥 형태
|
|
808
|
+
const SCANNER_CONFIG = Object.assign(Object.assign({}, DEFAULTS), { defaultDepth: TRANSPORT_SURFACE_HEIGHT * 0.2, defaultZPosFromParent: true, opacity: 1, metalness: 0.3, roughness: 0.7, defaultColor: 0x888888, shape: 'box' });
|
|
809
|
+
registerRealObjectFactory('Scanner', (component) => new MCSRealObject3D(component, SCANNER_CONFIG));
|
|
288
810
|
//# sourceMappingURL=machine-3d.js.map
|