@fmsim/machine 2.0.0-beta.27 → 2.0.0-beta.28
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 +1 -1
- package/dist/carrier-manager.js.map +1 -1
- package/dist/factories/machine-3d.js +16 -13
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/carrier-manager.ts +1 -1
- package/src/factories/machine-3d.ts +23 -11
- package/dist/factories/scanner-3d.js +0 -139
- package/dist/factories/scanner-3d.js.map +0 -1
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@fmsim/machine",
|
|
3
3
|
"description": "Layout View를 구성하는 설비(machine)들과 유닛을 구현한 모듈입니다.",
|
|
4
4
|
"author": "heartyoh",
|
|
5
|
-
"version": "2.0.0-beta.
|
|
5
|
+
"version": "2.0.0-beta.28",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"things-scene": true,
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"prettier --write"
|
|
56
56
|
]
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "d707acf52f4d81773f7594b83dddfc6ea465214f"
|
|
59
59
|
}
|
package/src/carrier-manager.ts
CHANGED
|
@@ -57,7 +57,7 @@ export default class CarrierManager {
|
|
|
57
57
|
) as unknown as Carrier
|
|
58
58
|
|
|
59
59
|
this.carriers.set(afterName, carrier)
|
|
60
|
-
holder.addComponent(carrier, true)
|
|
60
|
+
holder.addComponent(carrier, { silent: true })
|
|
61
61
|
} else if (carrier.parent !== holder && (!beforeName && afterName)) {
|
|
62
62
|
// CARRIERNAME이 새로 나타남 (이전엔 없었음) → 다른 holder로 이동
|
|
63
63
|
;(holder as any).reparent(carrier, { animated: true, duration: 2000, ease: 'inout' })
|
|
@@ -91,6 +91,9 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
protected get syncZPosOffset(): number {
|
|
94
|
+
// zPos가 명시되면 그 값을 절대 높이로 사용 (다른 컴포넌트와 동일한 의미)
|
|
95
|
+
if (this.component.state.zPos != null) return 0
|
|
96
|
+
|
|
94
97
|
// zPosOverride: 고정 높이 (OHTLine 등)
|
|
95
98
|
if (this._config.zPosOverride != null) {
|
|
96
99
|
return this._config.zPosOverride
|
|
@@ -99,10 +102,14 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
99
102
|
if (this._config.defaultZPosFromParent) {
|
|
100
103
|
return TRANSPORT_SURFACE_HEIGHT
|
|
101
104
|
}
|
|
102
|
-
// Carrier:
|
|
105
|
+
// Carrier: 부모 유형에 따라 배치 위치 결정
|
|
103
106
|
if (this._config.sizeRatio && this._config.sizeRatio < 1) {
|
|
104
107
|
const parentType = this.component.parent?.state?.type
|
|
105
|
-
|
|
108
|
+
// OHT: 그리퍼 아래에 매달림 (캐리어 상단이 OHT 하단에 붙음)
|
|
109
|
+
if (parentType === 'OHT') {
|
|
110
|
+
return -this.effectiveDepth
|
|
111
|
+
}
|
|
112
|
+
if (parentType === 'Port' || parentType === 'Shelf' || parentType === 'Shuttle') {
|
|
106
113
|
return 0
|
|
107
114
|
}
|
|
108
115
|
return TRANSPORT_SURFACE_HEIGHT
|
|
@@ -395,9 +402,9 @@ class MCSRealObject3D extends RealObjectGroup {
|
|
|
395
402
|
updateDimension() {}
|
|
396
403
|
|
|
397
404
|
updateAlpha() {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
const effective =
|
|
405
|
+
const state = this.component.state
|
|
406
|
+
// alpha가 명시적으로 설정되어 있으면 그 값을 사용, 없으면 config 기본값 적용
|
|
407
|
+
const effective = ('alpha' in state) ? (state.alpha as number) : this._config.opacity
|
|
401
408
|
|
|
402
409
|
this.object3d.traverse((o: any) => {
|
|
403
410
|
if (!o.material) return
|
|
@@ -979,10 +986,15 @@ class NodeRealObject3D extends RealObjectGroup {
|
|
|
979
986
|
|
|
980
987
|
registerRealObjectFactory('Node', (component: Component) => new NodeRealObject3D(component))
|
|
981
988
|
|
|
982
|
-
// Scanner —
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
989
|
+
// Scanner — 이송 표면 높이의 기둥 형태
|
|
990
|
+
const SCANNER_CONFIG: MCS3DConfig = {
|
|
991
|
+
...DEFAULTS,
|
|
992
|
+
defaultDepth: TRANSPORT_SURFACE_HEIGHT * 0.2,
|
|
993
|
+
defaultZPosFromParent: true,
|
|
994
|
+
opacity: 1,
|
|
995
|
+
metalness: 0.3,
|
|
996
|
+
roughness: 0.7,
|
|
997
|
+
defaultColor: 0x888888,
|
|
998
|
+
shape: 'box',
|
|
987
999
|
}
|
|
988
|
-
registerRealObjectFactory('Scanner', (component: Component) => new
|
|
1000
|
+
registerRealObjectFactory('Scanner', (component: Component) => new MCSRealObject3D(component, SCANNER_CONFIG))
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scanner 3D Factory
|
|
3
|
-
*
|
|
4
|
-
* 배터리 제조라인 고정 마운트 바코드/QR 스캐너.
|
|
5
|
-
* 최소 폴리곤: 삼각 기둥(마운트 폴) + 스캐너 헤드(작은 박스) + 스캔 방향 표시.
|
|
6
|
-
*
|
|
7
|
-
* 구조 (바닥 원점, y=0~depth):
|
|
8
|
-
* - 삼각 기둥: 2D 삼각형을 위로 올린 형태 (마운트 기둥 겸 방향 표시)
|
|
9
|
-
* - 스캐너 헤드: 삼각 기둥 꼭짓점(스캔 방향) 상단에 작은 박스
|
|
10
|
-
*/
|
|
11
|
-
import * as THREE from 'three';
|
|
12
|
-
import { RealObjectGroup, registerRealObjectFactory, disposeObject3D } from '@hatiolab/things-scene';
|
|
13
|
-
import { TRANSPORT_SURFACE_HEIGHT } from './machine-3d.js';
|
|
14
|
-
const PILLAR_COLOR = 0x555566;
|
|
15
|
-
const HEAD_COLOR = 0x222233;
|
|
16
|
-
const LENS_COLOR = 0xcc3300;
|
|
17
|
-
export class Scanner3D extends RealObjectGroup {
|
|
18
|
-
constructor() {
|
|
19
|
-
super(...arguments);
|
|
20
|
-
this._ownMeshes = [];
|
|
21
|
-
}
|
|
22
|
-
get effectiveDepth() {
|
|
23
|
-
const { depth } = this.component.state;
|
|
24
|
-
return depth || TRANSPORT_SURFACE_HEIGHT;
|
|
25
|
-
}
|
|
26
|
-
get syncZPosOffset() {
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
resolveColor() {
|
|
30
|
-
const comp = this.component;
|
|
31
|
-
try {
|
|
32
|
-
const sc = comp.statusColor;
|
|
33
|
-
if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent')
|
|
34
|
-
return new THREE.Color(sc).getHex();
|
|
35
|
-
}
|
|
36
|
-
catch ( /* */_a) { /* */ }
|
|
37
|
-
const { fillStyle } = this.component.state;
|
|
38
|
-
if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {
|
|
39
|
-
try {
|
|
40
|
-
return new THREE.Color(fillStyle).getHex();
|
|
41
|
-
}
|
|
42
|
-
catch ( /* */_b) { /* */ }
|
|
43
|
-
}
|
|
44
|
-
return PILLAR_COLOR;
|
|
45
|
-
}
|
|
46
|
-
build() {
|
|
47
|
-
super.build();
|
|
48
|
-
const { width = 10, height = 10 } = this.component.state;
|
|
49
|
-
const w = Math.abs(width);
|
|
50
|
-
const h = Math.abs(height);
|
|
51
|
-
const d = this.effectiveDepth;
|
|
52
|
-
if (!w || !h)
|
|
53
|
-
return;
|
|
54
|
-
const color = this.resolveColor();
|
|
55
|
-
// ── 삼각 기둥 (2D 삼각형을 y축으로 올린 형태) ──
|
|
56
|
-
// 삼각형: (0, -h/2) → (w/2, 0) → (0, h/2) (꼭짓점이 +X 방향 = 스캔 방향)
|
|
57
|
-
const shape = new THREE.Shape();
|
|
58
|
-
shape.moveTo(-w / 2, -h / 2);
|
|
59
|
-
shape.lineTo(w / 2, 0);
|
|
60
|
-
shape.lineTo(-w / 2, h / 2);
|
|
61
|
-
shape.closePath();
|
|
62
|
-
const extrudeSettings = { depth: d, bevelEnabled: false };
|
|
63
|
-
const pillarGeo = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
|
64
|
-
// ExtrudeGeometry: shape은 XY 평면, depth는 +Z로 올라감
|
|
65
|
-
// 3D 좌표계: Y=up이므로 X→X, Y→Z, Z→Y로 회전
|
|
66
|
-
pillarGeo.rotateX(-Math.PI / 2);
|
|
67
|
-
// 이제 shape XY 평면이 XZ가 되고, depth가 +Y로 올라감
|
|
68
|
-
const pillarMat = new THREE.MeshStandardMaterial({
|
|
69
|
-
color,
|
|
70
|
-
metalness: 0.5,
|
|
71
|
-
roughness: 0.5,
|
|
72
|
-
});
|
|
73
|
-
const pillarMesh = new THREE.Mesh(pillarGeo, pillarMat);
|
|
74
|
-
pillarMesh.castShadow = true;
|
|
75
|
-
pillarMesh.receiveShadow = true;
|
|
76
|
-
// ── 스캐너 헤드 (삼각 기둥 꼭짓점 상단, 작은 박스) ──
|
|
77
|
-
const headW = Math.max(w * 0.25, 2);
|
|
78
|
-
const headH = Math.max(d * 0.15, 2);
|
|
79
|
-
const headD = Math.max(h * 0.3, 2);
|
|
80
|
-
const headGeo = new THREE.BoxGeometry(headW, headH, headD);
|
|
81
|
-
const headMat = new THREE.MeshStandardMaterial({
|
|
82
|
-
color: HEAD_COLOR,
|
|
83
|
-
metalness: 0.6,
|
|
84
|
-
roughness: 0.3,
|
|
85
|
-
});
|
|
86
|
-
const headMesh = new THREE.Mesh(headGeo, headMat);
|
|
87
|
-
headMesh.position.set(w / 2 - headW / 2, d + headH / 2, 0);
|
|
88
|
-
headMesh.castShadow = true;
|
|
89
|
-
// ── 렌즈 (스캔 방향 표시, 작은 빨간 디스크) ──
|
|
90
|
-
const lensR = Math.min(headD, headH) * 0.3;
|
|
91
|
-
const lensGeo = new THREE.CylinderGeometry(lensR, lensR, headW * 0.1, 8);
|
|
92
|
-
lensGeo.rotateZ(Math.PI / 2);
|
|
93
|
-
const lensMat = new THREE.MeshStandardMaterial({
|
|
94
|
-
color: LENS_COLOR,
|
|
95
|
-
emissive: LENS_COLOR,
|
|
96
|
-
emissiveIntensity: 0.5,
|
|
97
|
-
metalness: 0.0,
|
|
98
|
-
roughness: 0.3,
|
|
99
|
-
});
|
|
100
|
-
const lensMesh = new THREE.Mesh(lensGeo, lensMat);
|
|
101
|
-
lensMesh.position.set(w / 2 + headW * 0.05, d + headH / 2, 0);
|
|
102
|
-
this._ownMeshes = [pillarMesh, headMesh, lensMesh];
|
|
103
|
-
for (const m of this._ownMeshes)
|
|
104
|
-
this.object3d.add(m);
|
|
105
|
-
}
|
|
106
|
-
updateDimension() { }
|
|
107
|
-
update() {
|
|
108
|
-
for (const m of this._ownMeshes) {
|
|
109
|
-
this.object3d.remove(m);
|
|
110
|
-
disposeObject3D(m);
|
|
111
|
-
}
|
|
112
|
-
this._ownMeshes = [];
|
|
113
|
-
this.build();
|
|
114
|
-
this.updateTransform();
|
|
115
|
-
this.updateAlpha();
|
|
116
|
-
this.updateHidden();
|
|
117
|
-
}
|
|
118
|
-
updateAlpha() {
|
|
119
|
-
const { alpha = 1 } = this.component.state;
|
|
120
|
-
for (const m of this._ownMeshes) {
|
|
121
|
-
const materials = Array.isArray(m.material) ? m.material : [m.material];
|
|
122
|
-
for (const mat of materials) {
|
|
123
|
-
;
|
|
124
|
-
mat.opacity = alpha;
|
|
125
|
-
mat.transparent = alpha < 1;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
onchange(after, before) {
|
|
130
|
-
if ('width' in after || 'height' in after || 'depth' in after ||
|
|
131
|
-
'fillStyle' in after || 'strokeStyle' in after || 'id' in after) {
|
|
132
|
-
this.update();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
super.onchange(after, before);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
registerRealObjectFactory('Scanner', (component) => new Scanner3D(component));
|
|
139
|
-
//# sourceMappingURL=scanner-3d.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scanner-3d.js","sourceRoot":"","sources":["../../src/factories/scanner-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scanner 3D Factory\n *\n * 배터리 제조라인 고정 마운트 바코드/QR 스캐너.\n * 최소 폴리곤: 삼각 기둥(마운트 폴) + 스캐너 헤드(작은 박스) + 스캔 방향 표시.\n *\n * 구조 (바닥 원점, y=0~depth):\n * - 삼각 기둥: 2D 삼각형을 위로 올린 형태 (마운트 기둥 겸 방향 표시)\n * - 스캐너 헤드: 삼각 기둥 꼭짓점(스캔 방향) 상단에 작은 박스\n */\n\nimport * as THREE from 'three'\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 PILLAR_COLOR = 0x555566\nconst HEAD_COLOR = 0x222233\nconst LENS_COLOR = 0xcc3300\n\nexport class Scanner3D extends RealObjectGroup {\n private _ownMeshes: THREE.Mesh[] = []\n\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 try {\n const sc = comp.statusColor\n if (typeof sc === 'string' && sc && sc.toUpperCase() !== '#F0F0F0' && sc !== 'transparent')\n return new THREE.Color(sc).getHex()\n } catch { /* */ }\n\n const { fillStyle } = this.component.state\n if (fillStyle && typeof fillStyle === 'string' && fillStyle !== 'transparent') {\n try { return new THREE.Color(fillStyle).getHex() } catch { /* */ }\n }\n return PILLAR_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 color = this.resolveColor()\n\n // ── 삼각 기둥 (2D 삼각형을 y축으로 올린 형태) ──\n // 삼각형: (0, -h/2) → (w/2, 0) → (0, h/2) (꼭짓점이 +X 방향 = 스캔 방향)\n const shape = new THREE.Shape()\n shape.moveTo(-w / 2, -h / 2)\n shape.lineTo(w / 2, 0)\n shape.lineTo(-w / 2, h / 2)\n shape.closePath()\n\n const extrudeSettings = { depth: d, bevelEnabled: false }\n const pillarGeo = new THREE.ExtrudeGeometry(shape, extrudeSettings)\n // ExtrudeGeometry: shape은 XY 평면, depth는 +Z로 올라감\n // 3D 좌표계: Y=up이므로 X→X, Y→Z, Z→Y로 회전\n pillarGeo.rotateX(-Math.PI / 2)\n // 이제 shape XY 평면이 XZ가 되고, depth가 +Y로 올라감\n\n const pillarMat = new THREE.MeshStandardMaterial({\n color,\n metalness: 0.5,\n roughness: 0.5,\n })\n const pillarMesh = new THREE.Mesh(pillarGeo, pillarMat)\n pillarMesh.castShadow = true\n pillarMesh.receiveShadow = true\n\n // ── 스캐너 헤드 (삼각 기둥 꼭짓점 상단, 작은 박스) ──\n const headW = Math.max(w * 0.25, 2)\n const headH = Math.max(d * 0.15, 2)\n const headD = Math.max(h * 0.3, 2)\n const headGeo = new THREE.BoxGeometry(headW, headH, headD)\n const headMat = new THREE.MeshStandardMaterial({\n color: HEAD_COLOR,\n metalness: 0.6,\n roughness: 0.3,\n })\n const headMesh = new THREE.Mesh(headGeo, headMat)\n headMesh.position.set(w / 2 - headW / 2, d + headH / 2, 0)\n headMesh.castShadow = true\n\n // ── 렌즈 (스캔 방향 표시, 작은 빨간 디스크) ──\n const lensR = Math.min(headD, headH) * 0.3\n const lensGeo = new THREE.CylinderGeometry(lensR, lensR, headW * 0.1, 8)\n lensGeo.rotateZ(Math.PI / 2)\n const lensMat = new THREE.MeshStandardMaterial({\n color: LENS_COLOR,\n emissive: LENS_COLOR,\n emissiveIntensity: 0.5,\n metalness: 0.0,\n roughness: 0.3,\n })\n const lensMesh = new THREE.Mesh(lensGeo, lensMat)\n lensMesh.position.set(w / 2 + headW * 0.05, d + headH / 2, 0)\n\n this._ownMeshes = [pillarMesh, headMesh, lensMesh]\n for (const m of this._ownMeshes) this.object3d.add(m)\n }\n\n updateDimension() {}\n\n update() {\n for (const m of this._ownMeshes) {\n this.object3d.remove(m)\n disposeObject3D(m)\n }\n this._ownMeshes = []\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('Scanner', (component: Component) => new Scanner3D(component))\n"]}
|