@fmsim/machine 2.0.0-beta.12 → 2.0.0-beta.15
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 +34 -0
- package/dist/carrier.js.map +1 -1
- package/dist/factories/machine-3d.js +0 -91
- package/dist/factories/machine-3d.js.map +1 -1
- package/dist/mcs-carrier-holder.js +39 -22
- package/dist/mcs-carrier-holder.js.map +1 -1
- package/dist/scene/root-container-override.js +9 -0
- package/dist/scene/root-container-override.js.map +1 -1
- 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 +40 -0
- package/src/factories/machine-3d.ts +0 -102
- package/src/mcs-carrier-holder.ts +38 -34
- package/src/scene/root-container-override.ts +12 -0
package/jest.config.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
rootDir: '../../',
|
|
3
|
+
roots: ['<rootDir>/packages/machine'],
|
|
4
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.tsx?$': 'babel-jest'
|
|
7
|
+
},
|
|
8
|
+
testRegex: 'packages/machine/.*\\.test\\.tsx?$',
|
|
9
|
+
testEnvironment: 'node'
|
|
10
|
+
}
|
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.15",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"things-scene": true,
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"prepublish": "tsc; exit 0",
|
|
23
23
|
"lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
|
|
24
24
|
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
|
|
25
|
+
"test": "jest --config jest.config.js",
|
|
25
26
|
"migration": "things-factory-migration"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
|
-
"@fmsim/api": "^2.0.0-beta.
|
|
29
|
+
"@fmsim/api": "^2.0.0-beta.15",
|
|
29
30
|
"@hatiolab/things-scene": "^10.0.0-beta.1"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
@@ -54,5 +55,5 @@
|
|
|
54
55
|
"prettier --write"
|
|
55
56
|
]
|
|
56
57
|
},
|
|
57
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "3f3e35585bb26b34ba637ac9d570a325066494b5"
|
|
58
59
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Model } from '@hatiolab/things-scene'
|
|
2
|
+
import type Carrier from './carrier'
|
|
3
|
+
import type MCSCarrierHolder from './mcs-carrier-holder'
|
|
4
|
+
|
|
5
|
+
const DYING_TIMEOUT = 1000
|
|
6
|
+
|
|
7
|
+
export default class CarrierManager {
|
|
8
|
+
private carriers: Map<string, Carrier> = new Map()
|
|
9
|
+
private dyingTimers: Map<string, ReturnType<typeof setTimeout>> = new Map()
|
|
10
|
+
private root: any
|
|
11
|
+
|
|
12
|
+
constructor(root: any) {
|
|
13
|
+
this.root = root
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get(carrierName: string): Carrier | undefined {
|
|
17
|
+
return this.carriers.get(carrierName)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
update(holder: MCSCarrierHolder, after: Record<string, any>, before: Record<string, any>): void {
|
|
21
|
+
const afterName = after?.CARRIERNAME
|
|
22
|
+
const beforeName = before?.CARRIERNAME
|
|
23
|
+
|
|
24
|
+
// 이전에는 캐리어가 있었는데 새 데이터에는 없다 → dying 후보
|
|
25
|
+
if (beforeName && (beforeName !== afterName || !afterName)) {
|
|
26
|
+
const carrier = this.carriers.get(beforeName)
|
|
27
|
+
if (carrier && carrier.parent === holder) {
|
|
28
|
+
this.scheduleDying(beforeName)
|
|
29
|
+
}
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!afterName) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// dying 상태면 구출
|
|
38
|
+
const wasDying = this.dyingTimers.has(afterName)
|
|
39
|
+
this.cancelDying(afterName)
|
|
40
|
+
|
|
41
|
+
const { width, height } = holder.state
|
|
42
|
+
let carrier = this.carriers.get(afterName)
|
|
43
|
+
|
|
44
|
+
if (!carrier) {
|
|
45
|
+
// 새로 생성
|
|
46
|
+
carrier = Model.compile(
|
|
47
|
+
{
|
|
48
|
+
id: afterName,
|
|
49
|
+
type: 'Carrier',
|
|
50
|
+
left: 0,
|
|
51
|
+
top: 0,
|
|
52
|
+
width,
|
|
53
|
+
height,
|
|
54
|
+
showText: holder.getState('showCarrierText') || false
|
|
55
|
+
},
|
|
56
|
+
this.root.app
|
|
57
|
+
) as unknown as Carrier
|
|
58
|
+
|
|
59
|
+
this.carriers.set(afterName, carrier)
|
|
60
|
+
holder.addComponent(carrier, true)
|
|
61
|
+
} else if (carrier.parent !== holder && (!beforeName && afterName)) {
|
|
62
|
+
// CARRIERNAME이 새로 나타남 (이전엔 없었음) → 다른 holder로 이동
|
|
63
|
+
;(holder as any).reparent(carrier, { animated: true, duration: 2000, ease: 'inout' })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (carrier.parent === holder) {
|
|
67
|
+
carrier.data = after
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private scheduleDying(carrierName: string): void {
|
|
72
|
+
this.cancelDying(carrierName)
|
|
73
|
+
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
this.dyingTimers.delete(carrierName)
|
|
76
|
+
|
|
77
|
+
const carrier = this.carriers.get(carrierName)
|
|
78
|
+
if (carrier) {
|
|
79
|
+
this.carriers.delete(carrierName)
|
|
80
|
+
if (!carrier.disposed) {
|
|
81
|
+
carrier.removeSelf(true)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}, DYING_TIMEOUT)
|
|
85
|
+
|
|
86
|
+
this.dyingTimers.set(carrierName, timer)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private cancelDying(carrierName: string): void {
|
|
90
|
+
const timer = this.dyingTimers.get(carrierName)
|
|
91
|
+
if (timer) {
|
|
92
|
+
clearTimeout(timer)
|
|
93
|
+
this.dyingTimers.delete(carrierName)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onCarrierDisposed(carrierName: string): void {
|
|
98
|
+
this.cancelDying(carrierName)
|
|
99
|
+
this.carriers.delete(carrierName)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
dispose(): void {
|
|
103
|
+
for (const timer of this.dyingTimers.values()) {
|
|
104
|
+
clearTimeout(timer)
|
|
105
|
+
}
|
|
106
|
+
this.dyingTimers.clear()
|
|
107
|
+
|
|
108
|
+
for (const carrier of this.carriers.values()) {
|
|
109
|
+
if (!carrier.disposed) {
|
|
110
|
+
carrier.removeSelf(true)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.carriers.clear()
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/carrier.ts
CHANGED
|
@@ -37,6 +37,40 @@ export default class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
|
|
|
37
37
|
|
|
38
38
|
set path(path) {}
|
|
39
39
|
|
|
40
|
+
|
|
41
|
+
added(parent: any) {
|
|
42
|
+
super.added(parent)
|
|
43
|
+
|
|
44
|
+
// 새 부모에 맞춰 크기 및 상태 갱신
|
|
45
|
+
if (parent) {
|
|
46
|
+
const { width, height } = parent.state || {}
|
|
47
|
+
if (width && height) {
|
|
48
|
+
this.set({ width, height })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.syncStateFromData()
|
|
53
|
+
|
|
54
|
+
// 3D 오브젝트 최초 생성 (동기적 — reparent()에서 _realObject 필요)
|
|
55
|
+
if ((this.root as any)?.threed && !this._realObject) {
|
|
56
|
+
;(this.root as any).model_layer?._threeCapability?.addObject(this)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// reparent 애니메이션은 things-scene의 reparent()가 처리
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* data로부터 carrier 상태를 동기화 (생성 시, 부모 변경 시 공통 사용)
|
|
64
|
+
*/
|
|
65
|
+
private syncStateFromData(): void {
|
|
66
|
+
const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}
|
|
67
|
+
|
|
68
|
+
this.setState('id', CARRIERNAME)
|
|
69
|
+
this.setState('EMPTYTYPE', EMPTYTYPE)
|
|
70
|
+
this.setState('CARRIERTYPE', CARRIERTYPE)
|
|
71
|
+
this.setState('CARRIERSTATUS', CARRIERSTATUS)
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
41
75
|
const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}
|
|
42
76
|
|
|
@@ -249,6 +283,12 @@ export default class Carrier extends MCSStatusMixin(ParentObjectMixin(Shape)) {
|
|
|
249
283
|
}
|
|
250
284
|
}
|
|
251
285
|
|
|
286
|
+
dispose(): void {
|
|
287
|
+
;(this.root as any)?._carrierManager?.onCarrierDisposed(this.state.id)
|
|
288
|
+
|
|
289
|
+
super.dispose()
|
|
290
|
+
}
|
|
291
|
+
|
|
252
292
|
async detailInfo() {
|
|
253
293
|
const { type, id } = this.state
|
|
254
294
|
|
|
@@ -643,111 +643,9 @@ const CARRIER_CONFIG: MCS3DConfig = {
|
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
class CarrierRealObject3D extends MCSRealObject3D {
|
|
646
|
-
private _animRaf: number | null = null
|
|
647
|
-
private _animStartTime = 0
|
|
648
|
-
|
|
649
646
|
constructor(component: Component) {
|
|
650
647
|
super(component, CARRIER_CONFIG)
|
|
651
648
|
}
|
|
652
|
-
|
|
653
|
-
build() {
|
|
654
|
-
super.build()
|
|
655
|
-
this.startCarrierAnimation()
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* CARRIERSTATUS의 애니메이션 설정을 읽어 3D에서 직접 실행.
|
|
660
|
-
* things-scene의 delta 시스템과 독립적으로 동작.
|
|
661
|
-
*/
|
|
662
|
-
private startCarrierAnimation() {
|
|
663
|
-
this.stopCarrierAnimation()
|
|
664
|
-
|
|
665
|
-
const comp = this.component as any
|
|
666
|
-
if (!comp.app?.isViewMode) return
|
|
667
|
-
|
|
668
|
-
const carrierStatus = comp.state?.CARRIERSTATUS
|
|
669
|
-
if (!carrierStatus) return
|
|
670
|
-
|
|
671
|
-
// 애니메이션 설정 가져오기
|
|
672
|
-
const config = comp.getAnimationConfig?.(carrierStatus)
|
|
673
|
-
if (!config?.animation) return
|
|
674
|
-
|
|
675
|
-
const { type, duration = 2000, scale: animScale = 1.3 } = config.animation
|
|
676
|
-
if (!type) return
|
|
677
|
-
|
|
678
|
-
this._animStartTime = performance.now()
|
|
679
|
-
|
|
680
|
-
// 애니메이션 대상: group(object3d) — mesh 개별 변환 대신 group 전체를 변환하여
|
|
681
|
-
// 회전/스케일 중심이 component의 3D 위치(= 2D 중심)와 정확히 일치하도록 함
|
|
682
|
-
const obj = this.object3d
|
|
683
|
-
|
|
684
|
-
const animate = () => {
|
|
685
|
-
if (!this._ownMesh) return
|
|
686
|
-
|
|
687
|
-
const elapsed = performance.now() - this._animStartTime
|
|
688
|
-
const progress = (elapsed % duration) / duration // 0~1 반복
|
|
689
|
-
|
|
690
|
-
switch (type) {
|
|
691
|
-
case 'heartbeat': {
|
|
692
|
-
const t = progress < 0.5 ? progress * 2 : (1 - progress) * 2
|
|
693
|
-
const s = 1 + (animScale - 1) * t
|
|
694
|
-
obj.scale.set(s, 1, s)
|
|
695
|
-
break
|
|
696
|
-
}
|
|
697
|
-
case 'rotation': {
|
|
698
|
-
obj.rotation.y = progress * Math.PI * 2
|
|
699
|
-
break
|
|
700
|
-
}
|
|
701
|
-
case 'vibration': {
|
|
702
|
-
const theta = config.animation.theta ?? 0.26
|
|
703
|
-
const current = Math.sin(progress * Math.PI * 2) * theta
|
|
704
|
-
obj.rotation.y = current
|
|
705
|
-
break
|
|
706
|
-
}
|
|
707
|
-
case 'fade': {
|
|
708
|
-
const alpha = 0.3 + 0.7 * (progress < 0.5 ? progress * 2 : (1 - progress) * 2)
|
|
709
|
-
obj.traverse((o: any) => {
|
|
710
|
-
if (o.material && !o.material.map) {
|
|
711
|
-
o.material.opacity = alpha
|
|
712
|
-
o.material.transparent = true
|
|
713
|
-
}
|
|
714
|
-
})
|
|
715
|
-
break
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
this._animRaf = requestAnimationFrame(animate)
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
this._animRaf = requestAnimationFrame(animate)
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// 자체 애니메이션이 실행 중이면 things-scene의 syncAnimationDelta 비활성화
|
|
726
|
-
syncAnimationDelta() {
|
|
727
|
-
if (!this._animRaf) {
|
|
728
|
-
super.syncAnimationDelta()
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
private stopCarrierAnimation() {
|
|
733
|
-
if (this._animRaf) {
|
|
734
|
-
cancelAnimationFrame(this._animRaf)
|
|
735
|
-
this._animRaf = null
|
|
736
|
-
}
|
|
737
|
-
// group 스케일/회전 초기화
|
|
738
|
-
this.object3d.scale.set(1, 1, 1)
|
|
739
|
-
this.object3d.rotation.y = 0
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
update() {
|
|
743
|
-
this.stopCarrierAnimation()
|
|
744
|
-
super.update()
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
clear() {
|
|
748
|
-
this.stopCarrierAnimation()
|
|
749
|
-
return super.clear()
|
|
750
|
-
}
|
|
751
649
|
}
|
|
752
650
|
|
|
753
651
|
registerRealObjectFactory('Carrier', (component: Component) => new CarrierRealObject3D(component))
|
|
@@ -1,7 +1,5 @@
|
|
|
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 Carrier from './carrier.js'
|
|
5
3
|
|
|
6
4
|
export default class MCSCarrierHolder extends MCSUnit {
|
|
7
5
|
static get properties(): any {
|
|
@@ -31,50 +29,56 @@ export default class MCSCarrierHolder extends MCSUnit {
|
|
|
31
29
|
]
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
super.ready()
|
|
32
|
+
get showOverflow() {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
get hierarchy() {
|
|
37
|
+
return {
|
|
38
|
+
...super.hierarchy,
|
|
39
|
+
components: []
|
|
41
40
|
}
|
|
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
41
|
}
|
|
61
42
|
|
|
62
43
|
dispose(): void {
|
|
63
|
-
this.
|
|
64
|
-
|
|
44
|
+
;(this.root as any)?.carrierManager?.update(this, {}, this.data)
|
|
65
45
|
super.dispose()
|
|
66
46
|
}
|
|
67
47
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
async ready() {
|
|
49
|
+
super.ready()
|
|
50
|
+
|
|
51
|
+
const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = this.data || {}
|
|
52
|
+
|
|
53
|
+
if(CARRIERNAME) {
|
|
54
|
+
;(this.root as any).carrierManager.update(this, {
|
|
55
|
+
CARRIERNAME,
|
|
56
|
+
CARRIERTYPE,
|
|
57
|
+
EMPTYTYPE,
|
|
58
|
+
CARRIERSTATUS
|
|
59
|
+
}, undefined)
|
|
72
60
|
}
|
|
73
61
|
}
|
|
74
62
|
|
|
75
63
|
onchangeData(after: Record<string, any>, before: Record<string, any>): void {
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
// CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS 이 변경되었을 때만 캐리어 관리자에게 알린다.
|
|
65
|
+
const { CARRIERNAME, CARRIERTYPE = '', EMPTYTYPE, CARRIERSTATUS } = after.data || {}
|
|
66
|
+
const { CARRIERNAME: beforeCARRIERNAME, CARRIERTYPE: beforeCARRIERTYPE = '', EMPTYTYPE: beforeEMPTYTYPE, CARRIERSTATUS: beforeCARRIERSTATUS } = before.data || {}
|
|
67
|
+
if(CARRIERNAME !== beforeCARRIERNAME ||
|
|
68
|
+
CARRIERTYPE !== beforeCARRIERTYPE ||
|
|
69
|
+
EMPTYTYPE !== beforeEMPTYTYPE ||
|
|
70
|
+
CARRIERSTATUS !== beforeCARRIERSTATUS) {
|
|
71
|
+
;(this.root as any).carrierManager.update(this, {
|
|
72
|
+
CARRIERNAME,
|
|
73
|
+
CARRIERTYPE,
|
|
74
|
+
EMPTYTYPE,
|
|
75
|
+
CARRIERSTATUS
|
|
76
|
+
}, {
|
|
77
|
+
CARRIERNAME: beforeCARRIERNAME,
|
|
78
|
+
CARRIERTYPE: beforeCARRIERTYPE,
|
|
79
|
+
EMPTYTYPE: beforeEMPTYTYPE,
|
|
80
|
+
CARRIERSTATUS: beforeCARRIERSTATUS
|
|
81
|
+
})
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
super.onchangeData(after, before)
|
|
@@ -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' {
|
|
@@ -26,6 +27,8 @@ declare module '@hatiolab/things-scene' {
|
|
|
26
27
|
vehicleAnimation?: boolean
|
|
27
28
|
// 캐시 관련 속성 추가
|
|
28
29
|
_animationConfigCache?: Map<string, any>
|
|
30
|
+
_carrierManager?: CarrierManager
|
|
31
|
+
carrierManager: CarrierManager
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -326,3 +329,12 @@ Object.defineProperty(RootContainer.prototype, 'vehicleAnimation', {
|
|
|
326
329
|
return !!vehicleAnimation
|
|
327
330
|
}
|
|
328
331
|
})
|
|
332
|
+
|
|
333
|
+
Object.defineProperty(RootContainer.prototype, 'carrierManager', {
|
|
334
|
+
get: function () {
|
|
335
|
+
if (!this._carrierManager) {
|
|
336
|
+
this._carrierManager = new CarrierManager(this)
|
|
337
|
+
}
|
|
338
|
+
return this._carrierManager
|
|
339
|
+
}
|
|
340
|
+
})
|