@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.
- package/dist/agv.js +6 -2
- package/dist/agv.js.map +1 -1
- package/dist/carrier-manager.js +183 -0
- package/dist/carrier-manager.js.map +1 -0
- package/dist/carrier.js +99 -9
- 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/crane.js +144 -1
- package/dist/crane.js.map +1 -1
- package/dist/factories/agv-3d.js +17 -36
- package/dist/factories/agv-3d.js.map +1 -1
- package/dist/factories/conveyor-3d.js +179 -100
- package/dist/factories/conveyor-3d.js.map +1 -1
- package/dist/factories/crane-3d.js +56 -29
- package/dist/factories/crane-3d.js.map +1 -1
- package/dist/factories/crane-rail-3d.js +14 -18
- package/dist/factories/crane-rail-3d.js.map +1 -1
- package/dist/factories/machine-3d.js +533 -197
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/factories/oht-3d.js +29 -47
- package/dist/factories/oht-3d.js.map +1 -1
- package/dist/factories/shuttle-3d.js +13 -32
- package/dist/factories/shuttle-3d.js.map +1 -1
- package/dist/factories/z-priority-defaults.js +33 -0
- package/dist/factories/z-priority-defaults.js.map +1 -0
- package/dist/factories/z-priority.js +54 -0
- package/dist/factories/z-priority.js.map +1 -0
- package/dist/features/conveyor-mixin.js.map +1 -1
- package/dist/features/mcs-status-default.js +15 -1
- package/dist/features/mcs-status-default.js.map +1 -1
- package/dist/features/mcs-status-mixin.js +22 -0
- package/dist/features/mcs-status-mixin.js.map +1 -1
- package/dist/features/mcs-zone-mixin.js.map +1 -1
- package/dist/features/parent-object-mixin.js +15 -7
- package/dist/features/parent-object-mixin.js.map +1 -1
- package/dist/floating-container.js +5 -0
- package/dist/floating-container.js.map +1 -1
- package/dist/index.js +52 -34
- package/dist/index.js.map +1 -1
- package/dist/mcs-carrier-holder.js +56 -22
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/mcs-machine.js +20 -0
- package/dist/mcs-machine.js.map +1 -1
- package/dist/mcs-unit.js +17 -0
- package/dist/mcs-unit.js.map +1 -1
- package/dist/mcs-vehicle.js +8 -3
- package/dist/mcs-vehicle.js.map +1 -1
- package/dist/node.js +10 -3
- package/dist/node.js.map +1 -1
- package/dist/oht.js +4 -0
- package/dist/oht.js.map +1 -1
- package/dist/port-flow.js +4 -0
- package/dist/port-flow.js.map +1 -1
- package/dist/port.js +4 -0
- package/dist/port.js.map +1 -1
- package/dist/scanner.js +30 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scene/root-container-override.js +160 -96
- package/dist/scene/root-container-override.js.map +1 -1
- package/dist/shelf.js +4 -0
- package/dist/shelf.js.map +1 -1
- package/dist/static-container.js +5 -0
- package/dist/static-container.js.map +1 -1
- package/dist/stocker-abnormal-bar.js +5 -1
- package/dist/stocker-abnormal-bar.js.map +1 -1
- package/dist/stocker-capacity-bar.js +253 -6
- package/dist/stocker-capacity-bar.js.map +1 -1
- package/dist/stocker-carrier-bar.js +5 -1
- package/dist/stocker-carrier-bar.js.map +1 -1
- package/dist/stocker.js +6 -0
- package/dist/stocker.js.map +1 -1
- package/dist/templates/backdrop.js +30 -0
- package/dist/templates/backdrop.js.map +1 -0
- package/dist/templates/decal.js +21 -0
- package/dist/templates/decal.js.map +1 -0
- package/dist/templates/index.js +7 -1
- 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/dist/utils/crane-position.js +83 -0
- package/dist/utils/crane-position.js.map +1 -0
- package/dist/utils/data-value.js +21 -0
- package/dist/utils/data-value.js.map +1 -0
- package/dist/utils/fit-gauge-scale.js +28 -0
- package/dist/utils/fit-gauge-scale.js.map +1 -0
- package/dist/utils/get-value-on-ranges.js +2 -4
- package/dist/utils/get-value-on-ranges.js.map +1 -1
- package/dist/utils/stocker-gauge.js +78 -0
- package/dist/utils/stocker-gauge.js.map +1 -0
- package/dist/zone-capacity-bar.js +3 -3
- package/dist/zone-capacity-bar.js.map +1 -1
- package/icons/backdrop.png +0 -0
- package/icons/decal.png +0 -0
- package/jest.config.js +10 -0
- package/package.json +4 -3
- package/src/agv.ts +7 -2
- package/src/carrier-manager.ts +223 -0
- package/src/carrier.ts +103 -10
- package/src/conveyor-join.ts +5 -2
- package/src/conveyor.ts +8 -0
- package/src/crane.ts +181 -1
- package/src/factories/agv-3d.ts +19 -36
- package/src/factories/conveyor-3d.ts +183 -103
- package/src/factories/crane-3d.ts +62 -29
- package/src/factories/crane-rail-3d.ts +17 -20
- package/src/factories/machine-3d.ts +631 -205
- package/src/factories/oht-3d.ts +30 -46
- package/src/factories/shuttle-3d.ts +14 -32
- package/src/factories/z-priority-defaults.ts +38 -0
- package/src/factories/z-priority.ts +60 -0
- package/src/features/conveyor-mixin.ts +1 -1
- package/src/features/mcs-status-default.ts +15 -1
- package/src/features/mcs-status-mixin.ts +26 -4
- package/src/features/mcs-zone-mixin.ts +1 -1
- package/src/features/parent-object-mixin.ts +17 -8
- package/src/floating-container.ts +5 -0
- package/src/index.ts +58 -34
- package/src/mcs-carrier-holder.ts +57 -34
- package/src/mcs-machine.ts +23 -0
- package/src/mcs-unit.ts +21 -1
- package/src/mcs-vehicle.ts +9 -6
- package/src/node.ts +9 -3
- package/src/oht.ts +6 -1
- package/src/port-flow.ts +5 -0
- package/src/port.ts +5 -0
- package/src/scanner.ts +29 -0
- package/src/scene/root-container-override.ts +154 -103
- package/src/shelf.ts +5 -0
- package/src/static-container.ts +5 -0
- package/src/stocker-abnormal-bar.ts +6 -1
- package/src/stocker-capacity-bar.ts +298 -7
- package/src/stocker-carrier-bar.ts +6 -1
- package/src/stocker.ts +7 -0
- package/src/templates/backdrop.ts +30 -0
- package/src/templates/decal.ts +21 -0
- package/src/templates/index.ts +7 -1
- package/src/templates/scanner.ts +20 -0
- package/src/utils/crane-position.ts +105 -0
- package/src/utils/data-value.ts +19 -0
- package/src/utils/fit-gauge-scale.ts +35 -0
- package/src/utils/get-value-on-ranges.ts +2 -4
- package/src/utils/stocker-gauge.ts +111 -0
- package/src/zone-capacity-bar.ts +3 -3
- package/translations/en.json +26 -1
- package/translations/ja.json +26 -1
- package/translations/ko.json +26 -1
- package/translations/ms.json +26 -1
- package/translations/zh.json +26 -1
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import { Model, Properties } from '@hatiolab/things-scene'
|
|
2
1
|
import { themesColorMap, themesAnimationMap } from '@fmsim/api'
|
|
3
2
|
import MCSUnit from './mcs-unit.js'
|
|
4
|
-
import
|
|
3
|
+
import { parseIfJsonString } from './utils/data-value'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Holder.data 변경 감지 시 carrierManager 에 전달할 4 키 묶음 + multiCarriers 배열.
|
|
7
|
+
* 비교 결과 단 하나라도 다르면 carrierManager.update 호출.
|
|
8
|
+
* (데이터 키는 모두 대문자 전제 — 케이스 정규화 없이 직접 읽는다)
|
|
9
|
+
*/
|
|
10
|
+
const CARRIER_DATA_KEYS = ['CARRIERNAME', 'CARRIERTYPE', 'EMPTYTYPE', 'CARRIERSTATUS'] as const
|
|
11
|
+
|
|
12
|
+
function multiCarriersChanged(a: any, b: any): boolean {
|
|
13
|
+
// MULTICARRIERS 값이 JSON 문자열로 올 수 있어 파싱 후 비교. shallow JSON 비교로 충분 —
|
|
14
|
+
// 각 항목이 4 키 primitive 만 가짐. 빈/누락은 같은 '[]' 직렬화로 동치 취급.
|
|
15
|
+
return (
|
|
16
|
+
JSON.stringify(parseIfJsonString(a?.MULTICARRIERS) ?? []) !==
|
|
17
|
+
JSON.stringify(parseIfJsonString(b?.MULTICARRIERS) ?? [])
|
|
18
|
+
)
|
|
19
|
+
}
|
|
5
20
|
|
|
6
21
|
export default class MCSCarrierHolder extends MCSUnit {
|
|
7
22
|
static get properties(): any {
|
|
@@ -28,55 +43,63 @@ export default class MCSCarrierHolder extends MCSUnit {
|
|
|
28
43
|
label: 'show-carrier-text',
|
|
29
44
|
name: 'showCarrierText'
|
|
30
45
|
}
|
|
46
|
+
// 적층 두께는 carrier 측 (Carrier.added) 에서 자기 default depth 를 결정.
|
|
47
|
+
// holder 측에는 별도 carrierDepth 속성을 두지 않는다.
|
|
31
48
|
]
|
|
32
49
|
}
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
super.ready()
|
|
51
|
+
get showOverflow() {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
38
54
|
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
get hierarchy() {
|
|
56
|
+
return {
|
|
57
|
+
...super.hierarchy,
|
|
58
|
+
components: []
|
|
41
59
|
}
|
|
42
|
-
|
|
43
|
-
const { width, height } = this.state
|
|
44
|
-
|
|
45
|
-
this.carrier = Model.compile(
|
|
46
|
-
{
|
|
47
|
-
type: 'Carrier',
|
|
48
|
-
left: 0,
|
|
49
|
-
top: 0,
|
|
50
|
-
width,
|
|
51
|
-
height,
|
|
52
|
-
showText: this.getState('showCarrierText') || false
|
|
53
|
-
},
|
|
54
|
-
this.root.app
|
|
55
|
-
) as any
|
|
56
|
-
|
|
57
|
-
this.addComponent(this.carrier!)
|
|
58
|
-
|
|
59
|
-
this.carrier!.data = this.data
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
dispose(): void {
|
|
63
|
-
this.
|
|
64
|
-
|
|
63
|
+
;(this.root as any)?.carrierManager?.update(this, {}, this.data)
|
|
65
64
|
super.dispose()
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
async ready() {
|
|
68
|
+
super.ready()
|
|
69
|
+
|
|
70
|
+
if (this._hasCarrierData(this.data)) {
|
|
71
|
+
;(this.root as any).carrierManager.update(this, this.data || {}, undefined)
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const afterData = after.data || {}
|
|
77
|
+
const beforeData = before.data || {}
|
|
78
|
+
|
|
79
|
+
let changed = false
|
|
80
|
+
for (const k of CARRIER_DATA_KEYS) {
|
|
81
|
+
if (afterData[k] !== beforeData[k]) {
|
|
82
|
+
changed = true
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!changed) {
|
|
87
|
+
changed = multiCarriersChanged(afterData, beforeData)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (changed) {
|
|
91
|
+
;(this.root as any).carrierManager.update(this, afterData, beforeData)
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
super.onchangeData(after, before)
|
|
81
95
|
}
|
|
96
|
+
|
|
97
|
+
/** data 안에 carrier 정보 (대표 또는 multiCarriers) 가 하나라도 있는지. */
|
|
98
|
+
private _hasCarrierData(data: any): boolean {
|
|
99
|
+
if (!data) return false
|
|
100
|
+
if (data.CARRIERNAME) return true
|
|
101
|
+
const list = parseIfJsonString(data.MULTICARRIERS)
|
|
102
|
+
if (Array.isArray(list) && list.some((a: any) => a?.CARRIERNAME)) return true
|
|
103
|
+
return false
|
|
104
|
+
}
|
|
82
105
|
}
|
package/src/mcs-machine.ts
CHANGED
|
@@ -26,6 +26,19 @@ export default class MCSMachine extends MCSStatusMixin(ContainerAbstract) {
|
|
|
26
26
|
return false
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* id 후보를 조회할 때 백엔드에 보낼 타입명. 값이 있으면 검색(lookup) 대상.
|
|
31
|
+
* 기본은 자기 타입. 자기 타입과 다른 타입으로 조회해야 하는 특수 케이스는 override.
|
|
32
|
+
*/
|
|
33
|
+
get idLookupType(): string | undefined {
|
|
34
|
+
return this.state.type
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** MCS 머신 — parentObject(부모 머신) 후보 수집 시 머신으로 식별. */
|
|
38
|
+
get isMcsMachine(): boolean {
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
containable(component: Component) {
|
|
30
43
|
return ['rect', 'ellipse'].includes(component.state.type)
|
|
31
44
|
}
|
|
@@ -70,6 +83,16 @@ export default class MCSMachine extends MCSStatusMixin(ContainerAbstract) {
|
|
|
70
83
|
return LEGEND_MACHINE
|
|
71
84
|
}
|
|
72
85
|
|
|
86
|
+
/**
|
|
87
|
+
* 2D 에서는 `state.alpha`(3D material 전용)를 무시한다. hidden/모델링 dim 흐림은 **코어(draw)가
|
|
88
|
+
* 일괄 처리**하므로 여기서 손대지 않는다 (component-drawing.ts draw 가 prerender 직후 faint 적용).
|
|
89
|
+
* fade 애니메이션은 보존. 3D 는 factory `updateAlpha()` 가 state.alpha 를 별도로 존중.
|
|
90
|
+
*/
|
|
91
|
+
prerender(ctx: any) {
|
|
92
|
+
super.prerender(ctx)
|
|
93
|
+
ctx.globalAlpha = 1 - this._delta.fade
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
render(ctx) {
|
|
74
97
|
var { round = 0, lineWidth } = this.state
|
|
75
98
|
var { left, top, width, height } = this.bounds
|
package/src/mcs-unit.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ContainerAbstract } from '@hatiolab/things-scene'
|
|
2
2
|
import { getPopupData } from '@fmsim/api'
|
|
3
3
|
|
|
4
4
|
import { MCSStatusMixin, MCSStatusMixinProperties } from './features/mcs-status-mixin'
|
|
@@ -13,6 +13,18 @@ export default class MCSUnit extends MCSStatusMixin(ParentObjectMixin(ContainerA
|
|
|
13
13
|
return [...MCSStatusMixinProperties, ...ParentObjectMixinProperties]
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
get showMoveHandle() {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* id 후보를 조회할 때 백엔드에 보낼 타입명. 값이 있으면 검색(lookup) 대상.
|
|
22
|
+
* 기본은 자기 타입. 특수 케이스는 override.
|
|
23
|
+
*/
|
|
24
|
+
get idLookupType(): string | undefined {
|
|
25
|
+
return this.state.type
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
get hasTextProperty() {
|
|
17
29
|
return true
|
|
18
30
|
}
|
|
@@ -49,6 +61,14 @@ export default class MCSUnit extends MCSStatusMixin(ParentObjectMixin(ContainerA
|
|
|
49
61
|
return LEGEND_UNIT
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 2D 에서는 state.alpha 무시. hidden/모델링 dim 흐림은 코어(draw)가 일괄 처리 (MCSMachine 동일).
|
|
66
|
+
*/
|
|
67
|
+
prerender(ctx: any) {
|
|
68
|
+
super.prerender(ctx)
|
|
69
|
+
ctx.globalAlpha = 1 - this._delta.fade
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
contains(x, y) {
|
|
53
73
|
var { left, top, width, height, lineWidth = 0 } = this.state
|
|
54
74
|
var extend = lineWidth / 2
|
package/src/mcs-vehicle.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Properties } from '@hatiolab/things-scene'
|
|
2
1
|
import { LEGEND_VEHICLE, Legend } from './features/mcs-status-default'
|
|
3
2
|
import MCSCarrierHolder from './mcs-carrier-holder'
|
|
4
3
|
import Node from './node'
|
|
@@ -124,22 +123,26 @@ export default class MCSVehicle extends MCSCarrierHolder {
|
|
|
124
123
|
|
|
125
124
|
if ((this.root as any).vehicleAnimation) {
|
|
126
125
|
this.trigger('waypoint', {
|
|
127
|
-
from: beforeNode,
|
|
126
|
+
from: beforeNode || afterNode,
|
|
128
127
|
to: afterNode,
|
|
129
128
|
duration: 5000
|
|
130
129
|
})
|
|
131
|
-
} else {
|
|
132
|
-
const fromCenter = beforeNode
|
|
130
|
+
} else if (beforeNode) {
|
|
131
|
+
const fromCenter = beforeNode.center
|
|
133
132
|
const toCenter = afterNode.center
|
|
134
133
|
|
|
135
|
-
const { x: startX, y: startY } = beforeNode
|
|
134
|
+
const { x: startX, y: startY } = beforeNode.transcoordS2T(fromCenter.x, fromCenter.y)
|
|
136
135
|
const { x: endX, y: endY } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
137
136
|
|
|
138
137
|
const theta = Math.atan2(endY - startY, endX - startX) + Math.PI / 2
|
|
139
138
|
|
|
140
|
-
// 위치 설정
|
|
141
139
|
this.location = { x: endX, y: endY }
|
|
142
140
|
this.setState('rotation', theta)
|
|
141
|
+
} else {
|
|
142
|
+
// beforeNode 없음: afterNode 위치로 즉시 이동
|
|
143
|
+
const toCenter = afterNode.center
|
|
144
|
+
const { x, y } = afterNode.transcoordS2T(toCenter.x, toCenter.y)
|
|
145
|
+
this.location = { x, y }
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
}
|
package/src/node.ts
CHANGED
|
@@ -28,11 +28,17 @@ export default class Node extends Shape {
|
|
|
28
28
|
static RADIUS = 8
|
|
29
29
|
|
|
30
30
|
is3dish() {
|
|
31
|
-
return
|
|
31
|
+
return true
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Node 는 기본적으로 컨테이너 드롭 입양 불가 — 라인 머신(AGVLine/OHTLine/CraneRail)만
|
|
36
|
+
* 자기 containable 화이트리스트로 Node 를 받는다. 단 FloorPlate 에는 들어갈 수 있게 허용.
|
|
37
|
+
* FloorPlate 는 things-scene 코어 컨테이너라 거기에 'Node' 를 명시할 수 없으므로
|
|
38
|
+
* Node 측에서 해결한다 (FloorPlate 의 기본 containable = component.isDescendible(this)).
|
|
39
|
+
*/
|
|
40
|
+
isDescendible(container?: any) {
|
|
41
|
+
return container?.state?.type === 'floor-plate'
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
render(context) {
|
package/src/oht.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, ComponentNature,
|
|
1
|
+
import { Component, ComponentNature, sceneComponent } from '@hatiolab/things-scene'
|
|
2
2
|
import MCSVehicle from './mcs-vehicle'
|
|
3
3
|
|
|
4
4
|
const NATURE: ComponentNature = {
|
|
@@ -20,6 +20,11 @@ export default class OHT extends MCSVehicle {
|
|
|
20
20
|
return 'black'
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** OHT 의 parentObject 는 OHTLine. */
|
|
24
|
+
get parentObjectTypes(): string[] {
|
|
25
|
+
return ['OHTLine']
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
getLegendFallback() {
|
|
24
29
|
return (this.root as any).ohtLegendTheme || super.getLegendFallback()
|
|
25
30
|
}
|
package/src/port-flow.ts
CHANGED
|
@@ -45,6 +45,11 @@ export default class PortFlow extends ParentObjectMixin(RectPath(Shape)) {
|
|
|
45
45
|
return NATURE
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/** PortFlow 의 parentObject 는 Port (유닛). */
|
|
49
|
+
get parentObjectTypes(): string[] {
|
|
50
|
+
return ['Port']
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
get hasTextProperty() {
|
|
49
54
|
return this.getState('showText') || false
|
|
50
55
|
}
|
package/src/port.ts
CHANGED
|
@@ -23,6 +23,11 @@ export default class Port extends MCSZoneMixin(MCSCarrierHolder) {
|
|
|
23
23
|
return (this.root as any).portLegendTheme || super.getLegendFallback()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/** Port 는 후보가 매우 많아 부모 머신 없이는 id 리스트를 조회하지 않는다 (machine 필수). */
|
|
27
|
+
get idLookupRequiresMachine() {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
render(ctx: CanvasRenderingContext2D) {
|
|
27
32
|
var { left, top, width, height } = this.bounds
|
|
28
33
|
|
package/src/scanner.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Component, ComponentNature, Triangle, sceneComponent } from '@hatiolab/things-scene'
|
|
2
|
+
import { MCSStatusMixin } from './features/mcs-status-mixin'
|
|
3
|
+
|
|
4
|
+
const NATURE: ComponentNature = {
|
|
5
|
+
mutable: true,
|
|
6
|
+
resizable: false,
|
|
7
|
+
rotatable: true,
|
|
8
|
+
properties: [
|
|
9
|
+
{
|
|
10
|
+
type: 'number',
|
|
11
|
+
label: 'round',
|
|
12
|
+
name: 'round',
|
|
13
|
+
property: { min: 0, max: 100, step: 1 }
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@sceneComponent('Scanner')
|
|
19
|
+
export default class Scanner extends MCSStatusMixin(Triangle) {
|
|
20
|
+
static get nature() {
|
|
21
|
+
return NATURE
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
is3dish() {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Component.memoize(Scanner.prototype, 'path', false)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component, RootContainer } from '@hatiolab/things-scene'
|
|
2
2
|
import { themesColorMap, themesColorRange, themesAnimationMap } from '@fmsim/api'
|
|
3
3
|
import { Legend } from '../features/mcs-status-default'
|
|
4
|
+
import CarrierManager from '../carrier-manager'
|
|
4
5
|
|
|
5
6
|
// RootContainer 클래스 타입 확장
|
|
6
7
|
declare module '@hatiolab/things-scene' {
|
|
@@ -24,11 +25,28 @@ declare module '@hatiolab/things-scene' {
|
|
|
24
25
|
equipmentLegendTheme?: any
|
|
25
26
|
style: any
|
|
26
27
|
vehicleAnimation?: boolean
|
|
28
|
+
// 스토커 게이지 표시 모드 — 런타임 전용 transient (모델 비저장, 항상 'capacity' 시작)
|
|
29
|
+
stockerGaugeMode: string
|
|
30
|
+
_stockerGaugeMode?: string
|
|
31
|
+
_refreshStockerGauges(): void
|
|
27
32
|
// 캐시 관련 속성 추가
|
|
28
33
|
_animationConfigCache?: Map<string, any>
|
|
34
|
+
_carrierManager?: CarrierManager
|
|
35
|
+
carrierManager: CarrierManager
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 스토커 적재율 게이지 표시 모드 — 런타임 선택지.
|
|
41
|
+
* value 는 엔진 canonical(2D StockerCapacityBar / 3D buildGauge 공통), label 은 표시명.
|
|
42
|
+
* 런타임 UI(뷰어 컨트롤)가 이 목록으로 selector 를 구성한다.
|
|
43
|
+
*/
|
|
44
|
+
export const STOCKER_GAUGE_MODES: { value: string; label: string }[] = [
|
|
45
|
+
{ value: 'capacity', label: 'Capacity' },
|
|
46
|
+
{ value: 'carrier', label: 'Carrier Status' },
|
|
47
|
+
{ value: 'abnormal', label: 'Abnormal Carrier' }
|
|
48
|
+
]
|
|
49
|
+
|
|
32
50
|
const ModelLayer = (Component as any).register('model-layer') as any
|
|
33
51
|
|
|
34
52
|
Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
@@ -194,6 +212,22 @@ Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
|
194
212
|
property: {
|
|
195
213
|
options: themesColorRange
|
|
196
214
|
}
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
type: 'legend',
|
|
218
|
+
label: '',
|
|
219
|
+
name: 'MOOD',
|
|
220
|
+
property: {
|
|
221
|
+
label: 'MOOD'
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: 'select',
|
|
226
|
+
label: 'light-preset',
|
|
227
|
+
name: 'lightPreset',
|
|
228
|
+
property: {
|
|
229
|
+
options: ['dark', 'medium', 'bright']
|
|
230
|
+
}
|
|
197
231
|
}
|
|
198
232
|
],
|
|
199
233
|
'value-property': 'text'
|
|
@@ -201,128 +235,145 @@ Object.defineProperty(ModelLayer.prototype, 'nature', {
|
|
|
201
235
|
}
|
|
202
236
|
})
|
|
203
237
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
Object.defineProperty(RootContainer.prototype, 'agvlineLegendTheme', {
|
|
219
|
-
get: function () {
|
|
220
|
-
const { agvlineLegendName } = this.rootModel.state
|
|
221
|
-
return agvlineLegendName && this.state.style[agvlineLegendName]
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
Object.defineProperty(RootContainer.prototype, 'ohtlineLegendTheme', {
|
|
226
|
-
get: function () {
|
|
227
|
-
const { ohtlineLegendName } = this.rootModel.state
|
|
228
|
-
return ohtlineLegendName && this.state.style[ohtlineLegendName]
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
Object.defineProperty(RootContainer.prototype, 'conveyorLegendTheme', {
|
|
233
|
-
get: function () {
|
|
234
|
-
const { conveyorLegendName } = this.rootModel.state
|
|
235
|
-
return conveyorLegendName && this.state.style[conveyorLegendName]
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
Object.defineProperty(RootContainer.prototype, 'stokerLegendTheme', {
|
|
240
|
-
get: function () {
|
|
241
|
-
const { stokerLegendName } = this.rootModel.state
|
|
242
|
-
return stokerLegendName && this.state.style[stokerLegendName]
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
Object.defineProperty(RootContainer.prototype, 'agvLegendTheme', {
|
|
247
|
-
get: function () {
|
|
248
|
-
const { agvLegendName } = this.rootModel.state
|
|
249
|
-
return agvLegendName && this.state.style[agvLegendName]
|
|
250
|
-
}
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
Object.defineProperty(RootContainer.prototype, 'ohtLegendTheme', {
|
|
254
|
-
get: function () {
|
|
255
|
-
const { ohtLegendName } = this.rootModel.state
|
|
256
|
-
return ohtLegendName && this.state.style[ohtLegendName]
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
Object.defineProperty(RootContainer.prototype, 'shuttleLegendTheme', {
|
|
261
|
-
get: function () {
|
|
262
|
-
const { shuttleLegendName } = this.rootModel.state
|
|
263
|
-
return shuttleLegendName && this.state.style[shuttleLegendName]
|
|
264
|
-
}
|
|
265
|
-
})
|
|
238
|
+
/**
|
|
239
|
+
* 조명 프리셋 — 3 단계 밝기 토글.
|
|
240
|
+
*
|
|
241
|
+
* fmsim 은 디테일 조명 옵션 UI 를 노출하지 않으므로 단일 select 로 단순화.
|
|
242
|
+
* medium = things-scene 의 현재 default 값과 동일 (외관 변화 없음).
|
|
243
|
+
* 사용자가 select 변경 시 onchange 가 exposure + hemiIntensity + dirLightIntensity
|
|
244
|
+
* 를 일괄 set → three-capability 의 변경 감지 핸들러가 즉시 갱신.
|
|
245
|
+
*/
|
|
246
|
+
const LIGHT_PRESETS: Record<string, { exposure: number; hemiIntensity: number; dirLightIntensity: number }> = {
|
|
247
|
+
dark: { exposure: 1.2, hemiIntensity: 4.0, dirLightIntensity: 0.6 },
|
|
248
|
+
medium: { exposure: 1.2, hemiIntensity: 4.0, dirLightIntensity: 2.0 },
|
|
249
|
+
bright: { exposure: 1.2, hemiIntensity: 4.0, dirLightIntensity: 5.0 }
|
|
250
|
+
}
|
|
266
251
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
252
|
+
const _origModelLayerOnchange = ModelLayer.prototype.onchange
|
|
253
|
+
ModelLayer.prototype.onchange = function (after: any) {
|
|
254
|
+
if (after && Object.prototype.hasOwnProperty.call(after, 'lightPreset')) {
|
|
255
|
+
const preset = LIGHT_PRESETS[after.lightPreset as string]
|
|
256
|
+
if (preset) {
|
|
257
|
+
this.set({
|
|
258
|
+
exposure: preset.exposure,
|
|
259
|
+
hemiIntensity: preset.hemiIntensity,
|
|
260
|
+
dirLightIntensity: preset.dirLightIntensity
|
|
261
|
+
})
|
|
262
|
+
}
|
|
271
263
|
}
|
|
272
|
-
|
|
264
|
+
return _origModelLayerOnchange.call(this, after)
|
|
265
|
+
}
|
|
273
266
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
// === LegendTheme / AnimationTheme value resolve ===
|
|
268
|
+
//
|
|
269
|
+
// rootModel.state.style[name] 에 저장된 legend/animation 값은 보드 직렬화
|
|
270
|
+
// 형태에 따라 (a) 정상 object 또는 (b) JSON 문자열 둘 다로 나타난다 (2026-06-09
|
|
271
|
+
// 진단 — consumer 인 carrier.ts:animationPreset 이 같은 이유로 typeof string
|
|
272
|
+
// 분기 + JSON5.parse 처리하는 선례 존재). LegendTheme getter 들이 이 분기를
|
|
273
|
+
// 두지 않아 string 통과 → getValueOnRanges 매칭 실패 → 3D gauge 의 색상이
|
|
274
|
+
// fallback 으로만 결정되던 증상.
|
|
275
|
+
//
|
|
276
|
+
// fix 는 getter 한 곳에서 lazy parse. WeakMap<state, Map<rawString, parsed>>
|
|
277
|
+
// 으로 *raw string 자체를 key* — 보드 편집으로 raw 가 바뀌면 자연 무효화,
|
|
278
|
+
// 같은 raw 의 반복 lookup 은 O(1). root 가 GC 되면 cache 도 같이 GC.
|
|
279
|
+
const _legendParseCache = new WeakMap<object, Map<string, any>>()
|
|
280
280
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
281
|
+
function parseLegendIfString(state: any, raw: any): any {
|
|
282
|
+
if (typeof raw !== 'string') return raw
|
|
283
|
+
let cache = _legendParseCache.get(state)
|
|
284
|
+
if (!cache) {
|
|
285
|
+
cache = new Map()
|
|
286
|
+
_legendParseCache.set(state, cache)
|
|
285
287
|
}
|
|
286
|
-
|
|
288
|
+
if (cache.has(raw)) return cache.get(raw)
|
|
289
|
+
let parsed: any
|
|
290
|
+
try { parsed = JSON.parse(raw) } catch { parsed = raw } // parse 실패 시 raw 보존
|
|
291
|
+
cache.set(raw, parsed)
|
|
292
|
+
return parsed
|
|
293
|
+
}
|
|
287
294
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
295
|
+
function legendThemeGetter(stateKey: string) {
|
|
296
|
+
return function (this: any) {
|
|
297
|
+
const name = this.rootModel.state[stateKey]
|
|
298
|
+
const raw = name && this.state.style[name]
|
|
299
|
+
return parseLegendIfString(this.state, raw)
|
|
292
300
|
}
|
|
293
|
-
}
|
|
301
|
+
}
|
|
294
302
|
|
|
295
|
-
Object.defineProperty(RootContainer.prototype, '
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
303
|
+
Object.defineProperty(RootContainer.prototype, 'equipmentLegendTheme', { get: legendThemeGetter('equipmentLegendName') })
|
|
304
|
+
Object.defineProperty(RootContainer.prototype, 'bufferLegendTheme', { get: legendThemeGetter('bufferLegendName') })
|
|
305
|
+
Object.defineProperty(RootContainer.prototype, 'agvlineLegendTheme', { get: legendThemeGetter('agvlineLegendName') })
|
|
306
|
+
Object.defineProperty(RootContainer.prototype, 'ohtlineLegendTheme', { get: legendThemeGetter('ohtlineLegendName') })
|
|
307
|
+
Object.defineProperty(RootContainer.prototype, 'conveyorLegendTheme', { get: legendThemeGetter('conveyorLegendName') })
|
|
308
|
+
Object.defineProperty(RootContainer.prototype, 'stokerLegendTheme', { get: legendThemeGetter('stokerLegendName') })
|
|
309
|
+
Object.defineProperty(RootContainer.prototype, 'agvLegendTheme', { get: legendThemeGetter('agvLegendName') })
|
|
310
|
+
Object.defineProperty(RootContainer.prototype, 'ohtLegendTheme', { get: legendThemeGetter('ohtLegendName') })
|
|
311
|
+
Object.defineProperty(RootContainer.prototype, 'shuttleLegendTheme', { get: legendThemeGetter('shuttleLegendName') })
|
|
312
|
+
Object.defineProperty(RootContainer.prototype, 'craneLegendTheme', { get: legendThemeGetter('craneLegendName') })
|
|
313
|
+
Object.defineProperty(RootContainer.prototype, 'shelfLegendTheme', { get: legendThemeGetter('shelfLegendName') })
|
|
314
|
+
Object.defineProperty(RootContainer.prototype, 'portLegendTheme', { get: legendThemeGetter('portLegendName') })
|
|
315
|
+
Object.defineProperty(RootContainer.prototype, 'carrierLegendTheme', { get: legendThemeGetter('carrierLegendName') })
|
|
316
|
+
Object.defineProperty(RootContainer.prototype, 'carrierAnimationTheme', { get: legendThemeGetter('carrierAnimationName') })
|
|
317
|
+
Object.defineProperty(RootContainer.prototype, 'stockerCapacityLegendTheme', { get: legendThemeGetter('stockerCapacityLegendName') })
|
|
318
|
+
Object.defineProperty(RootContainer.prototype, 'zoneCapacityLegendTheme', { get: legendThemeGetter('zoneCapacityLegendName') })
|
|
319
|
+
Object.defineProperty(RootContainer.prototype, 'batteryRateLegendTheme', { get: legendThemeGetter('batteryRateLegendName') })
|
|
301
320
|
|
|
302
|
-
Object.defineProperty(RootContainer.prototype, '
|
|
321
|
+
Object.defineProperty(RootContainer.prototype, 'vehicleAnimation', {
|
|
303
322
|
get: function () {
|
|
304
|
-
const {
|
|
305
|
-
return
|
|
323
|
+
const { vehicleAnimation } = this.rootModel.state
|
|
324
|
+
return !!vehicleAnimation
|
|
306
325
|
}
|
|
307
326
|
})
|
|
308
327
|
|
|
309
|
-
Object.defineProperty(RootContainer.prototype, '
|
|
328
|
+
Object.defineProperty(RootContainer.prototype, 'carrierManager', {
|
|
310
329
|
get: function () {
|
|
311
|
-
|
|
312
|
-
|
|
330
|
+
if (!this._carrierManager) {
|
|
331
|
+
this._carrierManager = new CarrierManager(this)
|
|
332
|
+
}
|
|
333
|
+
return this._carrierManager
|
|
313
334
|
}
|
|
314
335
|
})
|
|
315
336
|
|
|
316
|
-
|
|
337
|
+
// === 스토커 게이지 표시 모드 (런타임 전용 transient) ===
|
|
338
|
+
//
|
|
339
|
+
// 모델(state)에 저장하지 않는다 — nature 속성이 아니므로 보드 직렬화에 포함되지 않고,
|
|
340
|
+
// 보드를 다시 열면 항상 기본값 'capacity' 로 시작한다. 실행 중 뷰어 컨트롤 등이
|
|
341
|
+
// `scene.rootContainer.stockerGaugeMode = 'carrier'` 로 동적 변경한다.
|
|
342
|
+
// (주의: scene.root 은 model_layer 라 override 가 없다. 반드시 rootContainer 에 설정.
|
|
343
|
+
// 컴포넌트의 this.root 는 RootContainer(isRoot)로 해석되어 이 값을 읽는다.)
|
|
344
|
+
//
|
|
345
|
+
// 2D(StockerCapacityBar.gaugeMode) 와 3D(machine-3d buildGauge) 가 모두 이 값을 읽어
|
|
346
|
+
// 게이지 표현이 항상 일치한다. 값이 바뀌면 setter 가 2D 리드로우 + 3D 게이지 재빌드를
|
|
347
|
+
// 즉시 트리거한다.
|
|
348
|
+
Object.defineProperty(RootContainer.prototype, 'stockerGaugeMode', {
|
|
317
349
|
get: function () {
|
|
318
|
-
|
|
319
|
-
|
|
350
|
+
return this._stockerGaugeMode || 'capacity'
|
|
351
|
+
},
|
|
352
|
+
set: function (mode: string) {
|
|
353
|
+
const next = mode || 'capacity'
|
|
354
|
+
if (this._stockerGaugeMode === next) return
|
|
355
|
+
this._stockerGaugeMode = next
|
|
356
|
+
this._refreshStockerGauges()
|
|
320
357
|
}
|
|
321
358
|
})
|
|
322
359
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
360
|
+
/** scene 하위 STOCKER(3D 게이지) + StockerCapacityBar(2D 게이지) 를 새 모드로 갱신. */
|
|
361
|
+
RootContainer.prototype._refreshStockerGauges = function () {
|
|
362
|
+
const walk = (container: any) => {
|
|
363
|
+
const kids = container?.components
|
|
364
|
+
if (!Array.isArray(kids)) return
|
|
365
|
+
for (const c of kids) {
|
|
366
|
+
const type = c?.state?.type
|
|
367
|
+
if (type === 'STOCKER') {
|
|
368
|
+
// 3D 게이지는 스토커 자신의 real-object 가 그린다 → 재빌드 (2D 모드면 _realObject 없음)
|
|
369
|
+
c._realObject?.update?.()
|
|
370
|
+
} else if (type === 'StockerCapacityBar' || type === 'MCSGaugeCapacityBar') {
|
|
371
|
+
// 2D 게이지는 자식 컴포넌트가 그린다 → 리드로우
|
|
372
|
+
c.invalidate?.()
|
|
373
|
+
}
|
|
374
|
+
walk(c)
|
|
375
|
+
}
|
|
327
376
|
}
|
|
328
|
-
|
|
377
|
+
walk(this)
|
|
378
|
+
this.invalidate?.()
|
|
379
|
+
}
|
package/src/shelf.ts
CHANGED
|
@@ -19,6 +19,11 @@ export default class Shelf extends MCSZoneMixin(MCSCarrierHolder) {
|
|
|
19
19
|
return 'black'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Shelf 의 parentObject 는 STOCKER. */
|
|
23
|
+
get parentObjectTypes(): string[] {
|
|
24
|
+
return ['STOCKER']
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
getLegendFallback() {
|
|
23
28
|
return (this.root as any).shelfLegendTheme || super.getLegendFallback()
|
|
24
29
|
}
|
package/src/static-container.ts
CHANGED
|
@@ -13,6 +13,11 @@ const NATURE = {
|
|
|
13
13
|
|
|
14
14
|
@sceneComponent('container')
|
|
15
15
|
export default class StaticContainer extends Container {
|
|
16
|
+
// things-scene 의 `declare const Container_base` 패턴이 base 인스턴스 타입의 accessor
|
|
17
|
+
// 정보를 object-type property 로 평탄화하여 TS2611 발생. operato-scene 은 position
|
|
18
|
+
// override 케이스가 없어 미발현, fmsim 만 해당. things-scene 측 mixin 타입 패턴이
|
|
19
|
+
// 개선되면 제거.
|
|
20
|
+
// @ts-expect-error TS2611
|
|
16
21
|
get position() {
|
|
17
22
|
return 'static'
|
|
18
23
|
}
|